carlos / platform Public

host: the two-box offline move through a lifetime carries the pair on every row it holds and opens

TestOfflineMoveE2E gains three traces with the judge on and the record
created through its lifetime: the done move holds the source at (1, 1),
lands the target at (1, 1), opens it at (1, 2) and keeps (1, 2) through
finish; an abort from landed reopens the source at (1, 2), removes the
target's row and keeps (1, 2) through finish; a record moved on before
the target's release leaves the row held. move_seq joins the record
diff's allowlist.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 8cb5e8c52e28abb03075fe323348bd89f46de21d parent 23927cc
1 file changed, +126 −3
  • internal/host/movesoffline_e2e_test.go +126 −3
diff --git a/internal/host/movesoffline_e2e_test.go b/internal/host/movesoffline_e2e_test.go
index c93c1ae79..193eca8b3 100644
--- a/internal/host/movesoffline_e2e_test.go
+++ b/internal/host/movesoffline_e2e_test.go
@@ -27,6 +27,7 @@ import (
"github.com/carlosframework/platform/internal/console"
"github.com/carlosframework/platform/internal/domains"
"github.com/carlosframework/platform/internal/hostops"
+ "github.com/carlosframework/platform/internal/hostwrite"
"github.com/carlosframework/platform/internal/instancekey"
"github.com/carlosframework/platform/internal/layout"
"github.com/carlosframework/platform/internal/lease"
@@ -302,7 +303,7 @@ func newE2EWorld(t *testing.T, opts landOpts) *e2eWorld {
// The deployment: the ops account, app's manifest and the instance's
// record at home.
wd := &offWorld{t: t, s: rs, acct: release.OpsAccount, now: now}
- if opts.judged {
+ if opts.judged || opts.lifetime {
wd.judge = provision.NewLifetimeJudge()
}
if _, _, err := rs.Get(ctx, domains.AccountManifestKey(wd.acct)); errors.Is(err, os.ErrNotExist) {
@@ -314,7 +315,11 @@ func newE2EWorld(t *testing.T, opts landOpts) *e2eWorld {
if err := provision.PutManifest(ctx, rs, wd.acct, "app", provision.Manifest{Schema: 1, App: "app", Domain: "example.com"}); err != nil {
t.Fatal(err)
}
- wd.create(offHost)
+ if opts.lifetime {
+ wd.createLifetime(offHost, 1)
+ } else {
+ wd.create(offHost)
+ }
// The boxes. ie-box is the home's one box; uk-box is uk's.
sw := &sealWorld{offWorld: wd, fake: fake, keyB64: keyB64, priv: priv}
@@ -545,7 +550,7 @@ func asJSONMap(t *testing.T, raw []byte) map[string]any {
func (w *e2eWorld) requireRecordDiff(when string) map[string]any {
w.t.Helper()
after := asJSONMap(w.t, w.recordRaw())
- allowed := map[string]bool{"region": true, "move": true, "last_move": true, "record_seq": true}
+ allowed := map[string]bool{"region": true, "move": true, "last_move": true, "record_seq": true, "move_seq": true}
keys := map[string]bool{}
for k := range w.recBefore {
keys[k] = true
@@ -1074,6 +1079,13 @@ func TestOfflineMoveE2E(t *testing.T) {
t.Run("nested source with launch files", func(t *testing.T) { offlineMoveDone(t, landOpts{nested: true}) })
t.Run("no files directory", func(t *testing.T) { offlineMoveDone(t, landOpts{noFiles: true}) })
t.Run("rollback after a tampered archive", offlineMoveRollback)
+ // Self-serve moves plan 1 Task 7: the records are created through
+ // their lifetime with the judge on, and every row the move holds or
+ // opens carries (incarnation, serving_seq) from the transition that
+ // produced it.
+ t.Run("done, through a lifetime", func(t *testing.T) { offlineMoveDone(t, landOpts{lifetime: true}) })
+ t.Run("aborted from landed, through a lifetime", offlineMoveAbortedFromLanded)
+ t.Run("a release skips a record that moved on", offlineReleaseSkipsAMovedRecord)
// Harness Run 9, finding 7: the doors driven with nothing between them
// but what production runs (each door's serial bump, the boxes' own
// nudges and the offline re-assert loop), first back to back, then with
@@ -1264,13 +1276,23 @@ func offlineMoveDone(t *testing.T, opts landOpts) {
}
w.begin()
+ if opts.lifetime {
+ w.requirePair(w.ie, registry.Pair{Incarnation: 1, ServingSeq: 1}, w.id, "after start")
+ }
w.seal()
w.flipAndLand()
+ if opts.lifetime {
+ w.requirePair(w.uk, registry.Pair{Incarnation: 1, ServingSeq: 1}, w.id, "after the landing")
+ }
if r := w.wd.reportFor(w.id, moves.OfflineRoleTarget, "uk-box"); !r.Items["env-pin"].OK {
t.Fatalf("env-pin across the two agent environments: %+v", r.Items["env-pin"])
}
w.requireStopped("after the landing")
w.open()
+ if opts.lifetime {
+ w.requirePair(w.uk, registry.Pair{Incarnation: 1, ServingSeq: 2}, "", "after open")
+ w.requirePair(w.ie, registry.Pair{Incarnation: 1, ServingSeq: 1}, w.id, "after open")
+ }
w.requireTarget()
retired := w.requireRetired()
@@ -1296,6 +1318,13 @@ func offlineMoveDone(t *testing.T, opts landOpts) {
t.Fatalf("after finish: ie lists %v, uk lists %v", w.ie.lists(offHost), w.uk.lists(offHost))
}
w.requireRetiredAfterFinish(retired)
+ if opts.lifetime {
+ // The target's row keeps open's sequence through finish.
+ w.requirePair(w.uk, registry.Pair{Incarnation: 1, ServingSeq: 2}, "", "after finish")
+ if rec := w.wd.record(); rec.Incarnation != 1 || rec.MoveSeq != 3 {
+ t.Fatalf("after finish the record is at (%d, %d), want (1, 3)", rec.Incarnation, rec.MoveSeq)
+ }
+ }
after := w.requireRecordDiff("after finish")
if after["region"] != "uk" {
@@ -1526,3 +1555,97 @@ func (w *e2eWorld) parkedTarget() map[string]bool {
}
return out
}
+
+// createLifetime creates host's record through its lifetime, in the create
+// protocol's order: the host incarnation slot at inc, live, then the record
+// carrying incarnation inc.
+func (wd *offWorld) createLifetime(host string, inc uint64) {
+ wd.t.Helper()
+ ctx := context.Background()
+ if _, err := provision.WriteHostIncarnation(ctx, wd.s, host,
+ provision.HostIncarnation{Account: wd.acct, App: "app", Incarnation: inc, State: provision.SlotLive, Intent: "create-e2e"}, ""); err != nil {
+ wd.t.Fatal(err)
+ }
+ rec := provision.Record{Schema: 1, Host: host, Slug: strings.SplitN(host, ".", 2)[0], Account: wd.acct, App: "app", Region: "", Channel: "stable", CreatedAt: wd.now, Incarnation: inc}
+ if err := provision.CreateRecord(ctx, wd.s, rec); err != nil {
+ wd.t.Fatal(err)
+ }
+}
+
+// requirePair: b's row for the host is at want, held by op ("" is ready).
+func (w *e2eWorld) requirePair(b *offBox, want registry.Pair, op, when string) {
+ w.t.Helper()
+ rt := b.row(offHost)
+ if rt == nil {
+ w.t.Fatalf("%s: %s has no row", when, b.name)
+ }
+ got := registry.Pair{Incarnation: rt.Incarnation, ServingSeq: rt.ServingSeq}
+ held := op != ""
+ if got != want || rt.HoldOp != op || registry.IsHold(rt.Status) != held || (!held && rt.Status != "ready") {
+ w.t.Fatalf("%s: %s's row is %s %q at %+v, want %q at %+v", when, b.name, rt.Status, rt.HoldOp, got, op, want)
+ }
+}
+
+// An abort from landed: the source reopens at the abort's sequence, the
+// target's row is gone, and finish leaves the source's row where the abort
+// put it.
+func offlineMoveAbortedFromLanded(t *testing.T) {
+ w := newE2EWorld(t, landOpts{lifetime: true})
+ w.begin()
+ w.requirePair(w.ie, registry.Pair{Incarnation: 1, ServingSeq: 1}, w.id, "after start")
+ w.seal()
+ w.flipAndLand()
+ if st := w.wd.record().Move.State; st != provision.OfflineLanded {
+ t.Fatalf("fixture: state %s, want landed", st)
+ }
+ w.requirePair(w.uk, registry.Pair{Incarnation: 1, ServingSeq: 1}, w.id, "after the landing")
+
+ w.accept("abort", "", provision.OfflineReverting)
+ w.until("the revert", nextIs("finish"))
+ w.requirePair(w.ie, registry.Pair{Incarnation: 1, ServingSeq: 2}, "", "after the revert")
+ w.finish("aborted")
+ w.requireRecordDiff("after the abort")
+ w.tickAll()
+ w.requirePair(w.ie, registry.Pair{Incarnation: 1, ServingSeq: 2}, "", "after finish")
+ if rt := w.uk.row(offHost); rt != nil {
+ t.Fatalf("uk-box has a row after the abort: %+v", rt)
+ }
+ if !w.ie.lists(offHost) || w.uk.lists(offHost) {
+ t.Fatalf("after the abort: ie lists %v, uk lists %v", w.ie.lists(offHost), w.uk.lists(offHost))
+ }
+ if rec := w.wd.record(); rec.MoveSeq != 3 || rec.Move != nil {
+ t.Fatalf("after finish the record is at move_seq %d, move %+v", rec.MoveSeq, rec.Move)
+ }
+}
+
+// The target's open releases from the record it observes under the locks,
+// through armOffline's releaser: a record that moved on between the pass's
+// listing and the release is not released for, and the row stays held.
+func offlineReleaseSkipsAMovedRecord(t *testing.T) {
+ w := newE2EWorld(t, landOpts{lifetime: true})
+ probe := moveCfg{}
+ armOffline(&probe, t.TempDir(), w.uk.reg, nil, nil, hostwrite.Config{}, w.uk.getenv)
+ if probe.ReleaseHoldSeq == nil {
+ t.Fatal("armOffline wires no ReleaseHoldSeq")
+ }
+ w.begin()
+ w.seal()
+ w.flipAndLand()
+ w.accept("open", "", provision.OfflineOpen)
+ moved := 0
+ w.uk.w.cfg.beforeOfflineRelease = func(host string) {
+ if moved > 0 {
+ return
+ }
+ moved++
+ w.wd.move(provision.OfflineFinishingTransition(w.id))
+ }
+ w.tick(w.uk)
+ if moved != 1 {
+ t.Fatal("fixture: the target's open never reached its release")
+ }
+ w.requirePair(w.uk, registry.Pair{Incarnation: 1, ServingSeq: 1}, w.id, "after a release over a moved record")
+ if st := w.wd.record().Move.State; st != provision.OfflineFinishing {
+ t.Fatalf("fixture: state %s", st)
+ }
+}