carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "crypto/ed25519"
5 "encoding/json"
6 "errors"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "testing"
11 "time"
12)
13
14func TestPlan14ReconstructionReplacement(t *testing.T) {
15 for _, attack := range []string{"valid", "wrong predecessor", "omitted host", "wrong source", "stale revision", "bad signature", "visible sync"} {
16 t.Run(attack, func(t *testing.T) {
17 h, s, b := rootHistoryFixture(t)
18 pub, priv, _ := ed25519.GenerateKey(nil)
19 status, err := h.ReconstructionStatus()
20 if err != nil {
21 t.Fatal(err)
22 }
23 now := time.Now().UTC()
24 old := HistoryReconstruction{Schema: 1, Kind: "carlos-release-history", Identity: status.Identity, Revision: status.Revision, Operation: "old", IssuedAt: now, Account: b.Account, App: b.App, Hosts: []HistoryReconstructionHost{{Host: b.Host, Source: "before", Revision: 1, Refusals: []Refusal{{Schema: 1, Host: b.Host, App: b.App, Version: "bad", Kind: RefusalNotUp, RefusedAt: now}}}}, Holds: map[string]HistoryReconstructionHold{"stable": {Window: time.Hour, SetAt: now}}}
25 wire, err := SignRootEnvelope(RootSigner(priv), old)
26 if err != nil {
27 t.Fatal(err)
28 }
29 h.Store = &reconstructionCrashStore{healthyRetryStore: s, stage: "intent", fail: true}
30 if err := h.Reconstruct(pub, wire, b.Account, b.App, now, func(HistoryBinding) (string, error) { return "before", nil }); err == nil {
31 t.Fatal("did not interrupt")
32 }
33 h.Store = s
34 state, err := h.reconstructionState()
35 if err != nil {
36 t.Fatal(err)
37 }
38 replacement := old
39 replacement.Operation = "replacement"
40 replacement.Replaces = state.Pending.Digest
41 replacement.Hosts = []HistoryReconstructionHost{{Host: b.Host, Source: "after", Revision: 1}}
42 replacement.Holds = nil
43 switch attack {
44 case "wrong predecessor":
45 replacement.Replaces = "wrong"
46 case "omitted host":
47 replacement.Hosts = nil
48 case "wrong source":
49 replacement.Hosts[0].Source = "wrong"
50 case "stale revision":
51 replacement.Hosts[0].Revision++
52 case "bad signature":
53 _, priv, _ = ed25519.GenerateKey(nil)
54 case "visible sync":
55 h.Store = &reconstructionCrashStore{healthyRetryStore: s, stage: "intent", fail: true}
56 }
57 wire, err = SignRootEnvelope(RootSigner(priv), replacement)
58 if err != nil {
59 t.Fatal(err)
60 }
61 resolve := func(HistoryBinding) (string, error) { return "after", nil }
62 st := NewDirStore(t.TempDir())
63 if _, err := st.Put(t.Context(), HistoryReconstructionKey(b.Account, b.App, status.Identity, status.Revision), wire, ""); err != nil {
64 t.Fatal(err)
65 }
66 err = h.ReceiveReconstruction(t.Context(), st, pub, b, now, resolve)
67 if attack == "visible sync" {
68 if err == nil {
69 t.Fatal("unsynced replacement succeeded")
70 }
71 if _, err = h.Refusals(b); err == nil {
72 t.Fatal("pending replacement granted history")
73 }
74 h.Store = s
75 err = h.ReceiveReconstruction(t.Context(), st, pub, b, now, resolve)
76 }
77 if attack != "valid" && attack != "visible sync" {
78 if err == nil {
79 t.Fatal("invalid replacement succeeded")
80 }
81 if _, err = h.Refusals(b); err == nil {
82 t.Fatal("abandonment removed deny")
83 }
84 return
85 }
86 if err != nil {
87 t.Fatal(err)
88 }
89 refs, err := h.Refusals(b)
90 if err != nil || len(refs) != 1 || refs[0].Version != "bad" {
91 t.Fatalf("lost retained refusal: %+v %v", refs, err)
92 }
93 raw, err := s.Read(rootBakeKey)
94 if err != nil {
95 t.Fatal(err)
96 }
97 var bake rootBakeRecord
98 if err := json.Unmarshal(raw, &bake); err != nil {
99 t.Fatal(err)
100 }
101 if bake.Clock.Holds[channelClockKey(b.Account, b.App, "stable")].Window != time.Hour {
102 t.Fatal("replacement shortened hold")
103 }
104 if err = h.Reconstruct(pub, wire, b.Account, b.App, now, resolve); err != nil {
105 t.Fatal("replacement replay", err)
106 }
107 })
108 }
109}
110
111func TestPlan14ReconstructionReplacementCrash(t *testing.T) {
112 now := time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC)
113 resolve := func(HistoryBinding) (string, error) { return "after", nil }
114 if dir := os.Getenv("PLAN14_REPLACEMENT_CHILD"); dir != "" {
115 pub, err := os.ReadFile(filepath.Join(dir, "pub"))
116 if err != nil {
117 t.Fatal(err)
118 }
119 wire, err := os.ReadFile(filepath.Join(dir, "wire"))
120 if err != nil {
121 t.Fatal(err)
122 }
123 s := &reconstructionCrashStore{healthyRetryStore: &healthyRetryStore{dir: dir}, stage: os.Getenv("PLAN14_REPLACEMENT_STAGE")}
124 h := &RootHistory{Store: s}
125 t.Fatal("crash boundary not reached", h.Reconstruct(pub, wire, "bab", "console", now, resolve))
126 }
127 for _, stage := range []string{"intent", "host", "app", rootBakeKey, "complete"} {
128 t.Run(stage, func(t *testing.T) {
129 h, s, b := rootHistoryFixture(t)
130 pub, priv, _ := ed25519.GenerateKey(nil)
131 status, err := h.ReconstructionStatus()
132 if err != nil {
133 t.Fatal(err)
134 }
135 old := HistoryReconstruction{Schema: 1, Kind: "carlos-release-history", Identity: status.Identity, Revision: status.Revision, Operation: "old", IssuedAt: now, Account: b.Account, App: b.App, Hosts: []HistoryReconstructionHost{{Host: b.Host, Source: "before", Revision: 1, Refusals: []Refusal{{Schema: 1, Host: b.Host, App: b.App, Version: "bad", Kind: RefusalNotUp, RefusedAt: now}}}}}
136 wire, err := SignRootEnvelope(RootSigner(priv), old)
137 if err != nil {
138 t.Fatal(err)
139 }
140 h.Store = &reconstructionCrashStore{healthyRetryStore: s, stage: "intent", fail: true}
141 if err := h.Reconstruct(pub, wire, b.Account, b.App, now, func(HistoryBinding) (string, error) { return "before", nil }); err == nil {
142 t.Fatal("missing interrupted intent")
143 }
144 h.Store = s
145 state, err := h.reconstructionState()
146 if err != nil {
147 t.Fatal(err)
148 }
149 g := old
150 g.Operation = "replacement"
151 g.Replaces = state.Pending.Digest
152 g.Hosts = []HistoryReconstructionHost{{Host: b.Host, Source: "after", Revision: 1}}
153 wire, err = SignRootEnvelope(RootSigner(priv), g)
154 if err != nil {
155 t.Fatal(err)
156 }
157 for name, raw := range map[string][]byte{"pub": pub, "wire": wire} {
158 if err := os.WriteFile(filepath.Join(s.dir, name), raw, 0600); err != nil {
159 t.Fatal(err)
160 }
161 }
162 boundary := stage
163 if stage == "host" {
164 boundary = historyKey(b)
165 }
166 if stage == "app" {
167 boundary = historyAppKey(b.Account, b.App)
168 }
169 cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14ReconstructionReplacementCrash$")
170 cmd.Env = append(os.Environ(), "PLAN14_REPLACEMENT_CHILD="+s.dir, "PLAN14_REPLACEMENT_STAGE="+boundary)
171 out, err := cmd.CombinedOutput()
172 var exited *exec.ExitError
173 if !errors.As(err, &exited) || exited.ExitCode() != 83 {
174 t.Fatalf("child: %v %s", err, out)
175 }
176 if stage != "complete" {
177 if _, err := h.Refusals(b); err == nil {
178 t.Fatal("partial replacement granted history")
179 }
180 }
181 if err := h.ResumeReconstruction(resolve); err != nil {
182 t.Fatal(err)
183 }
184 refs, err := h.Refusals(b)
185 if err != nil || len(refs) != 1 || refs[0].Version != "bad" {
186 t.Fatalf("lost refusal after crash: %+v %v", refs, err)
187 }
188 if err := h.Reconstruct(pub, wire, b.Account, b.App, now, resolve); err != nil {
189 t.Fatal("replay", err)
190 }
191 })
192 }
193}
194