carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "strings"
6 "testing"
7 "time"
8
9 "github.com/carlosframework/platform/internal/activator"
10 "github.com/carlosframework/platform/internal/hostclaim"
11 "github.com/carlosframework/platform/internal/hostwrite"
12 "github.com/carlosframework/platform/internal/lease"
13 "github.com/carlosframework/platform/internal/moves"
14 "github.com/carlosframework/platform/internal/park"
15 "github.com/carlosframework/platform/internal/provision"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19// Settings spec rev 11 §4.3a: a move carries the build state with the
20// replica, and a held instance never reads as new before its first wake on
21// the destination.
22
23const stagedStateHost = "a.example.com"
24
25func stagedStateBody(host, le string) string {
26 return `{"schema":1,"host":"` + host + `","last_executed":"` + le + `","at":"2026-10-07T12:00:00Z"}`
27}
28
29func putObject(t *testing.T, s release.Store, key, body string) {
30 t.Helper()
31 if _, err := s.Put(context.Background(), key, []byte(body), ""); err != nil {
32 t.Fatal(err)
33 }
34}
35
36// legacyStaged is newBuildStates' staged prefix: the legacy round's
37// moves/<host>/, read only with host claims and Phase 1 off.
38func legacyStaged(h string) string { return activator.MovesPrefix(h) }
39
40// Before the first wake promotes a legacy landing's staging, the own prefix
41// is empty. The staged build-state.json is the state and a staged object is
42// a replica, so a held instance is held on its last_executed, not given the
43// head as a new instance.
44func TestAStagedMoveIsReadAsTheInstancesState(t *testing.T) {
45 t.Parallel()
46 b, s, _ := testBuildStates(t)
47 b.staged = legacyStaged
48 ctx := context.Background()
49 putObject(t, s, "moves/"+stagedStateHost+"/generations/0000/x.ltx", "x")
50 putObject(t, s, "moves/"+stagedStateHost+"/build-state.json", stagedStateBody(stagedStateHost, "v1"))
51 st, replica, err := b.read(ctx, stagedStateHost)
52 if err != nil || !replica || st == nil || st.LastExecuted != "v1" {
53 t.Fatalf("read = %+v, %v, %v; want the staged state and a replica", st, replica, err)
54 }
55 // And Target therefore holds a manual instance on v1, not the head.
56 d := release.Target(release.TargetInput{Pointer: &release.ChannelPointer{Version: "v3"},
57 Settings: release.UpdateSettings{Updates: release.UpdatesManual},
58 Build: release.BuildStateCopy{Known: true, Replica: replica, State: st}})
59 if d.Version != "v1" {
60 t.Fatalf("Target = %+v", d)
61 }
62 // refresh records the same in the registry copy root reads.
63 if err := b.refresh(ctx, stagedStateHost); err != nil {
64 t.Fatal(err)
65 }
66 if c := copyOf(t, b.reg); !c.Known || !c.Replica || c.State == nil || c.State.LastExecuted != "v1" {
67 t.Fatalf("copy = %+v", c)
68 }
69 // A staged replica with no build state is still a replica: never new.
70 b2, s2, _ := testBuildStates(t)
71 b2.staged = legacyStaged
72 putObject(t, s2, "moves/"+stagedStateHost+"/generations/0000/x.ltx", "x")
73 if st, replica, err := b2.read(ctx, stagedStateHost); err != nil || !replica || st != nil {
74 t.Fatalf("staged replica only: read = %+v, %v, %v", st, replica, err)
75 }
76}
77
78// The staged copy is read the way the wake's restore will use it
79// (promoteStagedReplica: staging wins when present, and its mirror deletes
80// what staging lacks). A host that comes back to a box it left keeps its
81// old lineage and build state at the own prefix; the restore replaces them
82// with the staging, so the staged build state is the one that counts.
83// With nothing staged the own prefix is read as before.
84func TestAStagedMoveOutranksAnOlderOwnStateAsTheRestoreDoes(t *testing.T) {
85 b, s, _ := testBuildStates(t)
86 b.staged = legacyStaged
87 ctx := context.Background()
88 own := "inst/" + stagedStateHost
89 putObject(t, s, own+"/generations/0000/old.ltx", "old")
90 putObject(t, s, own+"/build-state.json", stagedStateBody(stagedStateHost, "v1"))
91 if st, replica, err := b.read(ctx, stagedStateHost); err != nil || !replica || st == nil || st.LastExecuted != "v1" {
92 t.Fatalf("own prefix alone: read = %+v, %v, %v", st, replica, err)
93 }
94 putObject(t, s, "moves/"+stagedStateHost+"/generations/0000/new.ltx", "new")
95 putObject(t, s, "moves/"+stagedStateHost+"/build-state.json", stagedStateBody(stagedStateHost, "v2"))
96 if st, replica, err := b.read(ctx, stagedStateHost); err != nil || !replica || st == nil || st.LastExecuted != "v2" {
97 t.Fatalf("staged over own: read = %+v, %v, %v; want the staged v2", st, replica, err)
98 }
99 // update still writes only the own prefix: the wake promotes first.
100 if _, err := b.update(ctx, stagedStateHost, func(cur *release.BuildState) release.BuildState {
101 return release.BuildState{LastExecuted: "v2"}
102 }); err != nil {
103 t.Fatal(err)
104 }
105 if raw, _, err := s.Get(ctx, "moves/"+stagedStateHost+"/build-state.json"); err != nil || !strings.Contains(string(raw), `"v2"`) {
106 t.Fatalf("update touched the staging: %s %v", raw, err)
107 }
108}
109
110// Only the legacy round's own keys are its staging. moves/<host>/ also
111// holds Phase 1 op lanes (moves/<host>/<mv_ or rb_ id>/...), which no
112// legacy restore promotes and which say nothing about this box's state.
113func TestTheStagedReadExcludesPhase1OpLanes(t *testing.T) {
114 b, s, _ := testBuildStates(t)
115 b.staged = legacyStaged
116 ctx := context.Background()
117 for _, id := range []string{p1ID, p1RB} {
118 putObject(t, s, moves.ReplicaPrefix(stagedStateHost, id)+"0000/x.ltx", "x")
119 putObject(t, s, moves.BuildStateKey(stagedStateHost, id), stagedStateBody(stagedStateHost, "v9"))
120 }
121 if st, replica, err := b.read(ctx, stagedStateHost); err != nil || replica || st != nil {
122 t.Fatalf("read = %+v, %v, %v; want no state and no replica (op lanes only)", st, replica, err)
123 }
124 // A legacy key beside the op lanes is staging again.
125 putObject(t, s, "moves/"+stagedStateHost+"/build-state.json", stagedStateBody(stagedStateHost, "v1"))
126 if st, _, err := b.read(ctx, stagedStateHost); err != nil || st == nil || st.LastExecuted != "v1" {
127 t.Fatalf("read = %+v, %v; want the legacy staged v1", st, err)
128 }
129}
130
131// The staged read is the legacy round's: with Phase 1 on the restore never
132// promotes moves/<host>/ (ExecBackend.Phase1), and with host claims on the
133// state is at the claim holder's location, so newBuildStates leaves it off.
134func TestTheStagedReadIsOffWithPhase1OrClaimsOn(t *testing.T) {
135 cfg := &activator.Config{Bucket: "hib", Prefix: "inst"}
136 t.Setenv(moves.SwitchEnv, "off")
137 b, err := newBuildStates(cfg, nil, "")
138 if err != nil || b == nil || b.staged == nil || b.staged(stagedStateHost) != "moves/"+stagedStateHost+"/" {
139 t.Fatalf("Phase 1 off: staged = %v (%v)", b, err)
140 }
141 t.Setenv(moves.SwitchEnv, "on")
142 if b, err := newBuildStates(cfg, nil, ""); err != nil || b == nil || b.staged != nil {
143 t.Fatalf("Phase 1 on: the staged read is armed (%v)", err)
144 }
145 t.Setenv(moves.SwitchEnv, "off")
146 read := func(context.Context, string) (hostclaim.Observed, error) { return hostclaim.Observed{}, nil }
147 claimed := &activator.Config{Bucket: "hib", Prefix: "inst", HostWrites: hostwrite.Config{Enabled: true},
148 Claims: &activator.ClaimRuntime{Bucket: "data", Read: read}}
149 if b, err := newBuildStates(claimed, nil, ""); err != nil || b == nil || b.staged != nil {
150 t.Fatalf("claims on: the staged read is armed (%v)", err)
151 }
152
153 // With it off, a staged copy is not read.
154 b2, s, _ := testBuildStates(t)
155 putObject(t, s, "moves/"+stagedStateHost+"/build-state.json", stagedStateBody(stagedStateHost, "v1"))
156 putObject(t, s, "moves/"+stagedStateHost+"/generations/0000/x.ltx", "x")
157 if st, replica, err := b2.read(context.Background(), stagedStateHost); err != nil || replica || st != nil {
158 t.Fatalf("read = %+v, %v, %v; want nothing staged read", st, replica, err)
159 }
160}
161
162// p1StateBody is a build state for the Phase 1 harness's host.
163func p1StateBody(le string) string { return stagedStateBody(p1Host, le) }
164
165// storeState puts host's build state naming le at bucket/key, its ETag
166// unique to le as a real object's is to its bytes.
167func storeState(h *phase1Harness, bucket, key, le string) {
168 body := p1StateBody(le)
169 h.f.store(bucket, key, fakeMoveObject{lease.MirrorObject{ETag: "bs-" + le, Size: int64(len(body))}, body})
170}
171
172// The Phase 1 replica lane carries the build state beside the replica in
173// every round: exactly build-state.json from the claim holder's location,
174// under the lane's state/ prefix; a later round carries a later state.
175func TestThePhase1LaneCopiesTheBuildState(t *testing.T) {
176 h := newPhase1Harness(t)
177 h.start(t, "manual")
178 loc := strings.TrimSuffix(h.claim(t).Holder.Location, "/")
179 storeState(h, "data-ie", loc+"/build-state.json", "v6")
180 h.pass(t)
181 key := moves.BuildStateKey(p1Host, p1ID)
182 if got := h.f.buckets["data-uk"][key].body; got != p1StateBody("v6") {
183 t.Fatalf("the lane staged %q at %s, want the holder's build state", got, key)
184 }
185 state := stagedKeys(h, moves.StagingPrefix(p1Host, p1ID)+"state/")
186 if strings.Join(state, ",") != "build-state.json" {
187 t.Fatalf("the state lane holds %v, want only build-state.json", state)
188 }
189 storeState(h, "data-ie", loc+"/build-state.json", "v7")
190 h.pass(t)
191 if got := h.f.buckets["data-uk"][key].body; got != p1StateBody("v7") {
192 t.Fatalf("the next round staged %q, want the later state", got)
193 }
194 // Every write of the state lane went through the guard.
195 var guarded bool
196 for _, c := range h.f.stagingCalls() {
197 if c.Key == key {
198 guarded = !c.SignAt.IsZero()
199 }
200 }
201 if !guarded {
202 t.Fatalf("the state copy did not go through the guard: %+v", h.f.stagingCalls())
203 }
204}
205
206// The cut's byte-final round carries the final build state, not the one a
207// preparing round staged before the instance last ran.
208func TestTheCutsFinalRoundCopiesTheBuildState(t *testing.T) {
209 h := newCutHarness(t)
210 storeState(h.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v6")
211 h.ready(t) // the holder is at cutRoot from here
212 if got := h.f.buckets["data-uk"][moves.BuildStateKey(p1Host, p1ID)].body; got != p1StateBody("v6") {
213 t.Fatalf("the preparing rounds staged %q", got)
214 }
215 // The instance's last run before the stop wrote a later state: only the
216 // final round, after the final checkpoint, can carry it.
217 checkpoint := h.cfg.FinalCheckpoint
218 h.cfg.FinalCheckpoint = func(ctx context.Context, host, path string, holder hostclaim.Holder, check func(context.Context) error) error {
219 storeState(h.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v7")
220 return checkpoint(ctx, host, path, holder, check)
221 }
222 h.cut(t)
223 h.pass(t)
224 if m := h.move(t); m.State != provision.MoveLanding {
225 t.Fatalf("cut did not finish: %+v", m)
226 }
227 key := moves.BuildStateKey(p1Host, p1ID)
228 if got := h.f.buckets["data-uk"][key].body; got != p1StateBody("v7") {
229 t.Fatalf("the cut staged %q, want the final state", got)
230 }
231 // The seal names that copy by its version (ruling R-SEAL-STATE).
232 seal := h.seal(t)
233 if len(seal.State) != 1 || seal.State[0].Key != key || seal.State[0].VersionID != h.f.versionOf("data-uk", key) ||
234 !lease.UsableVersionID(seal.State[0].VersionID) || seal.State[0].Size != int64(len(p1StateBody("v7"))) {
235 t.Fatalf("seal state %+v, lane version %s", seal.State, h.f.versionOf("data-uk", key))
236 }
237}
238
239// The landing places the staged build state beside the location's lease,
240// holder.Location/build-state.json in the target's own bucket, before the
241// probe: the probe's and the first wake's record merge onto the moved
242// state, and a held instance never reads as new there.
243func TestTheLandingPlacesTheBuildStateBeforeTheProbe(t *testing.T) {
244 t.Parallel()
245 h := newLandingHarness(t)
246 storeState(h.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v6")
247 h.flip(t)
248 if got := h.f.buckets["data-uk"][moves.BuildStateKey(p1Host, p1ID)].body; got != p1StateBody("v6") {
249 t.Fatalf("the lane holds %q", got)
250 }
251 dst := cutRoot + "/build-state.json"
252 if _, ok := h.f.buckets["data-uk"][dst]; ok {
253 t.Fatal("the build state is at the location before the landing")
254 }
255 h.land(t)
256 h.installed(t)
257 if got := h.f.buckets["data-uk"][dst].body; got != p1StateBody("v6") {
258 t.Fatalf("after the install the location holds %q, want the moved state", got)
259 }
260 // The probe sees it.
261 if _, err := provision.SetRecordMove(context.Background(), h.s, "bab", "app", p1Host, func(rec *provision.Record) error {
262 rec.Move.CutAttempts = 1
263 return nil
264 }); err != nil {
265 t.Fatal(err)
266 }
267 p := &probeLog{t: t, h: h}
268 var seen []string
269 p.before = func(int) { seen = append(seen, h.f.buckets["data-uk"][dst].body) }
270 h.probe, h.probeLive = p.wake, p.liveProbe
271 h.holdProbed = true
272 h.land(t)
273 if len(seen) == 0 || seen[0] != p1StateBody("v6") {
274 t.Fatalf("the probe saw %q, want the moved state", seen)
275 }
276}
277
278// A landing places exactly the sealed version: a request a stale round
279// signed may land a newer version in the lane until the seal's bound, and
280// that version is never placed (ruling R-SEAL-STATE).
281func TestTheLandingPlacesTheSealedVersionNotALaterOne(t *testing.T) {
282 t.Parallel()
283 h := newLandingHarness(t)
284 h.f.keepHistory = true
285 storeState(h.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v6")
286 h.flip(t)
287 key := moves.BuildStateKey(p1Host, p1ID)
288 sealed := h.seal(t).State
289 if len(sealed) != 1 || sealed[0].VersionID != h.f.versionOf("data-uk", key) {
290 t.Fatalf("seal state %+v", sealed)
291 }
292 if _, err := h.f.PutVersionedAt(context.Background(), h.clock.Now(), "data-uk", key, []byte(p1StateBody("v5"))); err != nil {
293 t.Fatal(err)
294 }
295 if h.f.versionOf("data-uk", key) == sealed[0].VersionID {
296 t.Fatal("the late write made no new version")
297 }
298 h.land(t)
299 h.installed(t)
300 if got := h.f.buckets["data-uk"][cutRoot+"/build-state.json"].body; got != p1StateBody("v6") {
301 t.Fatalf("the location holds %q, want the sealed v6", got)
302 }
303}
304
305// A landing whose source had no build state seals none and places none,
306// even if a lane object appears after the seal.
307func TestALandingWithNoBuildStatePlacesNone(t *testing.T) {
308 t.Parallel()
309 h := newLandingHarness(t)
310 h.flip(t)
311 if st := h.seal(t).State; len(st) != 0 {
312 t.Fatalf("seal state %+v, want none", st)
313 }
314 if _, err := h.f.PutVersionedAt(context.Background(), h.clock.Now(), "data-uk", moves.BuildStateKey(p1Host, p1ID), []byte(p1StateBody("v5"))); err != nil {
315 t.Fatal(err)
316 }
317 h.land(t)
318 h.installed(t)
319 if _, ok := h.f.buckets["data-uk"][cutRoot+"/build-state.json"]; ok {
320 t.Fatal("a build state appeared at the location")
321 }
322}
323
324// A landing whose seal carries no state removes what an earlier stay left
325// beside the target's lease (Task 12a): otherwise that stay's build state
326// would read as this instance's. The removal runs under the location's
327// lease, like the placement.
328func TestALandingWithNoBuildStateRemovesALeftoverOne(t *testing.T) {
329 t.Parallel()
330 h := newLandingHarness(t)
331 key := cutRoot + "/build-state.json"
332 storeState(h.phase1Harness, "data-uk", key, "v2") // an earlier stay's
333 h.flip(t)
334 if st := h.seal(t).State; len(st) != 0 {
335 t.Fatalf("seal state %+v, want none", st)
336 }
337 h.land(t)
338 h.installed(t)
339 if o, ok := h.f.buckets["data-uk"][key]; ok {
340 t.Fatalf("an earlier stay's build state %q is still beside the lease", o.body)
341 }
342 if calls := h.vs.callsOn("delete-version data-uk/" + key); len(calls) == 0 {
343 t.Fatal("the leftover was not removed by version")
344 }
345}
346
347// A committed move's source location park acknowledges with a build state
348// beside the source's lease (the 8b reviewer's regression): the state is the
349// marker's twin, never an object of no generation that would hold the
350// park-replica open for ever.
351func TestTheSourceLocationParkAcknowledgesWithABuildState(t *testing.T) {
352 t.Parallel()
353 lh := newLandingHarness(t)
354 storeState(lh.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v6")
355 h := newFinalHarnessFrom(t, lh)
356 // One admission on the holder: with none the engine names no parked
357 // generations at all, and the build state is never classified.
358 rewriteP1Claim(t, h.s, func(c *hostclaim.Claim) {
359 c.Holder.Admissions = append(c.Holder.Admissions, hostclaim.AdmissionRecord{Region: "ie", Session: 1, Box: "ie-box", Generation: "cur", Mode: "cold"})
360 })
361 h.commit(t)
362 h.clock.Advance(park.SignatureAge + cutRequestLimit + time.Minute)
363 h.sourcePass(t)
364 for _, o := range h.claim(t).Obligations {
365 if o.Kind == "park-replica" && o.Region == "ie" {
366 t.Fatalf("the ie park-replica is still owed past its horizon: %+v", o)
367 }
368 }
369}
370
371// returnSealState is the return seal's state class, checked against the
372// lane object it names.
373func returnSealState(t *testing.T, h *rollbackHarness, rbID string) (moves.SealedObject, string) {
374 t.Helper()
375 seal, err := moves.DecodeSeal(h.claim(t).Op.Journal[moves.JournalReturnSeal])
376 if err != nil {
377 t.Fatal(err)
378 }
379 key := moves.BuildStateKey(p1Host, rbID)
380 if len(seal.State) != 1 || seal.State[0].Key != key || seal.State[0].VersionID != h.f.versionOf("data-ie", key) {
381 t.Fatalf("return seal state %+v, lane version %s", seal.State, h.f.versionOf("data-ie", key))
382 }
383 return seal.State[0], h.f.buckets["data-ie"][key].body
384}
385
386// The rollback's return lane carries the target's build state back, under
387// the lane's state/ prefix in the source's bucket, sealed by version. Once
388// the target's install has run, the state beside its lease is the one that
389// goes back: here the probe recorded v8 over the moved v6.
390func TestTheReturnLaneCarriesTheBuildState(t *testing.T) {
391 t.Parallel()
392 h := &rollbackHarness{landingHarness: newLandingHarness(t)}
393 storeState(h.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v6")
394 h.flip(t)
395 h.land(t)
396 h.installed(t)
397 h.step(t, provision.LandRestored, provision.LandProbing)
398 h.tpos = h.n
399 storeState(h.phase1Harness, "data-uk", cutRoot+"/build-state.json", "v8")
400 h.uncover(t)
401 h.rollBack(t)
402 h.rollbackPass(t)
403 rb := h.rb(t)
404 if !rb.Dirty || rb.TargetPhase != provision.RbTargetReturned {
405 t.Fatalf("rollback %+v", rb)
406 }
407 if _, body := returnSealState(t, h, rb.ID); body != p1StateBody("v8") {
408 t.Fatalf("the return lane holds %q, want the target's state", body)
409 }
410}
411
412// A return before the target's install built anything: the target's
413// location holds no state of this move, so the forward lane's sealed state
414// goes back as given (by its exact version, not the lane's latest).
415func TestAReturnBeforeTheInstallCarriesTheSealedState(t *testing.T) {
416 t.Parallel()
417 h := &rollbackHarness{landingHarness: newLandingHarness(t)}
418 h.f.keepHistory = true
419 storeState(h.phase1Harness, "data-ie", cutRoot+"/build-state.json", "v6")
420 h.flip(t)
421 if _, err := h.f.PutVersionedAt(context.Background(), h.clock.Now(), "data-uk", moves.BuildStateKey(p1Host, p1ID), []byte(p1StateBody("v5"))); err != nil {
422 t.Fatal(err)
423 }
424 h.landAt = "after-record-" + provision.LandBuilding
425 h.crashLand(t)
426 if m := h.read(t, p1Host).Move; m.LandingPhase != provision.LandBuilding {
427 t.Fatalf("landing phase %s", m.LandingPhase)
428 }
429 h.uncover(t)
430 h.rollBack(t)
431 h.rollbackPass(t)
432 rb := h.rb(t)
433 if !rb.Dirty || rb.TargetPhase != provision.RbTargetReturned {
434 t.Fatalf("rollback %+v", rb)
435 }
436 if _, body := returnSealState(t, h, rb.ID); body != p1StateBody("v6") {
437 t.Fatalf("the return lane holds %q, want the forward lane's sealed v6", body)
438 }
439}
440
441// The dirty return's install places the returned build state beside the
442// source's location lease, as the landing does.
443func TestTheReturnInstallPlacesTheBuildState(t *testing.T) {
444 t.Parallel()
445 h := newReturnHarness(t, true)
446 h.f.keepHistory = true
447 key := moves.BuildStateKey(p1Host, p1RB)
448 ver, err := h.vs.PutIfAbsent(context.Background(), "data-ie", key, []byte(p1StateBody("v8")))
449 if err != nil {
450 t.Fatal(err)
451 }
452 h.seal.State = []moves.SealedObject{{Key: key, VersionID: ver, Size: int64(len(p1StateBody("v8")))}}
453 if err := h.seal.Validate(); err != nil {
454 t.Fatal(err)
455 }
456 // A later lane version is not the sealed one and is not placed.
457 if _, err := h.f.PutVersionedAt(context.Background(), h.clock.Now(), "data-ie", key, []byte(p1StateBody("v5"))); err != nil {
458 t.Fatal(err)
459 }
460 pinned := h.read(t, p1Host).Move.Inputs
461 if refused, err := h.install(t); err != nil || refused != "" {
462 t.Fatalf("install: %q %v", refused, err)
463 }
464 h.installedReturn(t, pinned)
465 if got := h.f.buckets["data-ie"][cutRoot+"/build-state.json"].body; got != p1StateBody("v8") {
466 t.Fatalf("the source's location holds %q, want the returned state", got)
467 }
468}
469
470// A wake that promoted the staging and then failed to drop it
471// (dropStagedReplica is best-effort) leaves the pre-move staged state
472// behind its own newer record: when both hold a state, the later At wins.
473func TestALaterOwnStateOutranksALeftoverStaging(t *testing.T) {
474 b, s, _ := testBuildStates(t)
475 b.staged = legacyStaged
476 ctx := context.Background()
477 putObject(t, s, "moves/"+stagedStateHost+"/generations/0000/x.ltx", "x")
478 putObject(t, s, "moves/"+stagedStateHost+"/build-state.json", stagedStateBody(stagedStateHost, "v1"))
479 own := "inst/" + stagedStateHost
480 putObject(t, s, own+"/generations/0000/x.ltx", "x")
481 putObject(t, s, own+"/build-state.json",
482 `{"schema":1,"host":"`+stagedStateHost+`","last_executed":"v2","at":"2026-10-08T12:00:00Z"}`)
483 if st, replica, err := b.read(ctx, stagedStateHost); err != nil || !replica || st == nil || st.LastExecuted != "v2" {
484 t.Fatalf("read = %+v, %v, %v; want the own, later v2", st, replica, err)
485 }
486 // An older own state (an earlier stay's) loses to the staging.
487 _, etag, err := s.Get(ctx, own+"/build-state.json")
488 if err != nil {
489 t.Fatal(err)
490 }
491 if _, err := s.Put(ctx, own+"/build-state.json",
492 []byte(`{"schema":1,"host":"`+stagedStateHost+`","last_executed":"v0","at":"2026-10-01T12:00:00Z"}`), etag); err != nil {
493 t.Fatal(err)
494 }
495 if st, _, err := b.read(ctx, stagedStateHost); err != nil || st == nil || st.LastExecuted != "v1" {
496 t.Fatalf("read = %+v, %v; want the staged v1", st, err)
497 }
498}
499
500// An abort retires the build-state lane's receipts with the replica lane's.
501func TestAbortRetiresTheBuildStateReceipts(t *testing.T) {
502 h := newAbortHarness(t)
503 loc := strings.TrimSuffix(h.claim(t).Holder.Location, "/")
504 storeState(h.phase1Harness, "data-ie", loc+"/build-state.json", "v6")
505 h.pass(t)
506 key := moves.BuildStateReceiptsKey(p1Host, p1ID)
507 if _, ok := h.f.buckets["data-ie"][key]; !ok {
508 t.Fatalf("the state lane wrote no receipts at %s", key)
509 }
510 h.abort(t)
511 h.pass(t)
512 h.waiting(t)
513 h.ownStateGone(t)
514 if _, ok := h.f.buckets["data-ie"][key]; ok {
515 t.Fatalf("%s survived the abort", key)
516 }
517}
518