carlos / platform Public

host: two test seams, the stage round's start and statusTickAt

moveCfg.stageFault is now also called with "round-start" as a stager round
begins (after the stager finds a request it has not answered, before any
copy); the two crash tests that faulted any point now fault "round" only,
as they meant. statusTick is statusTickAt at the wall clock, so a caller
can stamp the snapshot at another clock's reading. Nil seams and the
agent's own calls are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 42e263d498f4fd631c7e5d06077ca9eaa650e1cb parent 4456d77
4 files changed, +28 −6
  • internal/host/agent.go +7 −1
  • internal/host/movesoffline_reports_test.go +12 −2
  • internal/host/movesoffline_stage.go +5 −0
  • internal/host/movespass.go +4 −3
diff --git a/internal/host/agent.go b/internal/host/agent.go
index db43a997e..0d3d1a126 100644
--- a/internal/host/agent.go
+++ b/internal/host/agent.go
@@ -3978,11 +3978,17 @@ func statusLoop(reg *registry.Registry, svc *activator.Service, buildStates func
// dials, not an opaque pseudonym only the public status page ever wanted.
// "" (every platform box) leaves CollectBox's own label untouched.
func statusTick(ctx context.Context, s release.Store, reg *registry.Registry, p status.Probes, publish bool, labelOverride string) error {
+ return statusTickAt(ctx, s, reg, p, publish, labelOverride, time.Now())
+}
+
+// statusTickAt is statusTick at the clock reading now: the box's snapshot
+// is stamped generated at now. The agent passes the wall clock (statusTick);
+// the end-to-end bridge passes its boxes' clock.
+func statusTickAt(ctx context.Context, s release.Store, reg *registry.Registry, p status.Probes, publish bool, labelOverride string, now time.Time) error {
routes, err := reg.List()
if err != nil {
return err
}
- now := time.Now()
// Aliases carries route_aliases' DNS/cert stamps (Task 2/5) up to the box
// snapshot (issue #151) — read fresh alongside routes above rather than
diff --git a/internal/host/movesoffline_reports_test.go b/internal/host/movesoffline_reports_test.go
index c6cf3e883..a585f00d3 100644
--- a/internal/host/movesoffline_reports_test.go
+++ b/internal/host/movesoffline_reports_test.go
@@ -717,7 +717,12 @@ func TestStageBookAcrossRounds(t *testing.T) {
// still lists it, the restarted stager seeds from the report, and the
// next complete round drops it.
sw.fake.BareDelete(sealIE, repl+"extra/single")
- ie.w.cfg.stageFault = func(string) error { return errors.New("crash before the report") }
+ ie.w.cfg.stageFault = func(point string) error {
+ if point != "round" {
+ return nil
+ }
+ return errors.New("crash before the report")
+ }
sw.restage()
if err := sw.stagePass(ie); err == nil {
t.Fatal("no crash")
@@ -788,7 +793,12 @@ func TestStageBookAcrossRounds(t *testing.T) {
// A crash before the report: the next round re-copies what it must and
// the book converges on the staged prefix.
sw.fake.Put(sealIE, repl+"extra/late", []byte("late"))
- ie.w.cfg.stageFault = func(string) error { return errors.New("crash before the report") }
+ ie.w.cfg.stageFault = func(point string) error {
+ if point != "round" {
+ return nil
+ }
+ return errors.New("crash before the report")
+ }
sw.restage()
if err := sw.stagePass(ie); err == nil {
t.Fatal("no crash")
diff --git a/internal/host/movesoffline_stage.go b/internal/host/movesoffline_stage.go
index bc47de2cc..7cbbf1973 100644
--- a/internal/host/movesoffline_stage.go
+++ b/internal/host/movesoffline_stage.go
@@ -91,6 +91,11 @@ func (w *movesWorker) offlineStageStep(ctx context.Context, s release.Store, rec
if prev != nil && prev.StageRequest.Equal(request) && (prev.Phase == moves.OfflinePhaseStaged || prev.Phase == moves.OfflinePhaseFailed) {
return nil // this request has had its round
}
+ if cfg.stageFault != nil {
+ if err := cfg.stageFault("round-start"); err != nil {
+ return err // a crash at the round's start, before any copy
+ }
+ }
r := moves.OfflineReport{Op: m.ID, Host: host, Box: cfg.Box, Role: moves.OfflineRoleStager, Phase: moves.OfflinePhaseStaged, StageRequest: request,
BoxID: cfg.BoxID, InstanceID: cfg.instance()}
book := w.stageBookFor(rec, prev)
diff --git a/internal/host/movespass.go b/internal/host/movespass.go
index 010ca699e..e23d9ae17 100644
--- a/internal/host/movespass.go
+++ b/internal/host/movespass.go
@@ -1052,9 +1052,10 @@ type moveCfg struct {
// is active on this box, nil when it cannot tell: the source's precheck
// and the target's ready report carry it. Nil hook: unknown.
StagePathArmed func() *bool
- // stageFault is the stage round's test seam: called after a round's
- // copies with "round", before its report, an error being a crash there.
- // Nil in production.
+ // stageFault is the stage round's test seam: called with "round-start"
+ // as a round begins, before any copy, and with "round" after a round's
+ // copies, before its report, an error being a crash there. Nil in
+ // production.
stageFault func(point string) error
// FenceAs installs, or meets again, an owned activator fence
// (activator.Service.FenceAndStopAs under a 2 s deadline,