carlos / platform Public

region HA 2a: the move room check refuses only where room is known missing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 7722ad72bfef8b9471eedc7d6a8b010f28ad0860 parent 09962b5
7 files changed, +234 −33
  • internal/console/apiinstancemove.go +7 −3
  • internal/console/boxselect.go +29 −11
  • internal/console/boxselect_ha_test.go +187 −9
  • internal/hostops/executor.go +3 −3
  • internal/provision/plan14_root_reads_test.go +1 −1
  • internal/provision/plan9_b8_census_test.go +2 −1
  • internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv +5 −5
diff --git a/internal/console/apiinstancemove.go b/internal/console/apiinstancemove.go
index 841c8f1aa..a8c747fb2 100644
--- a/internal/console/apiinstancemove.go
+++ b/internal/console/apiinstancemove.go
@@ -685,9 +685,13 @@ func (s *Server) handleAPIInstanceMoveRequest(w http.ResponseWriter, r *http.Req
from := s.homeRegionOut(rec.Region)
sel, err := s.selectMoveBoxes(ctx, host, to, rec.Region, acct.Sqid+"/"+app)
// No box in the destination has room (region HA spec 2 §7, H27), or the
- // read that would tell was incomplete: the claim doors' answers.
- var full *noCapacityError
- if errors.As(err, &full) || errors.Is(err, hostops.ErrEvidenceIncomplete) {
+ // read that would tell could not be completed: the claim doors' answers,
+ // not the protocol floor's below.
+ var (
+ full *noCapacityError
+ check *roomCheckError
+ )
+ if errors.As(err, &full) || errors.As(err, &check) {
writeClaimResponseError(w, err)
return
}
diff --git a/internal/console/boxselect.go b/internal/console/boxselect.go
index 79a1808bd..40ad1c723 100644
--- a/internal/console/boxselect.go
+++ b/internal/console/boxselect.go
@@ -172,23 +172,41 @@ func (s *Server) verifiedRoster(ctx context.Context, snap edgesSnapshot, aws map
return out, nil
}
+// roomCheckError is a placement read moveRoomCheck could not complete (an
+// incomplete snapshot, or an error reading it). It is not a refusal for
+// want of room: the move door answers it as temporarily unavailable
+// (writeClaimResponseError), never as the protocol floor's 503. It wraps
+// the read's error, so hostops.ErrEvidenceIncomplete is still found.
+type roomCheckError struct{ err error }
+
+func (e *roomCheckError) Error() string { return "move room check: " + e.err.Error() }
+func (e *roomCheckError) Unwrap() error { return e.err }
+
// moveRoomCheck is a move's destination under region HA (spec 2 §7,
-// H27): the destination is a door that chooses a box, so when none of its
-// candidates is a box the create door's chooser would place on (roles,
-// headroom, fresh edges files), the move is the no-room refusal. It only
-// refuses (census ruling region-ha-2a rev 2): the candidates, and so the
-// destination main's selection picks, are never narrowed or changed. The
-// source is never checked, so an instance on an excluded box can still
-// move out (A10-3). With region HA off, or no claim runtime, it answers nil
-// and no roster is read.
+// H27): the destination is a door that chooses a box, so when it has
+// candidates and none of them is a box the create door's chooser would
+// place on (roles, headroom, fresh edges files), the move is the no-room
+// refusal. It only refuses (census ruling region-ha-2a rev 2): the
+// candidates, and so the destination main's selection picks, are never
+// narrowed or changed. It answers nil, and main's own checks answer, when
+// region HA is off or there is no claim runtime (no roster is read), when
+// the destination has no candidates (main's "no box publishes an edges
+// file yet"), and when the roster does not place the region
+// (hostops.ErrContract), matching roomIn: it refuses only where placement
+// is known to have no room. The source is never checked, so an instance on
+// an excluded box can still move out (A10-3). A read that could not be
+// completed is a *roomCheckError, never no room.
func (s *Server) moveRoomCheck(ctx context.Context, to string, cands []boxCandidate) error {
x := s.placementChooser("")
- if x == nil {
+ if x == nil || len(cands) == 0 {
return nil
}
boxes, err := x.PlaceableBoxes(ctx, s.cfg.claimPlacementRegion(to))
- if err != nil && !errors.Is(err, hostops.ErrContract) {
- return err
+ switch {
+ case errors.Is(err, hostops.ErrContract):
+ return nil
+ case err != nil:
+ return &roomCheckError{err}
}
for _, c := range cands {
if slices.Contains(boxes, c.BoxID) {
diff --git a/internal/console/boxselect_ha_test.go b/internal/console/boxselect_ha_test.go
index 9a53e52fb..6bcaa6467 100644
--- a/internal/console/boxselect_ha_test.go
+++ b/internal/console/boxselect_ha_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "github.com/carlosframework/platform/internal/boxchoice"
"github.com/carlosframework/platform/internal/hostactors"
"github.com/carlosframework/platform/internal/hostclaim"
"github.com/carlosframework/platform/internal/hostops"
@@ -45,6 +46,13 @@ func roomyEdges(t *testing.T, fx *consoleFixture, edit func(map[string]any), box
// haRoster signs a roster naming both regions, every box of fleet, and
// each box's role from roles.
func haRoster(t *testing.T, fx *consoleFixture, roles map[string]string, fleet ...fleetBox) {
+ t.Helper()
+ haRosterIn(t, fx, roles, []string{"ie", "uk"}, fleet...)
+}
+
+// haRosterIn is haRoster naming only the regions in codes (the roster's
+// Regions), which decides whether it places boxes in a region.
+func haRosterIn(t *testing.T, fx *consoleFixture, roles map[string]string, codes []string, fleet ...fleetBox) {
t.Helper()
boxes := map[string]hostactors.Box{}
for _, b := range fleet {
@@ -52,10 +60,14 @@ func haRoster(t *testing.T, fx *consoleFixture, roles map[string]string, fleet .
rb.Role = roles[b.Box]
boxes[b.Box] = rb
}
- putMoveRoster(t, fx, hostactors.Roster{Boxes: boxes, Regions: map[string]hostactors.Region{
- "ie": {Account: moveTestAccount, AWSRegion: "eu-west-1"},
- "uk": {Account: moveTestAccount, AWSRegion: "eu-west-2"},
- }})
+ regions := map[string]hostactors.Region{}
+ for _, c := range codes {
+ regions[c] = map[string]hostactors.Region{
+ "ie": {Account: moveTestAccount, AWSRegion: "eu-west-1"},
+ "uk": {Account: moveTestAccount, AWSRegion: "eu-west-2"},
+ }[c]
+ }
+ putMoveRoster(t, fx, hostactors.Roster{Boxes: boxes, Regions: regions})
}
// bindRegionHA turns region HA on for fx's claim runtime, with the region
@@ -71,16 +83,22 @@ func bindRegionHA(fx *consoleFixture) {
// each with fresh, roomy headroom, a signed roster naming both regions
// with each box's role from roles, and, with ha, a region HA chooser.
func haMoveFleet(t *testing.T, ha bool, roles map[string]string, more ...fleetBox) *consoleFixture {
+ t.Helper()
+ return haFleetOf(t, ha, roles, []string{"ie", "uk"}, append([]fleetBox{ieBox("ie-box"), ukBox("uk-box")}, more...)...)
+}
+
+// haFleetOf is haMoveFleet for exactly fleet, with a roster naming only
+// the regions in codes.
+func haFleetOf(t *testing.T, ha bool, roles map[string]string, codes []string, fleet ...fleetBox) *consoleFixture {
t.Helper()
fx := newConsoleFixture(t)
- fleet := append([]fleetBox{ieBox("ie-box"), ukBox("uk-box")}, more...)
armMoveFleet(t, fx, fleet...)
names := []string{}
for _, b := range fleet {
names = append(names, b.Box)
}
roomyEdges(t, fx, nil, names...)
- haRoster(t, fx, roles, fleet...)
+ haRosterIn(t, fx, roles, codes, fleet...)
fx.srv.cfg.HostClaim = true
if ha {
d := hostactors.Directory{Store: fx.rel, PublicKey: fx.pub, Bucket: moveTestBucket}
@@ -172,6 +190,33 @@ func haMoveDoor(t *testing.T, code string, ha bool, edit func(map[string]any)) (
return fx, token, moveURL
}
+// haMoveDoorOf is moveDoorWithClaim's world over exactly fleet (the claim
+// is under fleet's first box, ie-box), with roomy edges files, each box's
+// role from roles and region HA on.
+func haMoveDoorOf(t *testing.T, code string, roles map[string]string, fleet ...fleetBox) (*consoleFixture, string, string) {
+ t.Helper()
+ fx, token := operatorInstanceFixture(t, code)
+ m := titoManifest()
+ m.Regions = nil
+ m.Pool = ""
+ enableInstances(t, fx, "titogo", m)
+ u := instancesURL(fx, "bab", "titogo")
+ if c, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"ie"}`); c != http.StatusCreated {
+ t.Fatalf("create = %d %s", c, out)
+ }
+ armMoveFleet(t, fx, fleet...)
+ claimFor(t, fx, "acme.go.tito.io", "ie-box", "ie")
+ names := []string{}
+ for _, b := range fleet {
+ names = append(names, b.Box)
+ }
+ roomyEdges(t, fx, nil, names...)
+ haRoster(t, fx, roles, fleet...)
+ armClaimMoves(t, fx, true, true, nil)
+ bindRegionHA(fx)
+ return fx, token, u + "/acme.go.tito.io/move"
+}
+
// Astra 2a round 2 finding 1: through the whole HTTP door, a destination
// with no box that has room (no edges file, or only blank public
// addresses) is the no-room refusal, alert included, and writes nothing:
@@ -246,8 +291,8 @@ func TestMoveDoorWithNoRoomInTheDestinationIsNoRoom(t *testing.T) {
// nobody, and writes nothing.
func TestMoveDoorWithAnIncompleteReadIsUnavailableNotNoRoom(t *testing.T) {
for _, c := range []struct {
- name string
- break_ func(*testing.T, *consoleFixture)
+ name string
+ breakIt func(*testing.T, *consoleFixture)
}{
{"a malformed edges file", func(t *testing.T, fx *consoleFixture) {
key := edgesFilePrefix + "uk-box.json"
@@ -265,7 +310,7 @@ func TestMoveDoorWithAnIncompleteReadIsUnavailableNotNoRoom(t *testing.T) {
}
}},
} {
- t.Run(c.name, func(t *testing.T) { moveDoorIncompleteRead(t, c.break_) })
+ t.Run(c.name, func(t *testing.T) { moveDoorIncompleteRead(t, c.breakIt) })
}
}
@@ -290,3 +335,136 @@ func moveDoorIncompleteRead(t *testing.T, breakIt func(*testing.T, *consoleFixtu
t.Fatalf("a refused move journaled: %+v %v", rec.Move, err)
}
}
+
+// Census ruling region-ha-2a (fix round 1): the room check refuses only
+// where placement is known to have no room. A roster that does not place
+// the destination (hostops.ErrContract) and a destination with no
+// candidates are main's: the selection equals the one region HA off makes,
+// and nobody is paged.
+func TestMoveRoomCheckRefusesOnlyWhereRoomIsKnownMissing(t *testing.T) {
+ const host, app = "acme.go.tito.io", "bab/titogo"
+ ctx := context.Background()
+ for _, c := range []struct {
+ name string
+ codes []string
+ fleet []fleetBox
+ want moveSelection
+ }{
+ {"the roster does not place the destination", []string{"ie"}, []fleetBox{ieBox("ie-box"), ukBox("uk-box")},
+ moveSelection{Target: "uk-box", Source: "ie-box"}},
+ {"the destination has no candidates", []string{"ie", "uk"}, []fleetBox{ieBox("ie-box"), func() fleetBox { b := ukBox("uk-box"); b.Unverified = true; return b }()},
+ moveSelection{Source: "ie-box"}},
+ } {
+ t.Run(c.name, func(t *testing.T) {
+ pages := pagesTo(t)
+ off, err := haFleetOf(t, false, nil, c.codes, c.fleet...).srv.selectMoveBoxes(ctx, host, "uk", "ie", app)
+ if err != nil || off.Target != c.want.Target || off.Source != c.want.Source {
+ t.Fatalf("region HA off: %+v %v, want target %q source %q", off, err, c.want.Target, c.want.Source)
+ }
+ // Every field, the floor's included, is main's.
+ on, err := haFleetOf(t, true, nil, c.codes, c.fleet...).srv.selectMoveBoxes(ctx, host, "uk", "ie", app)
+ if err != nil || on != off {
+ t.Fatalf("region HA on: %+v %v, want region HA off's %+v", on, err, off)
+ }
+ expectPages(t, pages)
+ })
+ }
+ t.Run("the door answers main's 400 for a destination with no candidates", func(t *testing.T) {
+ pages := pagesTo(t)
+ unverified := ukBox("uk-box")
+ unverified.Unverified = true
+ _, token, moveURL := haMoveDoorOf(t, "code-ha-move-nocands", nil, ieBox("ie-box"), unverified)
+ code, _, out := rawReq(t, http.MethodPost, moveURL, token, `{"region":"uk"}`)
+ if code != http.StatusBadRequest || !strings.Contains(out, "no box in uk publishes an edges file yet") {
+ t.Fatalf("move = %d %s, want main's 400", code, out)
+ }
+ expectPages(t, pages)
+ })
+}
+
+// The door's own selection branch (apiinstancemove.go, after
+// selectMoveBoxes): a destination whose placeable box is not a candidate
+// (uk-box-2 has room but no verified identity) while its only candidate is
+// excluded by role passes roomIn and is refused by moveRoomCheck, with the
+// no-room answer, its Retry-After and its alert, and writes nothing.
+func TestMoveDoorRefusesWhenNoCandidateHasRoom(t *testing.T) {
+ pages := pagesTo(t)
+ stranger := ukBox("uk-box-2")
+ stranger.Unverified = true
+ fx, token, moveURL := haMoveDoorOf(t, "code-ha-move-nocand-room", map[string]string{"uk-box": hostactors.RoleLegacy},
+ ieBox("ie-box"), ukBox("uk-box"), stranger)
+ claim := claimBytes(t, fx, "acme.go.tito.io")
+ code, hdr, out := rawReq(t, http.MethodPost, moveURL, token, `{"region":"uk"}`)
+ if code != http.StatusServiceUnavailable || !strings.Contains(out, noCapacityRefusal("uk")) {
+ t.Fatalf("move = %d %s", code, out)
+ }
+ if got := hdr.Get("Retry-After"); got != noCapacityRetryAfter {
+ t.Fatalf("Retry-After = %q, want %q", got, noCapacityRetryAfter)
+ }
+ expectPages(t, pages, noRoomSubject("uk"))
+ if claimBytes(t, fx, "acme.go.tito.io") != claim {
+ t.Fatal("a refused move wrote the claim")
+ }
+ if rec, err := provision.GetRecord(context.Background(), fx.rel, "bab", "titogo", "acme.go.tito.io"); err != nil || rec.Move != nil {
+ t.Fatalf("a refused move journaled: %+v %v", rec.Move, err)
+ }
+}
+
+// Fix round 1: a placement read moveRoomCheck cannot complete (the second
+// read the door makes, after roomIn's) is the claim doors' temporarily
+// unavailable 503, with no page and no Retry-After of no room. It is not
+// the moves protocol floor's answer, and an incomplete snapshot is still
+// hostops.ErrEvidenceIncomplete under the wrapper.
+func TestMoveRoomCheckReadFailureIsUnavailable(t *testing.T) {
+ for _, c := range []struct {
+ name string
+ fail func(boxchoice.Snapshot, error) (boxchoice.Snapshot, error)
+ }{
+ {"an error", func(boxchoice.Snapshot, error) (boxchoice.Snapshot, error) {
+ return boxchoice.Snapshot{}, errors.New("edges store down")
+ }},
+ {"an incomplete snapshot", func(snap boxchoice.Snapshot, err error) (boxchoice.Snapshot, error) {
+ snap.Complete = false
+ return snap, err
+ }},
+ } {
+ t.Run(c.name, func(t *testing.T) {
+ pages := pagesTo(t)
+ fx, token, moveURL := haMoveDoor(t, "code-ha-move-read", true, nil)
+ edges, calls := fx.srv.cfg.ClaimAuthority.Executors.Edges, 0
+ fx.srv.cfg.ClaimAuthority.Executors.Edges = func(ctx context.Context) (boxchoice.Snapshot, error) {
+ snap, err := edges(ctx)
+ if calls++; calls >= 2 { // roomIn's read passes; moveRoomCheck's does not
+ return c.fail(snap, err)
+ }
+ return snap, err
+ }
+ claim := claimBytes(t, fx, "acme.go.tito.io")
+ code, hdr, out := rawReq(t, http.MethodPost, moveURL, token, `{"region":"uk"}`)
+ if code != http.StatusServiceUnavailable || !strings.Contains(out, claimResponseUnavailable) {
+ t.Fatalf("move = %d %s, want the temporarily unavailable 503", code, out)
+ }
+ if calls != 2 || hdr.Get("Retry-After") == noCapacityRetryAfter {
+ t.Fatalf("chooser reads = %d, Retry-After = %q", calls, hdr.Get("Retry-After"))
+ }
+ expectPages(t, pages)
+ if claimBytes(t, fx, "acme.go.tito.io") != claim {
+ t.Fatal("a refused move wrote the claim")
+ }
+ })
+ }
+ t.Run("an incomplete snapshot stays ErrEvidenceIncomplete", func(t *testing.T) {
+ fx := haMoveFleet(t, true, nil)
+ edges := fx.srv.cfg.ClaimAuthority.Executors.Edges
+ fx.srv.cfg.ClaimAuthority.Executors.Edges = func(ctx context.Context) (boxchoice.Snapshot, error) {
+ snap, err := edges(ctx)
+ snap.Complete = false
+ return snap, err
+ }
+ _, err := fx.srv.selectMoveBoxes(context.Background(), "acme.go.tito.io", "uk", "ie", "bab/titogo")
+ var check *roomCheckError
+ if !errors.As(err, &check) || !errors.Is(err, hostops.ErrEvidenceIncomplete) {
+ t.Fatalf("err = %v, want a room check error wrapping ErrEvidenceIncomplete", err)
+ }
+ })
+}
diff --git a/internal/hostops/executor.go b/internal/hostops/executor.go
index 6541cbed2..228f38098 100644
--- a/internal/hostops/executor.go
+++ b/internal/hostops/executor.go
@@ -97,9 +97,9 @@ func (x ExecutorChooser) Permits(ctx context.Context, obs hostclaim.Observed, r
// PlaceableBoxes is every box in placement that could take a new
// instance in the chooser's namespace and pool now, sorted: Choose's set,
-// before the choice. The console's move door takes a move's destination
-// from it (region HA spec 2 §7, H27). An incomplete read is
-// ErrEvidenceIncomplete, never an empty set.
+// before the choice. The console's move door asks it only whether there
+// is room (region HA spec 2 §7, H27); it never takes a destination from
+// it. An incomplete read is ErrEvidenceIncomplete, never an empty set.
func (x ExecutorChooser) PlaceableBoxes(ctx context.Context, placement string) ([]string, error) {
r := Record{Holder: hostclaim.NewHolder{Region: placement}}
o, cands, err := x.judged(ctx, hostclaim.Observed{Absent: true}, r)
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index dfbb38b41..2337649ad 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -797,7 +797,7 @@ func TestPlan14RootReadCensus(t *testing.T) {
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
- seal = "fdfab9fb943e4e9678e3102b12020782ad181549b4a701d3267e799f986e92ae"
+ seal = "8198674d8809ef90ecebac55324bfd75e69fb3d73bc27b942f29ca2f43e3fe8d"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/plan9_b8_census_test.go b/internal/provision/plan9_b8_census_test.go
index d81d86a14..a146be989 100644
--- a/internal/provision/plan9_b8_census_test.go
+++ b/internal/provision/plan9_b8_census_test.go
@@ -668,7 +668,8 @@ var plan9B8CensusInventory = []censusEntry{
{"internal/console/apiinstancemove.go", "Server.moveTransitionSaying", "record-writer", "key:SetRecordMove", "d1f7162eecdc234607c708a83e9633cdf77cc6931bac6754fb51042dd8409377"},
// Re-reviewed at the merge of carlos-run into seeded-instances (2026-10-13): main's door with the seed rule (refuseSeedMove) after its gate and before the transition; the transition is unchanged and refuses the same seed again under its CAS.
// Re-reviewed for home part 2, Task 5 (ruling owed): a home target is refused on the plain path (MV-N510) and for a home-named box (MV-N511) before any write, and admitted under host claims through startClaimModeMove; its record writes are unchanged.
- {"internal/console/apiinstancemove.go", "Server.handleAPIInstanceMoveRequest", "record-writer", "key:SetRecordMove,lit:instances", "b672781c363eb29d5098ff3aa1cf09065a0673acc6f5ce283fd57b63abfe6db4"},
+ // Re-reviewed for region HA 2a Task 12 (H27): two refusals before any write, both only refusing with region HA on (roomIn before the placement checks; moveRoomCheck's through selectMoveBoxes, answered by writeClaimResponseError). The record writes (SetRecordMove, instances) are unchanged.
+ {"internal/console/apiinstancemove.go", "Server.handleAPIInstanceMoveRequest", "record-writer", "key:SetRecordMove,lit:instances", "2c79a176f32ff82a269f77f47e07c1fe0a347a8bda0454179e79bda01fc7ab51"},
{"internal/console/apiinstancemove.go", "Server.registerMovesPhase1Routes", "key-site", "lit:instances", "5af589d288ba22856f631481c40cc001aac342ea74cee6ee0464ec77d0320bbc"},
{"internal/console/apiinstances.go", "Server.instanceManifest", "key-site", "lit:instances", "e6160ffe84abda9163527ccb33e2ffff2109b35908adb2f3a754534325658fdf"},
{"internal/console/apiinstances.go", "Server.handleAPIInstanceList", "key-site", "lit:instances", "558d0b3e7b0f06faa8e5067ff6f8c131eb899f13d08b71218dfb2454c3e319e1"},
diff --git a/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv b/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv
index 16a63946e..fb4c940d5 100644
--- a/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv
+++ b/internal/provision/testdata/plan14-root-reads-region-ha-2a.tsv
@@ -7,7 +7,7 @@
# Task 7: the box's own addresses (internal/host boxaddress.go, the provider-neutral source over CARLOS_PRIVATE_IP with CARLOS_PRIVATE_CIDR, and CARLOS_PUBLIC_IP, all from root-owned host.env; awsaddr.go, the IMDS adapter, which reads the VPC's CIDR blocks too) and the edges file's private_ip and private_cidr, validated before they are written by edgesfile.PrivateAddress (census ruling region-ha-2a, condition 1). edgesFileWriteClaims (authority, F,J1) is re-digested with its class, task, basis and sites unchanged: with region HA on it reads the address source before every publication and no longer holds a blank EIP. It adds no registry, /data, release or claim read, and nothing runs as root that did not before; the legacy seal stands.
# Task 8: with the reader's own region HA on, forwarding dials a same-region box's private address when it passes edgesfile.PrivateAddress against the reader's own published network, and every other box at its public address (internal/edge homemap.go: dialAddress, regionHAFromEnv, edgesFileView's private_ip, private_cidr, region and placement). The home map is not a root-read unit. Envelope-only, and the legacy seal stands.
# Task 9: the load balancer health answer (internal/edge lbhealth.go, answered in httpFallbackHandler; the home map and the certificate mirror latch their first loads, Edge.Watch beats, the activator's sweeper beats through Service.LastSweep, and ClaimWiring.startRegional marks the box hosting). The only root-read unit that moved is the activator's authority row type Service, which gains one atomic.Int64 field (lastSweep) written after each finished sweep and read by LastSweep: no read, write or decision of the seed rows' authority changes, so its row is re-pinned to the new digest with its class, owner and basis unchanged. The answer reads only in-memory state. Envelope-only, and the legacy seal stands.
-# Task 10: the operator's box and group role doors (internal/console apiboxroles.go, registered with host claims on in registerCLIRoutes) and the CLI (system_roles.go). Census ruling region-ha-2a (platform-1e, approved with conditions) covers one accepted-legacy unit, Server.boxRoster: the role doors' read of the signed roster (Directory.Read), operator-only (it is reached only past apiSystemRoller), read-only, the same roster every reader reads, and no role it reads feeds root's claim authority. Its basis is main's for a console root input (J4). The seal moves.
+# Task 10: the operator's box and group role doors (internal/console apiboxroles.go, registered with host claims on in registerCLIRoutes) and the CLI (system_roles.go). Census ruling region-ha-2a (platform-1e, approved with conditions) covers one accepted-legacy unit, Server.boxRoster: the role doors' read of the signed roster (Directory.Read), operator-only (it is reached only past apiSystemRoller), read-only, the same roster every reader reads, and no role it reads feeds root's claim authority. Its basis is main's for a console root input (J4). The seal moves. Fix round 1: moveRoomCheck answers nil, so main answers, when the roster does not place the region (hostops.ErrContract, as roomIn) and when the destination has no candidates; a placement read it cannot complete is a roomCheckError the door answers as temporarily unavailable (writeClaimResponseError), not as the protocol floor. hostops.ExecutorChooser.PlaceableBoxes' doc comment is corrected (the move door asks only whether there is room): the hostops envelope is re-pinned for the comment, no unit changed.
# Task 10 (system.go): the box and group verbs add `box` and `group` to systemVerb's verb list and two cases to runSystem's dispatch, calling runSystemBoxVerb and runSystemGroupVerb (system_roles.go). The two seed-overlay authority declarations are re-digested, class, task, basis and (empty) kinds unchanged: no root read is added, and the verbs reach the console over the operator's CLI session.
# Task 11: H27 on the create doors (internal/console nocapacity.go; hostops chosen.go): beginClaimCreate chooses the executor before it registers anything (chooseExecutorFirst) and carries the choice to the create (hostops.WithChosenExecutor, honoured by ProductionAuthority.SelectExecutor), claimResponseRefusal answers no room with a 503 on instance create, domain attach's first route (attachClaimFirstRoute, unchanged) and standalone sidecar create, and handleAPIInstanceCreate judges an app-derived region by the chooser's eligible set. handleAPIInstanceCreate chooses before its first write, the environment binding (chooseCreateFirst), and beginClaimCreate keeps that choice; with region HA on, an explicit region with no box that has room is the no-room refusal before the armed-placement gate (roomIn). Census ruling region-ha-2a covers two accepted-legacy units: Server.beginClaimCreate re-frozen (one call added before RegisterStart, its one root-read site, the claim read file:Read@470, unchanged), and Server.chooseCreateFirst, new: the same claim read, made before the binding, feeding only the placement choice; it returns its choice with the claim observation it decided on, and beginClaimCreate keeps the choice only if its own read is that observation (ruling item 5). The seal moves.
# Task 11 fix round 1: an incomplete placement read is not "no room" (controller ruling). hostops.ExecutorChooser's Choose, Permits and PlaceableBoxes ask judged, which wraps the chooser's own Edges read and answers ErrEvidenceIncomplete when that snapshot is incomplete; ExecutorChooser.eligible (accepted-legacy, merge ruling 2) is unchanged. The no-capacity alert is recorded only once sent (noCapacityAlerts.failed gives a failed send's slot back), and the no-room 503 carries Retry-After: 60 (setNoCapacityRetryAfter in writeClaimResponseError, renderDoorRefusal and handleAPIDomainAttach's refusal write). Envelope-only: no root-read unit changed or was added, and the legacy seal stands.
@@ -18,11 +18,11 @@ internal/activator/activator.go type Service authority B,C,D,F,J4 rev3=PROPOSED-
@coverage internal/boxchoice 1bfd561bcb48b51e60e63f99e086fd44d3b1be2160e28b3ed0253a14bbb9ff66
@coverage internal/edgesfile f62c3973bb48fa36f561d26f496c0ba9a14fe882fe5843867e9f7a9eaf0abbdf
@coverage internal/host 4ad9bdcd8b94516a6ccdad3bc900b166833faf1f0334ac83478be5b04de47069
-@coverage internal/hostops 2a6dc31a50858f27e9b96ce89f98d8ec184a0d524ba77ecb64931cf054186788
+@coverage internal/hostops 116489befd32c2afed09a4de9c60dd56cc3f78089b7752670faeb388a38e3645
@coverage internal/edge 20177c9e7dc961937677bbf2763c73004d7c95f68512591800f6fe841c36152f
internal/host/edgesfile.go edgesFileWriteClaims authority F,J1 root-route;kept=F;scope=protected-decision-only registry:Account@10835,registry:Alias@5297,registry:Alias@5332,registry:Alias@5754,registry:Alias@5911,registry:Alias@5981,registry:Alias@6013,registry:Alias@6482,registry:Alias@8772,registry:App@10847,registry:DNSStatus@5429,registry:DNSStatus@5920,registry:Host@10480,registry:Host@2878,registry:Host@5389,registry:Host@6234,registry:Host@6567,registry:Host@8819,registry:Host@8866,registry:Host@8916,registry:Host@8951,registry:Host@9000,registry:Platform@2921,registry:Provisioned@2935,registry:Replica@2959,registry:Status@877,whole:Route@3212,whole:Route@323,whole:Route@3505,whole:Route@351,whole:RouteAlias@5282,whole:RouteAlias@8725,whole:RoutePath@3520,whole:WildcardDomain@8905 8c7cdffba66886b92f9a2ac663a86dbdd5f80699afd2e6afb4571da916c727ad
@coverage internal/activator fd9cf14712f55895541bb8bfbf8137cfd446298bcaa8634b0aba7b8344c29ec3
-@coverage internal/console 7187e2cb5803a942616df8d349a87be050a65be35cfdaf66493b0f4112a02387
+@coverage internal/console e80945195061ccda68851a1fb043b2624d243b62a2f85f4175485e58a3f7d747
@coverage . 0e6e4542758248000de1882a01f13e3449c880b4ae23e8b2ff0d6dce076b614c
@legacy b02b534b6617f57381ba12e9d9a2bf1bd63f737dc68005de926ff18b7db93dd3 internal/console/apiboxroles.go Server.boxRoster legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@484 9be9925de14dbbd7d86123bf9faf1535c7b116575cadcb8ba983a665af0513f4
internal/console/apiboxroles.go Server.boxRoster legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@484 9be9925de14dbbd7d86123bf9faf1535c7b116575cadcb8ba983a665af0513f4
@@ -32,5 +32,5 @@ system.go systemVerb authority B,C,D,F,J4 rev3=PROPOSED-seed; command arguments,
internal/console/claim_create.go Server.beginClaimCreate legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@470 ff9b246aa3dcfe180ea1901ecd9fc1b3b18561dc309e08c6a922870d89179705
@legacy 5aa99a1b92eb73601585a25265c5285bcfd0c6b6ae7f402248eb1a299bc79831 internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@271 7cc51e7812cc59f39ea23d5285c621d912557f4890d6e76d566f6182b0fe3ffd
internal/console/nocapacity.go Server.chooseCreateFirst legacy accepted-legacy accepted-legacy;rev3=J4;basis=root-input;census-ruling-region-ha-2a file:Read@271 7cc51e7812cc59f39ea23d5285c621d912557f4890d6e76d566f6182b0fe3ffd
-@legacy d7a8539af8e4f3123718211dcc8ba59c6620de84ef93c18a8ddb22686d2a0c4c internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy accepted-legacy accepted-legacy;rev3=census-ruling-region-ha-2a;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with two refusals added before any write, both no room in the destination (H27): roomIn before the placement checks and moveRoomCheck's through selectMoveBoxes; read sites unchanged, offsets moved file:Exec@10024,file:Exec@10201,file:Exec@10352,file:Exec@11352,file:Exec@8945,file:Exec@9033,file:Read@7431,file:Read@9743 b672781c363eb29d5098ff3aa1cf09065a0673acc6f5ce283fd57b63abfe6db4
-internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy accepted-legacy accepted-legacy;rev3=census-ruling-region-ha-2a;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with two refusals added before any write, both no room in the destination (H27): roomIn before the placement checks and moveRoomCheck's through selectMoveBoxes; read sites unchanged, offsets moved file:Exec@10024,file:Exec@10201,file:Exec@10352,file:Exec@11352,file:Exec@8945,file:Exec@9033,file:Read@7431,file:Read@9743 b672781c363eb29d5098ff3aa1cf09065a0673acc6f5ce283fd57b63abfe6db4
+@legacy 156242e34deafce7a73ae6951c7b39201964827c4326d7052339d0b0bb018808 internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy accepted-legacy accepted-legacy;rev3=census-ruling-region-ha-2a;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with two refusals added before any write, both no room in the destination (H27): roomIn before the placement checks and moveRoomCheck's through selectMoveBoxes; read sites unchanged, offsets moved file:Exec@10077,file:Exec@10254,file:Exec@10405,file:Exec@11405,file:Exec@8998,file:Exec@9086,file:Read@7484,file:Read@9796 2c79a176f32ff82a269f77f47e07c1fe0a347a8bda0454179e79bda01fc7ab51
+internal/console/apiinstancemove.go Server.handleAPIInstanceMoveRequest legacy accepted-legacy accepted-legacy;rev3=census-ruling-region-ha-2a;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with two refusals added before any write, both no room in the destination (H27): roomIn before the placement checks and moveRoomCheck's through selectMoveBoxes; read sites unchanged, offsets moved file:Exec@10077,file:Exec@10254,file:Exec@10405,file:Exec@11405,file:Exec@8998,file:Exec@9086,file:Read@7484,file:Read@9796 2c79a176f32ff82a269f77f47e07c1fe0a347a8bda0454179e79bda01fc7ab51