console: every live box in a region must qualify before it is automatically eligible (final review F2)
A record names a region, not a box. Reconcile admits every box in the region whose pools serve the shared pool and which takes inbound (inScope and the CARLOS_INBOUND=none refusal); it never asks whether the box holds the deployment's secrets key or has its activator wired. So with one keyed box and one keyless sibling, the keyless one can win the lease and wake the instance with no config, secrets or store binding. regionCandidates now marks a coded region Eligible only when at least one fresh box passes the whole predicate (dial address, hibernation_configured, serves_shared_pool, accepts_inbound, matching key) AND no fresh box publishing the code fails it; absent and unknown fields fail it. Stale boxes count for neither side. Reachable, Hosting, KeyOK and NoKeyProven keep describing the boxes that are there, so a mixed region reads not-eligible and the C16 to C19 lists are unchanged in vocabulary. The explicit door does not consume Eligible and is unchanged. The mixed-box test now expects ineligibility, with the reason why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+104
−18
internal/console/regioncandidates.go+39 −11internal/console/regioncandidates_test.go+65 −7
diff --git a/internal/console/regioncandidates.go b/internal/console/regioncandidates.go| index 0dc5a1b3c..24d8f017f 100644 |
| --- a/internal/console/regioncandidates.go |
| +++ b/internal/console/regioncandidates.go |
| @@ -25,7 +25,7 @@ type regionCandidate struct { |
| Reachable bool // some FRESH box publishing the code has a dial address (F1: fresh = a current status snapshot) |
| Hosting bool // ... and its activator configured (unknown counts, as in placements) |
| KeyOK bool // ... and holds the deployment's secrets key (R9) |
| - Eligible bool // ONE box has all of it, every fact POSITIVELY true |
| + Eligible bool // some fresh box has all of it, every fact POSITIVELY true, and no fresh box lacks any of it (F2) |
| NoKeyProven bool // R9: every reachable hosting box, fresh or stale, is a current writer, and none holds the key |
| } |
| @@ -50,20 +50,40 @@ func isTrue(b *bool) bool { return b != nil && *b } |
| // regionCandidates folds an edges snapshot into candidates. expectedFP is |
| // the console's own secrets fingerprint; "" means it cannot judge, and then |
| // no coded region is KeyOK. The home is exempt from every eligibility fact. |
| -// A coded candidate's AWS region comes only from its ELIGIBLE boxes (codex |
| -// plan-R2 2); NoKeyProven needs a complete snapshot (codex plan-R2 1). |
| +// A coded candidate's AWS region comes only from its fully eligible boxes |
| +// (codex plan-R2 2); NoKeyProven needs a complete snapshot (codex plan-R2 |
| +// 1). |
| +// |
| +// A coded region is Eligible only when BOTH hold (final review F2): |
| +// |
| +// - at least one fresh box publishing it passes the whole predicate: |
| +// a dial address, hibernation_configured, serves_shared_pool and |
| +// accepts_inbound all positively true, and the deployment's key; |
| +// - no fresh box publishing it fails that predicate. Absent and unknown |
| +// fields fail it. |
| +// |
| +// A record names a region, not a box. internal/provision's reconcile pass |
| +// (inScope, and the CARLOS_INBOUND=none refusal) admits every box whose |
| +// region matches, whose pools serve the shared pool and which takes |
| +// inbound; it never asks whether the box holds the key or has its |
| +// activator wired, so a keyless or activator-less sibling can win the lease |
| +// and wake the instance with nothing. Every box that could serve the record |
| +// must therefore be able to. Stale boxes count for neither side (F1): they |
| +// are not evidence that a region works, and not evidence that it is broken. |
| func regionCandidates(snap edgesSnapshot, homeName, expectedFP string) []regionCandidate { |
| type acc struct { |
| c regionCandidate |
| regions map[string]int |
| hostingBoxes int |
| unknownWriter, keyFound bool |
| + anyEligible bool |
| + allEligible bool // over fresh boxes; vacuously true |
| } |
| by := map[string]*acc{} |
| get := func(code string, home bool) *acc { |
| a, ok := by[code] |
| if !ok { |
| - a = &acc{c: regionCandidate{Code: code, Home: home}, regions: map[string]int{}} |
| + a = &acc{c: regionCandidate{Code: code, Home: home}, regions: map[string]int{}, allEligible: true} |
| by[code] = a |
| } |
| return a |
| @@ -80,18 +100,16 @@ func regionCandidates(snap edgesSnapshot, homeName, expectedFP string) []regionC |
| code = "" |
| } |
| a := get(code, home) |
| - if f.EIP == "" { |
| - continue |
| - } |
| if home { |
| - if f.Region != "" { |
| + if f.EIP != "" && f.Region != "" { |
| a.regions[f.Region]++ |
| } |
| continue |
| } |
| + reachable := f.EIP != "" |
| hosting := isTrue(f.HibernationConfigured) || f.HibernationConfigured == nil |
| keyOK := expectedFP != "" && f.SecretsKeyFP == expectedFP |
| - if hosting { |
| + if reachable && hosting { |
| // R9's negative proof counts every box with a dial address, |
| // fresh or not: a lingering keyless file still describes that |
| // box's configuration, and the explicit door this proof serves |
| @@ -107,19 +125,29 @@ func regionCandidates(snap edgesSnapshot, homeName, expectedFP string) []regionC |
| // edges file lingers after retirement; see readLiveEdgesSnapshot. |
| continue |
| } |
| - eligible := isTrue(f.HibernationConfigured) && isTrue(f.ServesSharedPool) && isTrue(f.AcceptsInbound) && keyOK |
| + // A fresh box with no dial address still fails the predicate (F2): |
| + // reconcile admits it all the same, so it can take the record and |
| + // nothing can reach it. It just does not make the region Reachable. |
| + eligible := reachable && isTrue(f.HibernationConfigured) && isTrue(f.ServesSharedPool) && isTrue(f.AcceptsInbound) && keyOK |
| + a.anyEligible = a.anyEligible || eligible |
| + a.allEligible = a.allEligible && eligible |
| + if !reachable { |
| + continue |
| + } |
| a.c.Reachable = true |
| a.c.Hosting = a.c.Hosting || hosting |
| if hosting { |
| a.c.KeyOK = a.c.KeyOK || keyOK |
| } |
| - a.c.Eligible = a.c.Eligible || eligible |
| if eligible && f.Region != "" { |
| a.regions[f.Region]++ |
| } |
| } |
| out := make([]regionCandidate, 0, len(by)) |
| for _, a := range by { |
| + if !a.c.Home { |
| + a.c.Eligible = a.anyEligible && a.allEligible |
| + } |
| a.c.AWSRegion = majority(a.c.Code, a.regions) |
| a.c.NoKeyProven = !a.c.Home && snap.Complete && expectedFP != "" && a.hostingBoxes > 0 && !a.unknownWriter && !a.keyFound |
| if a.c.Eligible && !a.c.Home { // a home without a point yields "default" (spec §6.3), not "left out" |
diff --git a/internal/console/regioncandidates_test.go b/internal/console/regioncandidates_test.go| index ef9592f09..84cce3f3a 100644 |
| --- a/internal/console/regioncandidates_test.go |
| +++ b/internal/console/regioncandidates_test.go |
| @@ -284,11 +284,67 @@ func TestRegionCandidatesSecretsKey(t *testing.T) { |
| } |
| } |
| // One box with the key and one without: the region is not PROVEN |
| - // keyless, and is eligible through the box that holds it. |
| + // keyless (the explicit door may still use it), but it is NOT eligible |
| + // for automatic placement (final review F2). A record names a region, |
| + // not a box, and reconcile admits every box in the region that serves |
| + // the shared pool and takes inbound: the keyless box can win the lease |
| + // and wake the instance with no config, secrets or store binding. |
| ukKeyless2 := edgeJSON("uk-box-2", "1.2.3.5", "eu-west-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)}) |
| cand, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, ukKeyless2), "ie", testKeyFP), "uk") |
| - if !cand.Eligible || cand.NoKeyProven { |
| - t.Errorf("mixed region = %+v", cand) |
| + if cand.Eligible || cand.NoKeyProven || cand.unavailableReason() != "not-eligible" { |
| + t.Errorf("mixed region = %+v, want ineligible (not-eligible) and not proven keyless", cand) |
| + } |
| +} |
| + |
| +// F2: a region is automatically eligible only when at least one fresh box |
| +// is fully eligible AND no fresh box publishing the code fails the |
| +// predicate. One box reconcile would admit that cannot serve the record |
| +// takes the region out; stale boxes count for neither side. |
| +func TestEligibilityNeedsEveryFreshBoxToQualify(t *testing.T) { |
| + sibling := func(o edgeOpts) string { return edgeJSON("uk-box-2", "1.2.3.5", "eu-west-2", "uk", o) } |
| + keyed := func(hib, shared, inbound *bool) edgeOpts { |
| + return edgeOpts{hib: hib, shared: shared, inbound: inbound, fp: testKeyFP} |
| + } |
| + bad := map[string]string{ |
| + "keyless": sibling(edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)}), |
| + "wrong key": sibling(edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true), fp: "sha256:" + strings.Repeat("0", 64)}), |
| + "hibernation false": sibling(keyed(bp(false), bp(true), bp(true))), |
| + "hibernation absent": sibling(keyed(nil, bp(true), bp(true))), |
| + "shared pool false": sibling(keyed(bp(true), bp(false), bp(true))), |
| + "shared pool absent": sibling(keyed(bp(true), nil, bp(true))), |
| + "inbound false": sibling(keyed(bp(true), bp(true), bp(false))), |
| + "inbound absent": sibling(keyed(bp(true), bp(true), nil)), |
| + "old writer, every field": sibling(edgeOpts{}), |
| + "no dial address": edgeJSON("uk-box-2", "", "eu-west-2", "uk", eligibleBox), |
| + } |
| + for name, body := range bad { |
| + cands := regionCandidates(views(t, homeBox, ukEligible, body), "ie", testKeyFP) |
| + c, _ := findCandidate(cands, "uk") |
| + if c.Eligible || c.unavailableReason() == "" { |
| + t.Errorf("%s sibling: uk = %+v, want ineligible", name, c) |
| + } |
| + if n := eligibleCount(cands); n != 1 { |
| + t.Errorf("%s sibling: %d eligible candidates, want the home alone", name, n) |
| + } |
| + // The diagnostics still describe the boxes that ARE there: the |
| + // eligible box is reachable, hosting and keyed, so the C16 word is |
| + // not-eligible, never absent. |
| + if c.unavailableReason() == "absent" { |
| + t.Errorf("%s sibling: uk read as absent with a reachable box present", name) |
| + } |
| + } |
| + // Two fully eligible boxes: eligible. |
| + if c, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, sibling(eligibleBox)), "ie", testKeyFP), "uk"); !c.Eligible { |
| + t.Fatalf("two eligible boxes: uk = %+v, want eligible", c) |
| + } |
| + // A STALE failing sibling counts for neither side: the live box alone |
| + // decides, and the region stays eligible. |
| + if c, _ := findCandidate(regionCandidates(markStale(views(t, homeBox, ukEligible, bad["keyless"]), "uk-box-2"), "ie", testKeyFP), "uk"); !c.Eligible { |
| + t.Fatalf("stale keyless sibling: uk = %+v, want eligible", c) |
| + } |
| + // A stale ELIGIBLE box beside a fresh failing one lends nothing. |
| + if c, _ := findCandidate(regionCandidates(markStale(views(t, homeBox, ukEligible, bad["keyless"]), "uk-box"), "ie", testKeyFP), "uk"); c.Eligible || c.unavailableReason() != "no-secrets-key" { |
| + t.Fatalf("stale eligible, fresh keyless: uk = %+v, want no-secrets-key", c) |
| } |
| } |
| @@ -343,14 +399,16 @@ func (f failingGet) Get(ctx context.Context, key string) ([]byte, string, error) |
| return f.Store.Get(ctx, key) |
| } |
| -// codex plan-R2 2: a candidate's coordinates come from its ELIGIBLE boxes |
| -// only; ties among them go to the lexically smallest AWS region. |
| +// codex plan-R2 2: a candidate's coordinates come from its fully eligible |
| +// boxes only; ties among them go to the lexically smallest AWS region. The |
| +// mixed region is no longer eligible (F2), but it is still NAMED (placeOf, |
| +// C16's refusal) from the box that qualifies, not from the ones that do not. |
| func TestCandidateLocationComesFromEligibleBoxes(t *testing.T) { |
| syd1 := edgeJSON("uk-syd-1", "2.2.2.1", "ap-southeast-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(true)}) |
| syd2 := edgeJSON("uk-syd-2", "2.2.2.2", "ap-southeast-2", "uk", edgeOpts{hib: bp(true), shared: bp(true), inbound: bp(false), fp: testKeyFP}) |
| c, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, syd1, syd2), "ie", testKeyFP), "uk") |
| - if !c.Eligible || c.AWSRegion != "eu-west-2" { |
| - t.Fatalf("mixed eligibility located uk at %q, want the eligible box's eu-west-2", c.AWSRegion) |
| + if c.Eligible || c.AWSRegion != "eu-west-2" { |
| + t.Fatalf("mixed eligibility = %+v, want ineligible (F2) and located at the qualifying box's eu-west-2", c) |
| } |
| dub := edgeJSON("uk-dub", "2.2.2.3", "eu-west-1", "uk", eligibleBox) |
| if c, _ := findCandidate(regionCandidates(views(t, homeBox, ukEligible, dub), "ie", testKeyFP), "uk"); c.AWSRegion != "eu-west-1" { |