carlos / platform Public

e2e: the claims-off witness reads the instance id as production does: none until the metadata answers, then kept for the process

TestClaimsOffInstanceIDIsPublished asserted that a later tick publishes a
changed instance id, which production never does: the agent remembers the
first id it reads (cachedInstanceID) and the moves worker asks at most once
a minute while it has none (throttledInstanceID). The bridge now arms each
box's id that way and E2E.status publishes the cached id as the agent's
status tick does; a replaced box is SetInstanceID then ArmOffline (a new
agent process), so TestReplacedParticipantAtEveryGate rearms. The witness
starts the source with no metadata answer (published empty, the request
refused with MV-O204, nothing admitted), then the answer (published,
pinned) and, a minute on, the worker's role (the move reaches staging).
The operator-doors witness also checks the legacy door's home-region
refusal by its status. Census: E2E.status and the internal/host envelope
re-pinned in place; the seal moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 359a6e9f8c57ba9b5bf880768774838bc763f61f parent 283ffb2
6 files changed, +112 −38
  • docs/superpowers/notes/census-proposal-self-serve-moves-2.md +2 −0
  • internal/e2e/selfserve/job_requests_test.go +5 −1
  • internal/e2e/selfserve/selfserve_operator_test.go +51 −20
  • internal/host/e2e_bridge.go +46 −12
  • internal/provision/plan14_root_reads_test.go +4 −2
  • internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv +4 −3
diff --git a/docs/superpowers/notes/census-proposal-self-serve-moves-2.md b/docs/superpowers/notes/census-proposal-self-serve-moves-2.md
index db6a9fb30..1cc630ad2 100644
--- a/docs/superpowers/notes/census-proposal-self-serve-moves-2.md
+++ b/docs/superpowers/notes/census-proposal-self-serve-moves-2.md
@@ -47,3 +47,5 @@ Task 9 fix round 1 adds no root read, class or task and no record write. `Store.
Task 9 fix round 2 adds no root read, class or task and no record write. `Server.estimateRules` (a rated timing that finished inside its fixed part, or within one tick of it, is left out of the rate's median; the 100 ms clamp is gone) and `Store.moveTimings` (the rate floor is now a bound parameter) are re-pinned in place with their bases extended; `selfServeMoveTimings` (no land or promote row from a landed mark without bytes) reads nothing and is no unit. The internal/console envelope is re-pinned in place and the seal moves to `603f7552…`.
Task 10b (the fourteen named end-to-end witnesses) adds no root read, class or task, no record write and no writer, and changes no production unit. It adds two test seams. `console.TestHooks.LifetimesOn` (`internal/console/testhooks.go`) reads the cutover marker through `readCutoverMarker`, the watcher's own reader, and activates the gate past the settle, so a witness can call the member's request door (which admits only with lifetimes on). Only `internal/e2e/selfserve` calls it, under the e2e build tag. `host.BoxHandle.OptDir` (`internal/host/e2e_bridge.go`) answers the test box's opt directory, a field. The witnesses themselves write only from test code: the cutover marker, a foreign open record, a damaged sealed archive in the loopback data bucket, active.json entries and records of other hosts. In the root-read overlay the `internal/console` and `internal/host` envelopes are re-pinned in place, and the seal stands. The writers list above is unchanged.
+
+Task 10b fix round 1 changes how the end-to-end bridge arms a box's instance id. Each box now arms it as `armMovesPass` arms the agent's: a per-process cache of the metadata answer, shaped like `cachedInstanceID`, read once at arm and then lazily through `throttledInstanceID`. `E2E.status` publishes the cached id in the status block, as the agent's status tick does. In the root-read overlay `E2E.status` is re-pinned in place (tests only, no new read site) and the internal/host envelope is re-pinned in place. The seal moves to `9193bc80…`. The record-write inventory and the writers list are unchanged.
diff --git a/internal/e2e/selfserve/job_requests_test.go b/internal/e2e/selfserve/job_requests_test.go
index a5008780a..e17bf6b7b 100644
--- a/internal/e2e/selfserve/job_requests_test.go
+++ b/internal/e2e/selfserve/job_requests_test.go
@@ -216,10 +216,14 @@ func TestAbandonedAdmissionIsFencedAndConditional(t *testing.T) {
// TestReplacedParticipantAtEveryGate: a pinned box whose current status
// report names another instance aborts the move "box <label> was replaced",
-// at the reservation, at the reports and at the prechecks.
+// at the reservation, at the reports and at the prechecks. The replacement
+// is a new machine under the same label: its metadata answers another id
+// and its agent is a new process (the agent remembers the first id it
+// reads for the life of its process).
func TestReplacedParticipantAtEveryGate(t *testing.T) {
replace := func(w *jobWorld, label string) {
w.e.Box(label).SetInstanceID("i-replacement")
+ w.e.Box(label).ArmOffline()
if err := w.e.Status(w.ctx); err != nil {
w.t.Fatal(err)
}
diff --git a/internal/e2e/selfserve/selfserve_operator_test.go b/internal/e2e/selfserve/selfserve_operator_test.go
index 7aed1123e..f6c3aabd0 100644
--- a/internal/e2e/selfserve/selfserve_operator_test.go
+++ b/internal/e2e/selfserve/selfserve_operator_test.go
@@ -11,6 +11,7 @@ import (
"path/filepath"
"strings"
"testing"
+ "time"
"github.com/carlosframework/platform/internal/activator"
"github.com/carlosframework/platform/internal/host"
@@ -137,6 +138,11 @@ func TestSelfServeOperatorDoorsUnchangedOnTheBridge(t *testing.T) {
if got, want := operatorLine(w.legacyMoveDoor(`{"region":"UK!"}`)), `400 "invalid region"`; got != want {
t.Errorf("the move door: %s, want %s", got, want)
}
+ // Its refusal of the home region, by its status alone (the sentence
+ // is the legacy door's own, not a recorded row).
+ if code, out := w.legacyMoveDoor(`{"region":"ie"}`); code != http.StatusBadRequest || errorOf(out) == "" {
+ t.Errorf("the move door with the home region: %d %s", code, out)
+ }
// In self-serve mode the move door is the member's: the operator's
// token is not a move-capable token, and gets the 404.
w.switchOff = false
@@ -261,12 +267,17 @@ func TestSelfServeThenOperatorReturn(t *testing.T) {
}
}
-// TestClaimsOffInstanceIDIsPublished: on a claims-off box the agent's
-// status tick publishes the box's id and its cloud instance id in its
-// status block, the instance id read when the tick runs (an instance id
-// the box learns later is published by the next tick, not held at the one
-// the worker was armed with), and the console pins a self-serve move's
-// participants by them.
+// TestClaimsOffInstanceIDIsPublished: on a claims-off box the agent reads
+// its cloud instance id lazily (cachedInstanceID: the metadata service is
+// asked on each status tick until it answers, and the first id is kept for
+// the life of the process). The source's agent starts before its metadata
+// service answers: its status tick publishes its box id and no instance id,
+// and the member's request refuses to pin it ("the box ie-box has not
+// reported recently", MV-O204), admitting nothing. Once the
+// service answers, the next tick publishes the id, the request pins both
+// boxes by what they publish, and once the moves worker asks again (at
+// most once a minute while it had no answer, throttledInstanceID) the
+// source takes its role and the move goes on through its reports.
func TestClaimsOffInstanceIDIsPublished(t *testing.T) {
w := newMemberWorld(t)
src, tgt := w.e.Box(host.E2ESourceBox), w.e.Box(host.E2ETargetBox)
@@ -282,30 +293,50 @@ func TestClaimsOffInstanceIDIsPublished(t *testing.T) {
if os.Getenv("CARLOS_HOST_CLAIM") != "" {
t.Fatal("the bridge's boxes are not claims-off")
}
- for _, b := range []struct {
- label string
- h interface{ BoxID() string }
- }{{src.Label(), src}, {tgt.Label(), tgt}} {
- if id, inst := published(b.label); id == "" || id != b.h.BoxID() || inst != "i-e2e-"+b.label {
- t.Fatalf("%s publishes box %q instance %q", b.label, id, inst)
- }
+ if id, inst := published(tgt.Label()); id == "" || id != tgt.BoxID() || inst != "i-e2e-uk-box" {
+ t.Fatalf("%s publishes box %q instance %q", tgt.Label(), id, inst)
+ }
+ // The source's agent starts before its metadata service answers.
+ src.SetInstanceID("")
+ src.ArmOffline()
+ w.boxes()
+ if id, inst := published(src.Label()); id != src.BoxID() || inst != "" {
+ t.Fatalf("with no metadata answer the source publishes box %q instance %q", id, inst)
+ }
+ code, out := w.memberDoor(http.MethodPost, host.E2EHost, "", `{"region":"uk","display_name":"Shop's account"}`)
+ if code != http.StatusConflict || errorOf(out) != "the box ie-box has not reported recently" {
+ t.Fatalf("the request with no instance id = %d %s", code, out)
}
- // The source learns a new instance id (a stop and start of its VM):
- // the next status tick publishes it.
- src.SetInstanceID("i-e2e-ie-box-2")
+ if open, err := w.cst.OpenSelfServeMoves(); err != nil || len(open) != 0 {
+ t.Fatalf("a move was admitted on a box with no instance id: %d %s, rows %v %v", code, out, open, err)
+ }
+ // The metadata service answers: the next tick publishes the id.
+ src.SetInstanceID("i-e2e-ie-box")
if err := w.e.Status(w.ctx); err != nil {
t.Fatal(err)
}
- if id, inst := published(src.Label()); id != src.BoxID() || inst != "i-e2e-ie-box-2" {
- t.Fatalf("after the new instance id: box %q instance %q", id, inst)
+ if id, inst := published(src.Label()); id != src.BoxID() || inst != "i-e2e-ie-box" {
+ t.Fatalf("after the metadata answered: box %q instance %q", id, inst)
}
- // The member's move pins both boxes by what they publish.
id := w.prepare()
r := w.row(id)
- if len(r.Participants.Source) != 1 || r.Participants.Source[0].BoxID != src.BoxID() || r.Participants.Source[0].InstanceID != "i-e2e-ie-box-2" ||
+ if len(r.Participants.Source) != 1 || r.Participants.Source[0].BoxID != src.BoxID() || r.Participants.Source[0].InstanceID != "i-e2e-ie-box" ||
r.Participants.Target.BoxID != tgt.BoxID() || r.Participants.Target.InstanceID != "i-e2e-uk-box" {
t.Fatalf("the move pinned %+v", r.Participants)
}
+ // The moves worker asks again a minute on (the boxes' clock and the
+ // console's moved together), and the source takes its role.
+ if got := src.InstanceID(); got != "" {
+ t.Fatalf("the worker re-read the instance id inside its minute: %q", got)
+ }
+ w.e.Clock().Advance(61 * time.Second)
+ w.advance(61 * time.Second)
+ if got := src.InstanceID(); got != "i-e2e-ie-box" {
+ t.Fatalf("the worker's instance id a minute on: %q", got)
+ }
+ if r := w.phase(id, "staging", 60); r.Outcome != "" {
+ t.Fatalf("the move %s", describe(r))
+ }
}
// liveBuild is the build live for the instance on box label: the version
diff --git a/internal/host/e2e_bridge.go b/internal/host/e2e_bridge.go
index a3a1c7fb0..face63c30 100644
--- a/internal/host/e2e_bridge.go
+++ b/internal/host/e2e_bridge.go
@@ -107,18 +107,21 @@ type offBox struct {
w *movesWorker
nested bool
last []byte
- // The bridge's hooks (E2E): the cloud instance id the box reports,
- // read on every use as moveCfg.InstanceIDOf, the stage path's answer
- // (moveCfg.StagePathArmed) and the disk's (moveCfg.DiskFree). All unset
- // in this package's own worlds, which then arm exactly as before: an
- // empty id, and armOffline's stagePathActive and diskBytes.
+ // The bridge's hooks (E2E): the cloud instance id the box's metadata
+ // service answers (instanceID; the agent process caches it, idSeen),
+ // the stage path's answer (moveCfg.StagePathArmed) and the disk's
+ // (moveCfg.DiskFree). All unset in this package's own worlds, which
+ // then arm exactly as before: an empty id, and armOffline's
+ // stagePathActive and diskBytes.
instanceID atomic.Pointer[string]
+ idMu sync.Mutex
+ idSeen string
stagePath func() *bool
diskFree func(dir string) (total, avail uint64, err error)
}
-// instanceIDOf is moveCfg.InstanceIDOf for the box: "" until the bridge
-// names an instance id.
+// instanceIDOf is the box's metadata service: the instance id it answers,
+// "" until the bridge names one.
func (b *offBox) instanceIDOf() string {
if id := b.instanceID.Load(); id != nil {
return *id
@@ -126,6 +129,31 @@ func (b *offBox) instanceIDOf() string {
return ""
}
+// cachedInstanceID is the agent's cachedInstanceID for this box's process:
+// the metadata answer read on every call until it is not empty, then
+// remembered for the life of the process (an agent restart, armIdentity,
+// forgets it).
+func (b *offBox) cachedInstanceID() string {
+ b.idMu.Lock()
+ defer b.idMu.Unlock()
+ if b.idSeen == "" {
+ b.idSeen = b.instanceIDOf()
+ }
+ return b.idSeen
+}
+
+// armIdentity arms cfg's instance id as armMovesPass arms the agent's, for
+// a fresh agent process: the process cache forgotten, InstanceID read once
+// at arm, and InstanceIDOf the cache asked at most once a minute (of the
+// box's clock) while it has no answer (throttledInstanceID).
+func (b *offBox) armIdentity(cfg *moveCfg) {
+ b.idMu.Lock()
+ b.idSeen = ""
+ b.idMu.Unlock()
+ cfg.InstanceID = b.cachedInstanceID()
+ cfg.InstanceIDOf = throttledInstanceID(b.cachedInstanceID, time.Minute, b.wd.clock)
+}
+
func (wd *offWorld) box(name, region string, env map[string]string) *offBox {
wd.t.Helper()
b := &offBox{wd: wd, name: name, region: region, dd: wd.t.TempDir(), opt: wd.t.TempDir(), cg: wd.t.TempDir(), env: env}
@@ -154,7 +182,8 @@ func (b *offBox) getenv(k string) string {
func (b *offBox) restart() {
b.svc = activator.New(activator.Config{Bucket: "data", Idle: time.Hour}, &fakeWakeBackend{}, b.reg)
cfg := moveCfg{Judge: b.wd.judge, Region: b.region, Box: b.name, DataDir: b.dd, OptDir: b.opt, Quiesced: b.svc.Quiesced, Route: b.reg.Get, Now: b.wd.clock,
- InstanceIDOf: b.instanceIDOf, StagePathArmed: b.stagePath, DiskFree: b.diskFree}
+ StagePathArmed: b.stagePath, DiskFree: b.diskFree}
+ b.armIdentity(&cfg)
armOffline(&cfg, b.dd, b.reg, b.svc, nil, hostwrite.Config{}, b.getenv)
// The stage-requested marker is root's business, and its throttled
// timer would outlive the test's directories.
@@ -438,11 +467,12 @@ func (lw *landWorld) arm(b *offBox) {
bucket, region := b.getenv("CARLOS_HIBERNATE_BUCKET"), b.getenv("CARLOS_HIBERNATE_REGION")
b.svc = activator.New(activator.Config{Bucket: bucket, Idle: time.Hour}, &fakeWakeBackend{}, b.reg)
cfg := moveCfg{Judge: b.wd.judge, Region: b.region, Box: b.name, DataDir: b.dd, OptDir: b.opt, Quiesced: b.svc.Quiesced, Route: b.reg.Get, Now: b.wd.clock,
- InstanceIDOf: b.instanceIDOf, StagePathArmed: b.stagePath, DiskFree: b.diskFree,
+ StagePathArmed: b.stagePath, DiskFree: b.diskFree,
OwnBucket: bucket, OwnBucketRegion: region, OwnPrefix: sealPrefix,
Mirror: func(ctx context.Context, dst string) (lease.MirrorStore, error) {
return lease.NewS3Mirror(ctx, region, dst)
}}
+ b.armIdentity(&cfg)
armOffline(&cfg, b.dd, b.reg, b.svc, nil, hostwrite.Config{}, b.getenv)
cfg.Offline = newOfflineView(cfg, nil)
cfg.ReleaseHold = releaseHeld(b.reg, nil)
@@ -1275,7 +1305,8 @@ func (e *E2E) status(ctx context.Context, x *e2eBox) error {
if err != nil {
return fmt.Errorf("%s: status tick: %w", b.name, err)
}
- p := status.Probes{MachineID: b.name}
+ // The instance id as the agent's status tick takes it (cachedInstanceID).
+ p := status.Probes{MachineID: b.name, InstanceID: b.cachedInstanceID()}
selfServeStatusProbes(ctx, &p, b.w, b.w.cfg.BoxID, b.getenv, routes, now)
if err := statusTickAt(ctx, e.lw.wd.s, b.reg, p, false, b.name, now); err != nil {
return fmt.Errorf("%s: status tick: %w", b.name, err)
@@ -1336,8 +1367,11 @@ func (h BoxHandle) BoxID() string { return h.x.b.w.cfg.BoxID }
// status block report it (moveCfg.instance).
func (h BoxHandle) InstanceID() string { return h.x.b.w.cfg.instance() }
-// SetInstanceID makes the box report another cloud instance id from now
-// on: a box replaced under the same label.
+// SetInstanceID sets what the box's metadata service answers from now on
+// ("" for a service that does not answer yet). The agent remembers the
+// first id it reads for the life of its process (cachedInstanceID), as
+// production does, so a box replaced under the same label is SetInstanceID
+// and then ArmOffline (the new machine's agent process).
func (h BoxHandle) SetInstanceID(id string) { h.x.b.instanceID.Store(&id) }
// Region is the box's placement ("" is the home).
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 1a18331db..4a6ac607e 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -1361,8 +1361,10 @@ func TestPlan14RootReadCensus(t *testing.T) {
// estimateRules and SetSelfServeReason in place, which moves it again.
// Its fix round 2 re-pins estimateRules (rated rows inside the fixed
// part left out) and moveTimings (the floor bound) in place, which
- // moves it once more.
- seal = "603f7552a0b96baaccc745d86c97b6ded5dfbe9f291b7fc98234cc0cb505f582"
+ // moves it once more. Task 10b's fix round 1 re-pins E2E.status in
+ // place (the bridge publishes each box's cached instance id as the
+ // agent's status tick does), which moves it again.
+ seal = "9193bc80e8f38382d2505c182dae0e5e8b2f367d0a16cf9d9faeac875a1e0843"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv b/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv
index 89e005922..7245b27dc 100644
--- a/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv
+++ b/internal/provision/testdata/plan14-root-reads-self-serve-moves-2.tsv
@@ -123,15 +123,15 @@ internal/host/movespass.go armOffline legacy accepted-legacy accepted-legacy;rev
internal/host/probes.go stagePathActive legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=root-input (J4, as configPathActive): systemctl is-active carlos-stage.path for the precheck's and the ready report's stage-path fact; self-serve moves 2 Task 6 file:Command@42 f7cbe0e2ba971d201f8fc7111eb4cebac9c9bae14cc826366c06b9e8bd3eecb8
@legacy 6febdffd5ed3a46ca7c8296e2bf12d8249561e26ab6479eb672f40a96b13f0f4 internal/status/snapshot.go CollectBox legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=root-route; self-serve moves 2 Task 6: carries Probes.BoxID, Lanes and Hosts into the private BoxMetrics only (never the public feed); read sites unchanged, offsets moved registry:Alias@4465,registry:CertExpiresAt@3160,registry:CertExpiresAt@3396,registry:CertExpiresAt@4580,registry:DBPath@2495,registry:DBPath@2553,registry:DNSCheckedAt@3365,registry:DNSCheckedAt@4549,registry:DNSStatus@3139,registry:DNSStatus@3334,registry:DNSStatus@4519,registry:Hibernate@3853,registry:Host@2295,registry:Host@2437,registry:Host@2666,registry:Host@3280,registry:Host@3315,registry:Host@4500,registry:Kind@2473,registry:Kind@3807,registry:Platform@2152,registry:ServingVersion@2324,registry:ServingVersion@2447,registry:Socket@3988,registry:Version@2186,registry:Version@2305,whole:Route@1581,whole:RouteAlias@4362 e874e3166d22d437b4d4daf2b1dc8b2d56bc853076cc132b9b2c1d30f9a2b1be
internal/status/snapshot.go CollectBox legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=root-route; self-serve moves 2 Task 6: carries Probes.BoxID, Lanes and Hosts into the private BoxMetrics only (never the public feed); read sites unchanged, offsets moved registry:Alias@4465,registry:CertExpiresAt@3160,registry:CertExpiresAt@3396,registry:CertExpiresAt@4580,registry:DBPath@2495,registry:DBPath@2553,registry:DNSCheckedAt@3365,registry:DNSCheckedAt@4549,registry:DNSStatus@3139,registry:DNSStatus@3334,registry:DNSStatus@4519,registry:Hibernate@3853,registry:Host@2295,registry:Host@2437,registry:Host@2666,registry:Host@3280,registry:Host@3315,registry:Host@4500,registry:Kind@2473,registry:Kind@3807,registry:Platform@2152,registry:ServingVersion@2324,registry:ServingVersion@2447,registry:Socket@3988,registry:Version@2186,registry:Version@2305,whole:Route@1581,whole:RouteAlias@4362 e874e3166d22d437b4d4daf2b1dc8b2d56bc853076cc132b9b2c1d30f9a2b1be
-@coverage internal/host ae35dd236ba1b2ee8e0e3244f4639cbd0ea7b5c1a95cf8a95c73c242a5742f85
+@coverage internal/host 2594da7b6ef031ae2e1bddafaeacb08f9d110003eedf4f57c9f375b14436c4ef
@coverage internal/lease 7e2a4a3652f964aaf1ee32356d2538a292c66ee0aaf9e622cf087b644aeaaa61
@coverage internal/status 6bf65bf3b04fdc150e2ec35d2d0d67046cf37b7c60a9dcde1c2204b75bcf8d2b
# Task 6 fix round 1 (the import cycle internal/park's tests saw), rev3=PROPOSED-self-serve-moves-2: PlacementMatches moves into internal/regionname (park.PlacementMatches delegates to it, unchanged in behaviour) so internal/moves no longer imports internal/park; no root read is added or moved. Envelopes: internal/moves in place, internal/park and internal/regionname added.
@coverage internal/park 778a86513bc64068b93e257c09972fbc307ebe401a125a61003f3ad26a28f611
@coverage internal/regionname edcc0632066dc324b7df2d59a03bebcc0bf9e243bd0892fcddcfa633747977f4
# Task 10a (the exported end-to-end bridge), rev3=PROPOSED-self-serve-moves-2: the offline move tests' two-box world moves out of the _test.go files into internal/host/e2e_bridge.go, an untagged file of package host, with the exported bridge (E2E) after it. Seven units of that file read root inputs, every one in the test's own temporary directories, registries and loopback buckets; each is frozen accepted-legacy beside leasetest's, tests only. No production unit changes. The internal/host envelope is refreshed in place in its Task 6 row.
-@legacy 9bb3a9d24598c3583d0b838c6f2c8e6939e3ec5295223bc1c0d32eb0448d64a4 internal/host/e2e_bridge.go E2E.status legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): lists the bridge box's own registry in the test's temporary directory (Registry.List) and hands the rows to selfServeStatusProbes and statusTickAt at the bridge clock's reading, as statusLoop does at the wall clock's; self-serve moves 2 Task 10a (fix round 1: statusTickAt) whole:Route@333 fe5d65cec4d1b3827eda583a77c9d1bc797205a87d5d37ab62e14df5fbd4e137
-internal/host/e2e_bridge.go E2E.status legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): lists the bridge box's own registry in the test's temporary directory (Registry.List) and hands the rows to selfServeStatusProbes and statusTickAt at the bridge clock's reading, as statusLoop does at the wall clock's; self-serve moves 2 Task 10a (fix round 1: statusTickAt) whole:Route@333 fe5d65cec4d1b3827eda583a77c9d1bc797205a87d5d37ab62e14df5fbd4e137
+@legacy ec18aafcaea342467be07259d74e461a3fdd713571505bd1c4335431053c54c8 internal/host/e2e_bridge.go E2E.status legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): lists the bridge box's own registry in the test's temporary directory (Registry.List) and hands the rows to selfServeStatusProbes and statusTickAt at the bridge clock's reading, as statusLoop does at the wall clock's; the status block's instance id is the box process's cached metadata answer, as the agent's status tick takes it (cachedInstanceID); self-serve moves 2 Task 10a (fix round 1: statusTickAt), Task 10b fix round 1 (the cached instance id) whole:Route@443 413d358032f876109eb5bd102e848dc49744a53a3de1015c138bed567b351632
+internal/host/e2e_bridge.go E2E.status legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): lists the bridge box's own registry in the test's temporary directory (Registry.List) and hands the rows to selfServeStatusProbes and statusTickAt at the bridge clock's reading, as statusLoop does at the wall clock's; the status block's instance id is the box process's cached metadata answer, as the agent's status tick takes it (cachedInstanceID); self-serve moves 2 Task 10a (fix round 1: statusTickAt), Task 10b fix round 1 (the cached instance id) whole:Route@443 413d358032f876109eb5bd102e848dc49744a53a3de1015c138bed567b351632
@legacy bc7fc8c932d216174f2db676b1f566d94d181624cd13e4c776908ac6af1c300a internal/host/e2e_bridge.go offBox.row legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): reads one row of the test box's own registry (Registry.Get); moved unchanged from movesoffline_test.go but for testing.TB; self-serve moves 2 Task 10a whole:Route@145 95c7619a77fc2d84c66952e74a7f04a5de8b0275d11cafd05aaf924941230a80
internal/host/e2e_bridge.go offBox.row legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): reads one row of the test box's own registry (Registry.Get); moved unchanged from movesoffline_test.go but for testing.TB; self-serve moves 2 Task 10a whole:Route@145 95c7619a77fc2d84c66952e74a7f04a5de8b0275d11cafd05aaf924941230a80
@legacy cdaee7d732a5ba5c04a82a1c9e89c9fb1245b3f105d3a41b7172e2248210b215 internal/host/e2e_bridge.go offWorld.box legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=tests only (internal/host/e2e_bridge.go: the offline move tests' two-box world and the exported end-to-end bridge, called by internal/host's tests and by internal/e2e/selfserve under the e2e build tag and by no production code path; linked into the agent as dead code, stated and accepted by the plan): opens a fresh registry in the test's temporary directory (registry.Open) for one test box; moved unchanged from movesoffline_test.go but for testing.TB; self-serve moves 2 Task 10a file:Open@227 c07455d80534d8d75d233aea2f72ceffedb4b2ebdfb70138de981fbd203bac30
@@ -234,3 +234,4 @@ internal/console/selfserve_store.go Store.SetSelfServeReason legacy accepted-leg
internal/console/selfserve_estimate.go Store.moveTimings legacy accepted-legacy accepted-legacy;rev3=PROPOSED-self-serve-moves-2;basis=console.service user carlos-console, J4 root-input: reads the region pair's newest move_timings rows of one phase under a fixed condition (all rows, the rated rows or the rows under the rate floor) from the console SQLite, for MoveTimings and the estimate's window; no bucket or root action; fix round 2: the rate floor is a bound parameter file:Query@181 7c6d87943159d50771a66237824dc8be7c25e2334d688010450aa7357bc88b16
# Task 9 fix round 2, rev3=PROPOSED-self-serve-moves-2: Server.estimateRules (a rated row inside its fixed part, or within a tick of it, carries no rate and is left out) and Store.moveTimings (the floor bound) are re-pinned in place; the internal/console envelope is re-pinned in place.
# Task 10b (the end-to-end witnesses), rev3=PROPOSED-self-serve-moves-2: no root read, class or task is added and no unit changes. Two test seams are added: console.TestHooks.LifetimesOn (internal/console/testhooks.go: it reads the cutover marker through readCutoverMarker, the watcher's own reader, and activates the gate; called only by internal/e2e/selfserve under the e2e build tag) and host.BoxHandle.OptDir (internal/host/e2e_bridge.go: the test box's opt directory, a field). The internal/console and internal/host envelopes are re-pinned in place; the seal stands.
+# Task 10b fix round 1, rev3=PROPOSED-self-serve-moves-2: the end-to-end bridge arms each box's instance id as armMovesPass arms the agent's (a per-process cache of the metadata answer, cachedInstanceID's shape, read at arm and lazily through throttledInstanceID) and E2E.status publishes it in the status block as the agent's status tick does, so E2E.status is re-pinned in place (tests only, no new read site); the internal/host envelope is re-pinned in place; the seal moves.