spec: move-any-app rev 5, on claim rev 3, every model gap exercised (#175)
Written against claim rev 3 (8b5bccb2), which adopted CC1 to CC13. The model now exercises rev 4's eight gaps: the seal by version and the staging horizon are proven, and rest on a rule the model found, a staging writer signs only before SafeUntil (model 14, CC14); CC11 is proven (model 15); exec-admission and hold-op-open are caught only with the start check, a triple. T12 and quiet_after's install wait cannot bite under claim rev 3 and stay as defence in depth. 30,000 and 100,000 seeds clean; the full mutation sweep is §14.2.5. §15 step 8, the real-AWS check, is done, with each result cited.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+364
−123
docs/superpowers/specs/2026-10-02-move-any-app-design.md+364 −123
diff --git a/docs/superpowers/specs/2026-10-02-move-any-app-design.md b/docs/superpowers/specs/2026-10-02-move-any-app-design.md| index c91d18fb5..8f4f8c29a 100644 |
| --- a/docs/superpowers/specs/2026-10-02-move-any-app-design.md |
| +++ b/docs/superpowers/specs/2026-10-02-move-any-app-design.md |
| @@ -1,9 +1,34 @@ |
| # Move any app or instance to another region: Phase 1, a safe single-instance move |
| **Date:** 2026-10-02 |
| -**Revision:** 4. |
| +**Revision:** 5. |
| + |
| +- **Rev 5** is written against **claim spec rev 3** (8b5bccb2 on |
| + `design-replica-ownership`), which adopted CC1 to CC13. It answers the |
| + coordinator's round on rev 4: |
| + - the model (§14.2) is extended until it exercises every rule rev 4 |
| + listed as a gap: stalled staging signers, paused registry writes that |
| + resume at the worst moment, an obligation runner beside the role |
| + worker, and admission from cached reads. It is re-encoded against |
| + claim rev 3, and reruns clean at 30,000 and 100,000 seeds, with the |
| + full mutation sweep (§14.2.5); |
| + - N2's second half is now proven: dropping the seal by version loses |
| + acknowledged writes, and dropping the staging horizon leaves residue. |
| + The model also found the rule both rest on: a staging writer signs |
| + only before its lease's `SafeUntil` (model 14, §7.3); |
| + - CC11 is now proven: an operation started over a pending park loses |
| + acknowledged writes (model 15); |
| + - T12 and the install wait on `quiet_after` cannot bite under claim |
| + rev 3: they are kept as defence in depth (§14.2.5); |
| + - §15 step 8 is done: every AWS behaviour the model assumes was checked |
| + against the real service; |
| + - one more claim change, CC14, goes to platform-1e (§19.4). |
| + |
| + Rev 5's changes are marked "(model N)" like rev 4's. |
| -- **Rev 4** is the first revision checked against an **executable model** |
| +Earlier revisions: |
| + |
| +- **Rev 4** (a80ff90b) was the first revision checked against an **executable model** |
| instead of a prose round. The joint Astra review of the claim spec (rev 1) |
| and this spec (rev 3), `CODEX-FINDINGS-JOINT-R1.md` (4316db73), found 12 |
| new P1s and 3 P2s, and recommended a joint simulation before planning. |
| @@ -18,9 +43,7 @@ |
| moves side of the others. Changes are marked "(joint N)"; |
| - fixes what the model found beyond the review, marked "(model N)" |
| (§14.2.4); |
| - - lists the claim changes the model requires (§19.3), for platform-1e. |
| - |
| -Earlier revisions: |
| + - lists the claim changes the model requires (§19.4), for platform-1e. |
| - **Rev 1** (e41c4fef) went to Astra (GPT-6, via codex): 12 P1, 7 P2, 1 P3, |
| "not ready". The findings are in `CODEX-FINDINGS-MOVES-R1.md` (9fbf4afe). |
| @@ -59,8 +82,8 @@ branch `design-nearest-region`. |
| from its §2 onward (tombstones become the claim's `deleting` and |
| `parking` operations). |
| - The claim spec, `docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md`, |
| - **rev 2 at cf3a7861** on `design-replica-ownership`. Rev 4 is written |
| - against rev 2. |
| + **rev 3 at 8b5bccb2** on `design-replica-ownership`. Rev 5 is written |
| + against rev 3; rev 4 was written against rev 2 (cf3a7861). |
| Citations are to this worktree at 7edd4b33 unless they name another branch |
| or repo. |
| @@ -109,7 +132,7 @@ traffic is undone automatically (§7.8); after the target opens, R4 stands. |
| ### 0.4 The host lifecycle claim |
| -From claim spec rev 2 (§2 to §10 there), the parts this spec relies on: |
| +From claim spec rev 3 (§2 to §10 there), the parts this spec relies on: |
| - **The object** `control/hosts/<host>.json`, rewritten whole by CAS: a |
| per-write revision `gen` (not a data generation, J-D2); a `holder`; |
| @@ -123,23 +146,32 @@ From claim spec rev 2 (§2 to §10 there), the parts this spec relies on: |
| **exact object versions**, with a shared staging-work lease for |
| cross-region staging; C4 fresh admission; C5 revocation needs stop |
| evidence or a lost-box fence; C6 a read error never acts; C7 |
| - already-admitted work is drained (`hostguard`). |
| + already-admitted work is drained (`hostguard`). Rev 3 limits C3's |
| + "late completion is harmless" to the operation's own version-bound |
| + calls, and covers the replication exception with a late-completion |
| + horizon (§6.2a there). |
| - **Op kinds** (§4 there): `moving`, `landing` and `reverting`/`restoring` |
| belong to this spec. One operation runs at a time. |
| -- **Mechanisms:** stop evidence from system state (§6.1), parking by exact |
| - version into per-retirement destinations (§6.2), local retirement for both |
| - layouts (§6.3), the host guard (§6.4), door reads pinned to versions |
| - (§6.5), execution leases and declare-lost (§7), the floor on every |
| - executable restore path (§10). |
| +- **Mechanisms:** stop evidence from system state, with every probe |
| + rendering held config (§6.1); parking by exact version into |
| + per-retirement destinations keyed `<relative key>@<source versionId>` |
| + (§6.2); the late-completion horizon `H` and the per-region `quiet_after` |
| + that survives `free` (§6.2a); local retirement for both layouts (§6.3); |
| + the host guard (§6.4); door reads pinned to versions (§6.5); execution |
| + leases for liveness, and declare-lost by compute fencing only (§7); one |
| + `claim.Adopt` routine with discovery parks (§9); the purge journal |
| + `reserved → adopted → claimed → torn-down → retired → done` (§5.3); the |
| + floor on every executable restore path (§10). |
| This spec's rules for using the claim are K1 to K13 in §19, named "K" so |
| they never read as the claim spec's C1 to C7. **Execution holder** below |
| means the claim's `op.exec` (rev 2 made it a first-class field, as rev 3 |
| asked). |
| -The model (§14.2) found that several claim rev 2 rules need changing. |
| -§19.3 lists them; until platform-1e adopts them, this spec states each as |
| -the behaviour it relies on. |
| +Rev 4's model (§14.2) required thirteen changes to claim rev 2, CC1 to |
| +CC13; claim rev 3 adopted all of them (§19.4 maps each to its section |
| +there). Rev 5's model requires one more, CC14 (§19.4): until platform-1e |
| +adopts it, this spec states it as the behaviour it relies on. |
| ## 1. What happened in production |
| @@ -272,6 +304,7 @@ model, and where each lives: |
| | an app store exists while its host is held for anything but delete | after every step | claim §5.3 | |
| | a door read returns only the incarnation it authorized | at every open | claim §6.5 | |
| | a commit happens within the cut budget | at the commit | INV-9 | |
| +| an admitted cut never waits out `quiet_after` (rev 5) | at the landing's park | INV-9, T12 | |
| | no IAM violation | at every request | M17 | |
| | no stuck recovery | after every step | INV-3 | |
| @@ -333,8 +366,8 @@ Round 2 showed two gaps: a flat child's descendant that outlives it |
| and evidence that cannot be produced after an agent restart, because |
| `FenceAndStop` returns `ErrStopUnproven` when it has neither an entry nor a |
| remembered stop history (`maintenance.go:90-100`). Evidence comes from |
| -**durable, observable state**, never from memory. Claim spec rev 2 §6.1 |
| -adopts the joint review's D3 text, and this section follows it (joint D3): |
| +**durable, observable state**, never from memory. Claim spec §6.1 (rev 2 |
| +and rev 3) adopts the joint review's D3 text, and this section follows it (joint D3): |
| > All lifecycle-controlled spawns require strict containment. Stop evidence |
| > requires a closed admission fence, drained spawn attempts, an empty |
| @@ -483,21 +516,23 @@ Phase 1 uses the claim spec's park (§6.2 there) and local retirement |
| location, noncurrent included; copies name `versionId`; deletes name |
| `VersionId`. The model replaces version deletes with bare-key deletes |
| (delete markers on a versioned bucket) and finds parks that never |
| - finish, in 2,311 of 3,000 seeds. |
| + finish, in 2,325 of 3,000 seeds (rev 5). |
| - **The live lock is not data (joint N6).** The parker takes the location's |
| replica lease first, and renews its own `lock.json` while it works. That |
| key is excluded from the snapshot, the manifest and the emptiness check, |
| and released by the parker's own version after the completion CAS. The |
| model includes the lock and the park never finishes. |
| - **Progress accumulates (model 2).** A copy's destination key carries the |
| - source version id, so a retry skips copies that landed and the manifest is |
| - written once every copy exists. Restarting the snapshot from scratch on |
| + source version id (`<relative key>@<source versionId>`, claim §6.2), so a |
| + retry skips copies that landed and the manifest is written once every |
| + copy exists. Restarting the snapshot from scratch on |
| every retry, with many versions and late completions, does not converge |
| in the model. |
| - **Verify after the horizon (model 1).** A park acknowledges only after a |
| - final listing taken after `max(evidence time, the region's quiet time) + |
| - H` is empty (§5.3). Anything found then is parked as a further retirement |
| - event. |
| + verification listing taken later than `evidence time + H` (§5.3, claim |
| + §6.2a) and, for a staging retirement, later than the staging horizon |
| + (§7.3). Anything found then is parked as a further retirement event of |
| + the same obligation, and the verification repeats. |
| - **Local retirement** takes the host guard and writes a retired marker |
| first (claim C7), so a stale background write for that incarnation is |
| refused. |
| @@ -594,15 +629,36 @@ further rules: |
| A stale round that lands later creates a newer version, which the seal |
| does not name, or a delete marker, which a copy by `versionId` ignores. |
| So nothing has to be drained for correctness. |
| +- **Sign only before `SafeUntil` (model 14).** A staging writer checks its |
| + lease's `SafeUntil` immediately before it signs each request, after any |
| + pause, and drops the request if the time has passed. Revalidating at the |
| + start of the round is not enough: a goroutine that revalidated and then |
| + paused can sign long after its lease ended, and the horizon below no |
| + longer bounds its write. |
| - **Retire staging after its writers' horizon.** Staging is retired (by |
| the target-region box, §7.11) only after `seal time + staging-lease TTL + |
| H`, when no writer that held the lease can still have a request in |
| - flight. |
| - |
| -The model exercises the stale second worker and catches the revalidation |
| -rule (§14.2.5). It does not, even at 30,000 seeds, produce a staging write |
| -that lands after the seal, so the seal and the staging horizon are **not |
| -yet proven by the model**; §14.2.5 records the gap. |
| + flight. That bound holds only because of the rule above: a request is |
| + signed before `SafeUntil` and lands within `H` of signing. |
| + |
| +All three are proven by the model (§14.2.5). A staging round's goroutine |
| +can pause between revalidation and signing (one round in three), past its |
| +lease; a cut round's lost-response retry can leave a paused duplicate |
| +(one in three). Dropping the seal by version loses acknowledged writes: |
| +the install copies a stale round's older snapshot. Dropping the staging |
| +horizon or the `SafeUntil` check leaves staging in the serving region or |
| +in a region the instance left. |
| + |
| +**Where correctness rests on the pause assumption (model 14).** Claim rev |
| +3 keeps its clock and pause assumption "only for liveness", because every |
| +destructive request is version-bound. A staging PUT is not destructive, |
| +but its residency is: the staging horizon is a bound on signing time, and |
| +the only thing that enforces it is the `SafeUntil` check, whose margin |
| +must cover the clock skew and the longest pause between check and |
| +signature. The worst case is garbage under `moves/<host>/<move id>/` |
| +(never live data, because the seal names versions), but in a region the |
| +instance left that is a residency failure. CC14 asks the claim spec to |
| +say so (§19.4). |
| **Snapshot rounds (source).** Each round the source builds the files |
| archive (§8) when the plaintext digest changes, and publishes its **launch |
| @@ -629,7 +685,7 @@ placement, the target writes `moves/<acct>/<app>/<host>/<move id>/target.json`: |
| | T8 one owner | exactly one hosting box publishes the target placement | |
| | T9 claim | the claim is `moving` with this move id, its holder is the record's incarnation, and this region and box are in `touched` (added by CAS before the first staging write, claim spec §2) | |
| | T10 return lane | this box can put and delete `moves/_lane/<move id>` in the source's bucket (M17) | |
| -| T12 quiet region | the claim's `quiet_after` for the target region (CC1) has passed, so the landing's park of an earlier stay never waits inside the cut window. A move back into a region the instance left within H waits at `ready`, not mid-cut | |
| +| T12 quiet region | the claim's `quiet_after` for the target region (CC1) has passed, so the landing's park of an earlier stay never waits inside the cut window. A move back into a region the instance left within H waits at `ready`, not mid-cut. **Defence in depth (model, rev 5):** under claim rev 3 no state reaches it, because every retirement acknowledges only after its own horizon and CC11 refuses a move over a pending chore; the model drops T12 and no cut ever waits (§14.2.5). It stays, because it costs one comparison and guards any future path that acknowledges early | |
| | T11 budget | the **whole** stop-to-open estimate fits the operator's cut window (default 120 s) (codex R2-10, M11), from measured rates: stop + final checkpoint + final mirror (at most the 16 MiB bar at the measured cross-region copy rate) + archive build and upload + install copy (replica size at the measured in-bucket copy rate) + restore (replica size at the measured restore rate) + render + **every permitted probe attempt** (three times 30 s, plus the 20 s settle) (joint N15). The estimate is recorded per phase as `cut_budget_s`, and admission sets an absolute `cut_deadline` (§7.4) | |
| **Source readiness.** S1: a probe request (§7.7) to the readiness path on |
| @@ -1100,7 +1156,7 @@ reach it through its config bundle, opened with the box's |
| update journal before activating (joint N12; claim §10). Any downgrade of |
| a box that is a participant in a held operation is refused. The console |
| roll recipe states the same floor. The model lets a reboot restore an old |
| - executable and fails in 2,651 of 3,000 seeds, mostly as parks an old |
| + executable and fails in 2,696 of 3,000 seeds (rev 5), mostly as parks an old |
| executable can never let finish. |
| - **The floor is published before any protected production rehearsal** |
| (joint N12). Rev 3's rollout order had it the other way round (§15). |
| @@ -1160,22 +1216,26 @@ Built for rev 4, as the coordinator directed and the joint review |
| recommended: `internal/sim/hostlife/`, a deterministic randomized Go |
| simulation. **It is test-only**: every file is a `_test.go` file, so |
| `go build ./...` never sees it, and it imports only the standard library. |
| -It encodes claim spec rev 2 (cf3a7861) and this spec as they would be |
| -implemented, and every rule is a named, mutable guard (`rules_test.go`). |
| +Rev 5 re-encodes it against claim spec rev 3 (8b5bccb2) and this spec as |
| +they would be implemented, and every rule is a named, mutable guard |
| +(`rules_test.go`). |
| #### 14.2.1 Running it |
| ``` |
| go test ./internal/sim/hostlife/ # the gate: 300 seeds, about a second |
| HOSTLIFE_SEEDS=30000 go test ./internal/sim/hostlife/ -run TestHostLifecycle -v -timeout 60m |
| +HOSTLIFE_SEEDS=100000 go test ./internal/sim/hostlife/ -run TestHostLifecycle -v -timeout 300m |
| HOSTLIFE_SEED=1796 go test ./internal/sim/hostlife/ -run TestHostLifecycle -v # one seed, full trace |
| ``` |
| Mutation checks run in a scratch copy of the tree, never in place: |
| `internal/sim/hostlife/mutate.sh <seeds> [rule | a,b ...]` copies `go.mod`, |
| `go.sum` and the package to a scratch directory per rule, sets |
| -`var mutated = "<rule>"` (or `"a,b"` for a pair), runs the seeds, reports |
| -CAUGHT or MISSED, and deletes the copy. Rules run in parallel. |
| +`var mutated = "<rule>"` (or `"a,b"` for a pair, `"a,b,c"` for a triple), |
| +runs the seeds, reports CAUGHT or MISSED, and deletes the copy. Rules run |
| +in parallel. Scratch copies live under `$HOSTLIFE_SCRATCH`, by default |
| +`~/.cache/hostlife-sim`, never `/tmp`. |
| #### 14.2.2 What it models |
| @@ -1190,7 +1250,33 @@ CAUGHT or MISSED, and deletes the copy. Rules run in parallel. |
| delete. Every request is checked; a violation is an invariant failure. |
| - **Late S3 completion.** A request completes at once (70%) or is |
| "submitted, client cancelled, completed later", within a long-tailed |
| - horizon, after the issuer has moved on, crashed or been replaced. |
| + horizon, after the issuer has moved on, crashed or been replaced. A |
| + quarter of the long tail lands at exactly `H`, the latest S3's |
| + signature-age limit allows; staging PUTs take the long tail one time in |
| + three, other requests one in eight, because the cross-region path is the slow |
| + one. |
| +- **Paused goroutines (rev 5).** Four kinds, each killed by an agent crash |
| + or reboot, as a real goroutine is: |
| + - a **staging round** that revalidated under the staging-work lease and |
| + paused before signing (one round in three), and a **cut round's |
| + lost-response retry** whose first attempt paused (one in three). The |
| + pause runs up to `2 × staging TTL + H`, so it can outlast the lease; |
| + a frozen or paused box holds it longer. The `SafeUntil` check runs |
| + when the goroutine resumes (§7.3); |
| + - a **registry hold or open** that passed its checks and paused (one |
| + hold in 25, one open in six): mostly under 150 steps, one in four up |
| + to 2,000. Half the paused opens are woken **at the worst moment**: |
| + while a newer operation that has already committed holds the row, so |
| + that operation no longer re-asserts its hold; |
| + - a **stalled role worker** replaying an old read (rev 4's actor); |
| + - a **stalled door read**, with the same pause lengths, which resumes |
| + early at its worst moment: when its location's current object belongs |
| + to another incarnation. Rev 4's door reads never stalled for long, and |
| + rev 5's slower lifecycle hid `door-by-version` until they did. |
| +- **Obligations beside roles (rev 5).** Each box runs its obligations in a |
| + runner separate from its role worker, as the agent will, so a park can |
| + run while the same box's role work for a new operation proceeds. Rev 4's |
| + single worker serialized them and hid CC11's race. |
| - **Box faults.** Agent crash (flat children die with the service, nested |
| ones survive, memory is lost), reboot, whole-VM freeze, agent-only |
| pause, partition from the control bucket, clock skew, destruction (a |
| @@ -1211,6 +1297,21 @@ CAUGHT or MISSED, and deletes the copy. Rules run in parallel. |
| faults, then up to 6,000 calm steps: faults linger for a while (so the |
| operator meets a frozen or partitioned participant), then heal; stopped |
| boxes return or are replaced; ops gives every box its secrets key. |
| +- **Scale (rev 5).** One step is the unit of time. `H` = 60 steps; the |
| + execution lease 40, renewed within 15; the cut budget 320 (220 in rev 4); the |
| + staging-work lease 400, longer than the cut budget, as in production |
| + (rev 4 had 30, which made the staging horizon shorter than any landing |
| + and so unobservable); a move aborts if readiness has not come within |
| + 800. Moves are three times as frequent as in rev 4. |
| +- **What claim rev 3 changed in the model.** C3's harmlessness only for |
| + the operation's own version-bound calls; every retirement verifies after |
| + `evidence time + H`, and stragglers go into a further retirement event; |
| + `quiet_after` per region, kept across `free`, raised by each |
| + acknowledgement, and waited for by a create's first wake, a landing's |
| + install and a push's install; park destinations keyed by source version; |
| + one adoption routine with discovery parks; declare-lost by compute |
| + fencing only; the purge journal's phases; `RenderHeld` and |
| + `MaterializeHeld` before every probe. |
| #### 14.2.3 Invariants and results |
| @@ -1218,11 +1319,20 @@ The invariants are the table at the end of §4. Each run must also reach a |
| quiescent state: claim at rest, no obligations, record agreeing, the |
| serving box able to start, and nothing left anywhere else. |
| -**30,000 seeds: zero violations** on the final model (101 s). Across those |
| -seeds: 772 completed moves, 784 clean rollbacks, 54 dirty rollbacks, 294 |
| -pre-flip reverts, 153 declared losses, 11,063 aborts (mostly targets |
| -without a secrets key, which T2 refuses until ops installs one), 19,735 |
| -pushes and 50,752 delete-park-free cycles. |
| +**Rev 5, on the final model: 30,000 seeds, zero violations (23 s); 100,000 |
| +seeds, zero violations (80 s).** Across the 100,000 seeds: 199,462 |
| +completed operations, among them 3,887 completed moves, 2,858 clean |
| +rollbacks, 129 dirty rollbacks, 1,037 pre-flip reverts, 664 declared |
| +losses, 35,232 pushes and 113,792 delete-park-free cycles; and 41,863 |
| +aborts, mostly sources whose plan pass had not yet rendered `config.env` |
| +or adopted the release since their row was recreated (S1 refuses them), |
| +the rest targets without a secrets key (T2). In 2,731 install attempts the |
| +staging object was newer than the seal, because a paused round had written |
| +after it; in 249 of them it held an older snapshot. Those are the cases |
| +the seal by version exists for. |
| + |
| +Rev 4's figures (30,000 seeds, 101 s, 772 completed moves) were on a model |
| +that exercised less. |
| #### 14.2.4 Faults the model found beyond the joint review |
| @@ -1244,97 +1354,163 @@ Each was a failing seed first, then a rule; each rule's mutation is in |
| | model 11 | The purge path adopted without discovery | one adoption routine for every actor | claim | |
| | model 12 | A hold left behind by a finished operation (a revert re-held its row after opening) blocked serving | reconcile releases a hold whose operation ended | claim | |
| | model 13 | The cut deadline, checked only while probing, let slow installs commit far past the budget | every phase before the commit (§7.4) | moves | |
| +| model 14 (rev 5) | A staging round revalidated under its lease, paused, and signed after the lease ended; its PUT landed after the staging retirement had verified the prefix empty, leaving staging in the serving region or in a region the instance left | check `SafeUntil` immediately before signing (§7.3); CC14 | moves, claim | |
| +| model 15 (rev 5) | With obligations run beside role work, a move started while a `park-replica` of its target region was still running; the park retired the landing's fresh versions, and a cold restore lost acknowledged writes (or the landing never converged) | CC11, already in claim rev 3 §4: now proven | claim | |
| +| model 16 (rev 5) | A push whose row was already open (a reconcile acting on a cached claim had released its hold) never released: its open is conditional on its own `hold_op` and the row had none. Found under the `fresh-admission` mutation, unreachable with fresh admission; the open should be idempotent anyway | a push's open, like the landing's, accepts a row already open (F7) | push | |
| #### 14.2.5 Mutation results |
| -Every rule was dropped in turn, in a scratch copy, on one model version: |
| -3,000 seeds each, then 30,000 seeds for every rule not caught at 3,000, |
| -then pairs for rules that looked like double protection. The full |
| -harness output is summarized here; seed numbers reproduce any line with |
| +Every rule was dropped in turn, in a scratch copy, on the final rev 5 |
| +model: 3,000 seeds each, then 30,000 seeds for every rule not caught at |
| +3,000, then 30,000 seeds for each combination below (100,000 for the |
| +triple). The model has 50 rules: rev 4's 48, plus `sign-before-safeuntil` |
| +and `readiness-quiet`. Seed numbers reproduce any line with |
| `HOSTLIFE_SEED`. |
| -**Caught alone: 30 of 48 rules.** |
| +| | rules | |
| +|---|---| |
| +| caught alone | 37 | |
| +| caught only with a stated partner | 9 (six pairs and a triple) | |
| +| caught with a partner, but adding nothing to it | 1 (`restore-lineage-filter`) | |
| +| liveness only | 2 | |
| +| unreachable under claim rev 3 | 1 (`readiness-quiet`) | |
| + |
| +**Rev 4's eight gaps, now exercised.** Each is caught by its own mutation, |
| +except the two admission and hold rules, which are caught together with |
| +the start check and with nothing less. |
| + |
| +| rule | rev 4 | rev 5 | what the model finds | |
| +|---|---|---|---| |
| +| `seal-by-version` (J-N2, §7.3) | not exercised | 5 / 30,000 | acknowledged writes lost: the install copied the current staging object, an older snapshot a paused round wrote after the seal | |
| +| `staging-quiet` (J-N2, §7.3) | not exercised | 9 / 30,000 | staging left in the serving region | |
| +| `fresh-admission` (claim C4) | not exercised | 79 / 30,000 | 77 pushes that never finish (a reconcile acting on a cached claim retired the pushing box's row); one freed host's residue; one pair of writers | |
| +| `exec-admission` (claim C4, K1) | not exercised | only in the triple below | two writers | |
| +| `hold-op-open` (§5.2) | not exercised | only in the triple below | two writers | |
| +| `stale-hold-release` (CC10) | not exercised | 37 / 30,000 | a held row blocks serving forever | |
| +| `obligations-block-ops` (CC11) | not exercised | 14 / 30,000 | **acknowledged writes lost** on a cold restore (6): a landing into a region whose park was still running; landings that never converge (8). Model 15 | |
| +| `claim-op-exclusive` (claim C2) | not exercised | 2,005 / 30,000 | operations that never converge when a delete starts over another operation (2,004); local state left for a freed host (1) | |
| + |
| +**The triple: `exec-admission` + `hold-op-open` + `start-check`.** 5 of |
| +100,000 seeds fail with two writers; each rule alone, and each pair of |
| +them, fails none of 100,000. Seed 1920: a push's paused open resumes after |
| +a later move from that box committed, and opens the old source's row, |
| +which the move held. Three independent fences stood behind that: |
| + |
| +- the conditional open refuses it, because the row's `hold_op` names the |
| + move (§5.2); |
| +- admission refuses the old source, because `op.exec` names the target |
| + (claim C4). The model's admission table names each phase's role by box, |
| + and the role is always `op.exec`, so `exec-admission` drops both; |
| +- the start check refuses its stale database, whose generation is not the |
| + committed one (INV-4). |
| + |
| +That is double protection twice over, as intended. The model cannot |
| +separate the exec check from the role column of the admission table, |
| +because in Phase 1 they never differ; a future kind whose roles are not |
| +its executor (a sidecar, F6) would need its own check. |
| + |
| +**New rules in rev 5.** |
| | rule | seeds failing | what the model finds | |
| |---|---|---| |
| -| `recovery-pass` (claim C2, §8) | 1,474 / 3,000 | creates and deletes stranded mid-operation never converge | |
| -| `fresh-incarnation` (claim §5.1, R3-12) | 467 / 3,000 | a reused identity meets its own retired marker; the host never serves again | |
| -| `legacy-discovery` (CC4) | 10 / 3,000 | legacy residue in a region the host does not serve | |
| -| `park-by-version` (claim C3, J-N1) | 2,311 / 3,000 | parks never finish | |
| -| `park-all-versions` (claim §6.2) | 572 / 3,000 | parks never finish (older versions resurface) | |
| -| `park-skip-lock` (claim §6.2, J-N6) | 3,000 / 3,000 | parks never finish (the parker's own lock) | |
| -| `retirement-ids` (claim §6.2, J-D5, J-N7) | 214 / 3,000 | parks never finish (one manifest, two events) | |
| -| `quiet-before-ack` (CC1) | 16 / 3,000 | residue after a park, from late replication PUTs | |
| -| `hostguard` (claim C7, J-N8) | 30 / 3,000 | an instance starts with another incarnation's `config.env` | |
| -| `purge-reserve-first` (claim §5.3, J-N9) | 2,300 / 3,000 | stores destroyed under held hosts | |
| -| `purge-adopts` (CC6) | 17 / 3,000 | a store destroyed under a serving legacy host | |
| -| `purge-journal` (CC7) | 6 / 3,000 | a new incarnation's store destroyed by a stalled purge | |
| -| `door-by-version` (claim §6.5, J-N10) | 1 / 3,000 | a door read returns another incarnation's data | |
| -| `evidence-whole-tree` (claim §6.1, J-D3) | 5 / 3,000 | two writers; lost writes | |
| -| `evidence-from-state` (claim §6.1, R2-2) | 2,999 / 3,000 | evidence never comes after an agent restart | |
| -| `floor-boot-recovery` (claim §10, J-N12) | 2,651 / 3,000 | old executables break parks and admission | |
| -| `participant-rows` (claim §2, R2-4) | 10 / 3,000 | source recovery stuck | |
| -| `revalidate-before-effect` (claim C3, J-N2) | 40 / 3,000 | a stalled worker's effects lose acknowledged writes | |
| -| `declare-lost-compute` (CC5, J-D6, J-N3) | 1 / 30,000 | two writers after a frozen target resumes | |
| -| `lost-quarantine` (claim §7.2, CC12) | 2 / 30,000 | a returning box keeps stale local state | |
| -| `cut-deadline` (§7.4, J-N15) | 60 / 3,000 | commits past the budget | |
| -| `revert-journal` (§7.4, J-N11) | 28 / 3,000 | pre-flip recovery stuck on its own writes | |
| -| `rollback-journal` (§7.8, R2-3) | 9 / 30,000 | rollback recovery stuck | |
| -| `dirty-return` (§7.8, INV-2) | 2 / 3,000 | the target's acknowledged writes lost | |
| -| `target-park` (§7.8, R2-5) | 8 / 3,000 | a rolled-back target's replica left live | |
| -| `abort-target-owner` (§7.11, J-N13) | 1,084 / 3,000 | IAM violations | |
| -| `timer-freeze` (§9.4, J-N14) | 10 / 3,000 | clean recovery stuck | |
| -| `timer-canonical` (§9.4, J-N14) | 6 / 3,000 | clean recovery stuck | |
| -| `markers-last` (§7.6, R2-6) | 1 / 3,000 | an acknowledged timer lost | |
| -| `source-readiness` (§7.3, S1) | 14 / 3,000 | pre-flip recovery stuck | |
| - |
| -**Caught only in pairs: 8 rules (double protection).** Each is covered by |
| -its partner; both stay, because the model cannot prove that the partner |
| -holds in every real case. |
| - |
| -| pair | seeds failing (30,000) | what the model finds | |
| +| `sign-before-safeuntil` (claim C3, §7.3, model 14) | 156 / 30,000 | staging in a region the instance left (131), in the serving region (17), and in a freed host's namespace (8) | |
| +| `readiness-quiet` (T12) | 0 / 100,000 | unreachable under claim rev 3; see below | |
| + |
| +**Caught alone: 37 of 50 rules.** |
| + |
| +| rule | seeds failing | what the model finds | |
| |---|---|---| |
| -| `files-carried` + `start-check` | 1,102 | **the kpmg outage**: the app mints a new `instance.key` over data sealed with the old one | |
| -| `strict-spawn` + `evidence-whole-tree` | 313 | two writers | |
| -| `restore-lineage-filter` + `quiet-before-ack` | 207 | residue | |
| -| `touched-incomplete` + `legacy-discovery` | 5,305 | a freed host's residue | |
| -| `commit-before-open` + `dirty-return` | 10 | lost writes | |
| -| `watchdog-independent` + `declare-lost-compute` | 2 | two writers | |
| -| `claim-cas-opid` + `revalidate-before-effect` | 1,062 (against 40 per 3,000 for revalidation alone) | a stale purge step destroys a store; the op-id condition is the second line behind revalidation | |
| +| `recovery-pass` (claim C2, §8) | 1,405 / 3,000 | creates and deletes stranded mid-operation never converge | |
| +| `fresh-incarnation` (claim §5.1, R3-12) | 100 / 3,000 | a reused identity meets its own retired marker; the host never serves again | |
| +| `legacy-discovery` (CC4) | 12 / 3,000 | legacy residue in a region the host does not serve | |
| +| `park-by-version` (claim C3, J-N1) | 2,325 / 3,000 | parks never finish | |
| +| `park-all-versions` (claim §6.2) | 926 / 3,000 | parks never finish (older versions resurface) | |
| +| `park-skip-lock` (claim §6.2, J-N6, CC3) | 3,000 / 3,000 | parks never finish (the parker's own lock) | |
| +| `retirement-ids` (claim §6.2, J-D5, J-N7) | 392 / 3,000 | parks never finish (one manifest, two events) | |
| +| `quiet-before-ack` (CC1) | 103 / 3,000 | residue after a park, from late replication PUTs | |
| +| `hostguard` (claim C7, J-N8) | 8 / 3,000 | an instance starts with another incarnation's `config.env` | |
| +| `purge-reserve-first` (claim §5.3, J-N9) | 2,113 / 3,000 | stores destroyed under held hosts | |
| +| `purge-adopts` (CC6) | 19 / 3,000 | a store destroyed under a serving legacy host | |
| +| `purge-journal` (CC7) | 5 / 30,000 | a new incarnation's store destroyed by a stalled purge | |
| +| `door-by-version` (claim §6.5, J-N10) | 25 / 30,000 | a door read returns another incarnation's data | |
| +| `evidence-whole-tree` (claim §6.1, J-D3) | 13 / 30,000 | two writers; lost writes | |
| +| `evidence-from-state` (claim §6.1, R2-2) | 2,982 / 3,000 | evidence never comes after an agent restart | |
| +| `floor-boot-recovery` (claim §10, J-N12) | 2,696 / 3,000 | old executables break parks and admission | |
| +| `participant-rows` (claim §2, R2-4) | 6 / 3,000 | source recovery stuck | |
| +| `revalidate-before-effect` (claim C3, J-N2) | 31 / 3,000 | a stalled worker's effects lose acknowledged writes | |
| +| `declare-lost-compute` (CC5, J-D6, J-N3) | 1 / 30,000 | acknowledged writes lost after a frozen target resumes | |
| +| `lost-quarantine` (claim §7, CC12) | 2 / 30,000 | a returning box keeps stale local state | |
| +| `cut-deadline` (§7.4, J-N15) | 50 / 3,000 | commits past the budget | |
| +| `revert-journal` (§7.4, J-N11) | 22 / 3,000 | pre-flip recovery stuck on its own writes | |
| +| `rollback-journal` (§7.8, R2-3) | 11 / 30,000 | rollback recovery stuck | |
| +| `dirty-return` (§7.8, INV-2) | 1 / 3,000 | the target's acknowledged writes lost | |
| +| `target-park` (§7.8, R2-5) | 11 / 3,000 | a rolled-back target's replica left live | |
| +| `abort-target-owner` (§7.11, J-N13) | 1,260 / 3,000 | IAM violations | |
| +| `timer-freeze` (§9.4, J-N14) | 4 / 3,000 | clean recovery stuck | |
| +| `timer-canonical` (§9.4, J-N14) | 4 / 3,000 | clean recovery stuck | |
| +| `markers-last` (§7.6, R2-6) | 12 / 30,000 | an acknowledged timer lost | |
| +| `source-readiness` (§7.3, S1) | 18 / 3,000 | pre-flip recovery stuck | |
| + |
| +The six rules of rev 4's gap table that are caught alone, and |
| +`sign-before-safeuntil`, complete the 37. |
| + |
| +**Caught only with a partner.** The partner's own count, on the same |
| +30,000 seeds, shows whether the combination adds anything. |
| + |
| +| combination | fails (30,000) | partner alone | what the model finds | |
| +|---|---|---|---| |
| +| `files-carried` + `start-check` | 1,498 | 0 and 0 | **the kpmg outage**: the app mints a new `instance.key` over data sealed with the old one | |
| +| `strict-spawn` + `evidence-whole-tree` | 210 | 13 | two writers | |
| +| `touched-incomplete` + `legacy-discovery` | 5,925 | 170 | a freed host's residue | |
| +| `claim-cas-opid` + `revalidate-before-effect` | 1,201 | 248 | a stale purge step destroys a store | |
| +| `watchdog-independent` + `declare-lost-compute` | 6 | 1 | two writers (the partner alone loses writes, but never runs two) | |
| +| `commit-before-open` + `dirty-return` | 8 | 6 | lost writes; a weak signal, two seeds over the partner | |
| +| `restore-lineage-filter` + `quiet-before-ack` | 1,105 | 1,105 | **nothing beyond the partner**: the same seeds fail the same way. Rev 4 reported 207 against about 160; rev 5's model makes `quiet-before-ack` fail first in every seed where the filter would matter. The filter stays as the second line behind the horizon, unproven by this model | |
| +| `exec-admission` + `hold-op-open` + `start-check` | 5 / 100,000 | 0 / 100,000 for each pair | two writers (above) | |
| **Liveness rules: 2.** `readiness-key` (T2) and `config-before-probe` |
| cannot break a safety invariant: without them a target that cannot render |
| config fails its probe and rolls back. They exist to avoid a pointless |
| cut, and the model confirms that is all they do. |
| -**Not exercised by the model: 8 rules, the model's known gaps.** |
| - |
| -| rule | why not | next step | |
| -|---|---|---| |
| -| `seal-by-version`, `staging-quiet` (J-N2) | no seed lands a staging write after the seal, even at 30,000 seeds with a fat staging tail; dropping both together is not caught either | add a staging writer that re-signs after the seal from a stale read past its lease, then require both rules to be caught | |
| -| `exec-admission`, `fresh-admission` | the model's row holds and per-role admission table already refuse what these refuse; dropping both is not caught | model a second consumer of the claim that does not consult the row (the db door, a sibling box's edge) | |
| -| `hold-op-open`, `stale-hold-release` | no stale opener survives once the revert fix was in; stale holds no longer arise | add an open retried by a stalled worker after release | |
| -| `obligations-block-ops` (CC11) | one worker per box serializes obligations and roles, which hides the race | split the obligation runner from the role worker | |
| -| `claim-op-exclusive` | caught once in an earlier model version (1 / 30,000), not in the final one: a delete mid-move stays safe under the quiet horizon | keep as a product rule; widen the delete actor | |
| +**Unreachable under claim rev 3: `readiness-quiet` (T12).** With it |
| +dropped, no cut waits on `quiet_after` in 100,000 seeds, and an |
| +instrumented run of 30,000 seeds finds no install of any kind (a create's |
| +first wake, a landing, a push) that waits on it either. Every retirement |
| +acknowledges only after its own `evidence time + H` (claim §6.2a), so the |
| +`quiet_after` it writes has passed by then; CC11 keeps operations off |
| +regions with pending chores; and `free` needs every obligation gone. T12 |
| +and the field stay as defence in depth (§7.3, §19.4's note). |
| -**Seeds:** 300 in the gate (about a second); the 30,000-seed baseline and |
| -the sweep are the evidence for this revision. The harness is committed; |
| -its scratch copies are not. |
| +**Seeds:** 300 in the gate (about a second); the 30,000- and 100,000-seed |
| +baselines and this sweep are the evidence for rev 5. The harness is |
| +committed; its scratch copies are not. |
| #### 14.2.6 What the model does not prove |
| - **It is a model.** It checks the protocol, not the code that will |
| implement it. Its S3 is a versioned map with the documented semantics |
| (copy and delete by version id, delete markers, conditional writes on |
| - the claim); the AWS behaviour it relies on must be checked against the |
| - real service before planning, as the paid-launch work learned ("a fake |
| - provider is never proof"). |
| + the claim). As the paid-launch work learned, "a fake provider is never |
| + proof": every AWS behaviour it relies on has now been checked against the |
| + real service (§15 step 8), and each held. |
| - **Granularity.** One host, three regions, one box per region. One step |
| is atomic, so "revalidate immediately before the effect" is modelled as |
| revalidating at the start of the step. |
| -- **Clock and pause assumptions** (claim §3.1) are modelled only as skew; |
| - correctness never depends on them except through the late-completion |
| - horizon H, which S3's signature-age limit bounds. |
| +- **Clock and pause assumptions** (claim §3.1) are modelled as skew of |
| + at most one step and, since rev 5, as paused goroutines (§14.2.2). |
| + Correctness depends on them in two places only: the late-completion |
| + horizon `H`, which S3's signature-age limit bounds (checked, §15 step 8), |
| + and the staging writer's `SafeUntil` check, whose margin must cover the |
| + pause between check and signature (model 14, CC14). The model makes the |
| + check and the signature one atomic step; the margin is the |
| + implementation's to prove. |
| +- **Adversarial scheduling is partial.** The model wakes two kinds of |
| + paused work at the worst moment it knows of, and everything else at |
| + random. Two results depend on those targeted wake-ups: the admission |
| + triple (a paused open woken after a newer operation committed) and |
| + `door-by-version` (a stalled read woken while its location holds another |
| + incarnation's object). Worse moments it does not know of may exist. |
| - **Not modelled:** placement (`placing`), sidecars, nested-to-flat path |
| translation beyond the files directory, the db door's multi-console |
| drain, and app moves (F5). |
| @@ -1365,7 +1541,7 @@ region that lost. |
| 1. **Ops (in progress):** `CARLOS_SECRETS_KEY` on every region box. |
| 2. **Release with `secrets_key_fp`.** Verify every box against the console. |
| 3. **The host lifecycle claim** (claim spec §10) ships and every box and |
| - console honours it, with §19.4's changes adopted. |
| + console honours it: claim rev 3, with CC14 (§19.4) adopted. |
| 4. **Phase 1 release, dormant.** Consoles first, then every box. |
| 5. **Infra (M17, approved):** every region's `sibling_staging_arns` lists |
| every other region's data bucket. Applied per deployment as scoped work. |
| @@ -1376,10 +1552,26 @@ region that lost. |
| production rehearsal** (joint N12). Rev 3 had this after the |
| rehearsals. |
| 7. **Rehearsals** in §14.4's order; each gates the next. |
| -8. **Before planning:** the AWS behaviours the model assumes (copy and |
| - delete by `versionId`, delete-marker semantics, conditional writes on the |
| - claim, the signature-age limit behind H) are checked against the real |
| - service in a scoped sandbox (§14.2.6). |
| +8. **Done (2026-10-02, before planning):** the AWS behaviours the model |
| + assumes were checked against the real service, on a versioned |
| + general-purpose bucket (§14.2.6). Every assumption holds: |
| + - **copy by version:** `CopyObject` from `versionId=V1` returns V1's |
| + body after V2 exists; |
| + - **delete by version:** `DeleteObject --version-id V1` removes only V1; |
| + - **delete markers:** a bare delete leaves a marker, and V2 is still |
| + copyable by its `versionId` (why bare deletes are forbidden on host |
| + prefixes, and why a copy by version ignores a stale marker); |
| + - **create-only writes:** `If-None-Match: *` gets 412 on an existing key |
| + and succeeds on a new one (evidence objects, manifests, `move.json`); |
| + - **conditional writes:** `If-Match` with a wrong ETag gets 412 on both |
| + PUT and DELETE, so conditional delete works on general-purpose |
| + versioned buckets (the claim CAS, lock release); |
| + - **the signature-age limit behind `H`:** a SigV4 request signed 14 |
| + minutes earlier returns 200; one signed 16 minutes earlier returns 403 |
| + `RequestTimeTooSkewed`; |
| + - **copy receipts:** a multipart ETag becomes a single-part ETag on |
| + copy; `CopyObjectResult.ETag` equals the ETag the listing shows; a |
| + `CopySourceIfMatch` mismatch gets 412. |
| ## 16. Follow-on specs |
| @@ -1397,7 +1589,8 @@ No design here, only what each must satisfy. |
| ## 17. Interface points with #199 and the claim spec |
| -The claim spec supersedes #199 rev 3 from its §2 onward. Against claim rev 2: |
| +The claim spec supersedes #199 rev 3 from its §2 onward. Against claim rev 3 |
| +(each point held in rev 2 too): |
| | id | point | status | |
| |---|---|---| |
| @@ -1408,9 +1601,9 @@ The claim spec supersedes #199 rev 3 from its §2 onward. Against claim rev 2: |
| | I-5 | **Recreate** | agreed: create requires the claim free or absent (claim C2) | |
| | I-6 | **Participant rows** | agreed: retained while an operation lists them (claim §2 obligations, K9) | |
| | I-7 | **Flat files** | agreed: claim §6.3 retires the flat `<DataDir>/<host>/`, the sibling env files and markers (joint I-7) | |
| -| I-8 | **`generation.json`, `install-<op>.json`, `lock.json`** | agreed, with CC3: the parker's own renewed lock is excluded everywhere | |
| -| I-9 | **Destructive steps** | agreed: exact versions plus the staging-work lease (claim C3), with CC1's horizon for the replication exception | |
| -| I-10 | **Push** | agreed: later, on §5's handoff and the claim's `pushing` op; it needs CC13 | |
| +| I-8 | **`generation.json`, `install-<op>.json`, `lock.json`** | agreed: the parker's own renewed lock is excluded everywhere (CC3, claim rev 3 §6.2) | |
| +| I-9 | **Destructive steps** | agreed: exact versions plus the staging-work lease (claim C3), with the horizon for the replication exception (CC1, claim rev 3 §6.2a). Open: CC14, the staging writer's `SafeUntil` check | |
| +| I-10 | **Push** | agreed: later, on §5's handoff and the claim's `pushing` op, with every probe rendering held config (CC13, claim rev 3 §6.1). The model found the push's open must also accept a row already open (model 16) | |
| ## 18. Questions for Paul |
| @@ -1444,7 +1637,7 @@ The db door refuses on every move kind (claim spec §8). |
| | **K2 Acquire** | The request door CASes `serving` → `moving` only when the holder incarnation equals the record's and no other op holds the claim; it sets op id, participants and `exec` = source box. | |
| | **K3 Execution to none** | A participant that has written stop evidence for this op id (§5.3) may CAS `exec` from itself to `none`. Nobody moves `exec` from one box to another directly. | |
| | **K4 Execution handoff** | From `none` to a participant, by that participant, citing the previous holder's evidence object for this op id: at the flip (to target), at source recovery (to source), and at `reverting` (the source, citing its own evidence). | |
| -| **K5 Revalidation** | Before each destructive step the actor re-reads the claim and requires op id, kind, phase and `exec` to be what the step expects (claim spec C3); it holds the lease of every location it writes, acting before `SafeUntil`, with the exception in §19.3 D4. | |
| +| **K5 Revalidation** | Before each destructive step the actor re-reads the claim and requires op id, kind, phase and `exec` to be what the step expects (claim spec C3); it holds the lease of every location it writes, acting before `SafeUntil`, with the exception in §19.3 D4. A staging writer checks `SafeUntil` immediately before it signs each request, after any pause (§7.3, model 14). | |
| | **K6 Wake admission** | `doWake` reads the claim fresh (claim spec §8) and applies K1. A box that reads a claim whose `exec` does not name it kills that host's process tree (§5.3 step 1) before anything else. | |
| | **K7 Other operations** | Delete, purge, park and push start only from `serving` (claim spec §4); while a move op holds the claim they are refused. Create requires free or absent. | |
| | **K8 Release** | Only the move releases to `serving`, naming `exec` (target after `done`; source after `rolled_back` or `aborted`), and only when every obligation this op created is gone (§7.8, §7.9). | |
| @@ -1454,7 +1647,10 @@ The db door refuses on every move kind (claim spec §8). |
| | **K12 Touched** | Every move write site CAS-adds its region or box to `touched` before its first write: the source adds the target region before the first staging round; the target adds its box at `checking`; a dirty return adds nothing new (the source region and box are already there). | |
| | **K13 Evidence durability** | Evidence objects are create-only per (op id, role) in the deployment bucket, recomputable from system state after a restart (§5.3), and referenced from the claim journal, never inlined. | |
| -### 19.3 Rev 3's requests, against claim rev 2 |
| +### 19.3 Rev 3's requests, against claim rev 2 and rev 3 |
| + |
| +Each status below holds in claim rev 3 as well, except D6, which rev 3 |
| +completes. |
| | id | status in claim rev 2 | here | |
| |---|---|---| |
| @@ -1463,7 +1659,7 @@ The db door refuses on every move kind (claim spec §8). |
| | D3 strict spawn, whole-tree evidence | adopted (§6.1, joint text) | §5.3 | |
| | D4 cross-region staging exclusion | replaced, as the review ruled: a shared staging-work lease (C3); sealing and draining are this spec's | §7.3 | |
| | D5 per-event retirement destinations | adopted (§6.2) | §6.4 | |
| -| D6 declare-lost | adopted with an execution-lease branch; **this spec drops that branch** (model 5) | §7.10, CC5 | |
| +| D6 declare-lost | adopted with an execution-lease branch; this spec dropped that branch (model 5), and **claim rev 3 drops it too** (CC5): compute fencing only, the lease for liveness | §7.10, CC5 | |
| | D7 move-kind transitions | adopted (§4, joint text) | §19.1 | |
| | D8 who waits for obligations | adopted (§2) | K8 | |
| | I-7 flat local inventory | adopted (§6.3) | §6.1 | |
| @@ -1474,6 +1670,13 @@ This spec does not edit the claim spec. Each change below is what the |
| model needed for its invariants to hold; the mutation column says what the |
| model finds when the rule is dropped (§14.2.5). |
| +**CC1 to CC13 (rev 4, against claim rev 2): all adopted in claim rev 3** |
| +(8b5bccb2), which maps them in its §12: CC1 to C3, §2 `quiet_after` and |
| +§6.2a; CC2 and CC3 to §6.2; CC4 and CC8 to §9; CC5, CC9 and CC12 to §7; |
| +CC6 and CC7 to §5.3; CC10 to §8; CC11 to §4; CC13 to §6.1. Rev 5's model |
| +encodes rev 3's text; §14.2.5 gives each rule's mutation result, CC11's |
| +included now (model 15). |
| + |
| | id | change to claim rev 2 | why (model) | |
| |---|---|---| |
| | **CC1** | **A late-completion horizon on every retirement.** C3's "late completion is harmless" holds only for version-bound calls. Ordinary replication, the C3 exception, is plain PUTs to reused LTX names. A park acknowledges only after a verification listing taken later than `evidence time + H` (H = S3's 15-minute signature-age limit plus the longest request), and parks anything found then as a further retirement event. Persist a per-region `quiet_after` in the claim (kept across `free`, like `retired_incarnations`) and make any install into that region's namespace wait for it | model 1: residue in a freed host's namespace | |
| @@ -1486,10 +1689,25 @@ model finds when the rule is dropped (§14.2.5). |
| | **CC8** | **One adoption routine for every adopting actor**, discovery included | model 11 | |
| | **CC9** | **A fresh read with no operation ends a self-fence** (§7.1): a box clears its local execution-lease file when the claim shows it is not a participant | model 9 | |
| | **CC10** | **Reconcile releases a stale hold**: a held row whose `hold_op` is not the claim's operation, on the box the claim names as executor of a `serving` host, is opened | model 12 | |
| -| **CC11** | **No operation starts over a pending chore in its locations**: the start CAS from `serving` fails if an obligation names a region or box the operation will write | model gap: not exercised (§14.2.5) | |
| +| **CC11** | **No operation starts over a pending chore in its locations**: the start CAS from `serving` fails if an obligation names a region or box the operation will write | model 15 (rev 5): a move landed into a region whose park was still running, and the park retired the landing's new versions: acknowledged writes lost on cold restore, or a landing that never converges | |
| | **CC12** | **A quarantined box retires its own state.** §7.2 says a box on `lost` is refused "until its old state has been retired"; name the actor: the returning box itself, under the host guard, before anything else | the returning box otherwise keeps stale local state | |
| | **CC13** | **Every probe renders held config** (`RenderHeld`, `MaterializeHeld`) before spawning, including a future push's | model 4 | |
| +**Rev 5's request, against claim rev 3:** |
| + |
| +| id | change to claim rev 3 | why (model) | |
| +|---|---|---| |
| +| **CC14** | **Staging residency rests on `SafeUntil`.** C3's closing paragraph says correctness no longer depends on the clock and pause assumption, because every destructive request is version-bound. Cross-region staging is the exception: a staging PUT creates a version, and the moves spec's staging horizon (`seal time + staging TTL + H`, §7.3) bounds only requests signed before the lease's `SafeUntil`. State that every request under the staging-work lease checks `SafeUntil` immediately before it is signed, after any pause, and that §3.1's margin must cover clock skew plus the longest pause between that check and the signature | model 14: staging residue in a region the instance left, or in the serving region (`sign-before-safeuntil`, §14.2.5) | |
| + |
| +**Not a change, a note for platform-1e (model, rev 5).** Under rev 3 the |
| +install wait on `quiet_after` never bites: every retirement acknowledges |
| +only after its own `evidence time + H`, so the `quiet_after` it writes has |
| +already passed; CC11 keeps operations off regions with pending chores; and |
| +`free` needs every obligation gone. The model drops this spec's T12 |
| +(which reads `quiet_after`) and no cut ever waits. Keeping the field costs |
| +one map in the claim and guards any future path that acknowledges before |
| +its horizon; removing it is the claim authors' call. |
| + |
| ## 20. Copy (pending copy review) |
| Drafts for the operator CLI. None is approved; every string goes through the |
| @@ -1602,7 +1820,7 @@ New findings: |
| | # | finding | owner | rev 4 | |
| |---|---|---|---| |
| | N1 | late server-side completion | claim, with moves | exact-version deletes and copies (claim C3, §6.4); **plus CC1**: the replication exception is not version-bound, so retirements verify after the horizon. Mutations `park-by-version`, `park-all-versions` and `quiet-before-ack` are caught | |
| -| N2 | delayed staging round corrupts sealed staging | moves | staging-work lease; revalidation under it; seal by version; staging retired after its writers' horizon (§7.3). The stalled-worker actor exercises it; `revalidate-before-effect` is caught. See §14.2.5 for `seal-by-version` | |
| +| N2 | delayed staging round corrupts sealed staging | moves | staging-work lease; revalidation under it; seal by version; staging retired after its writers' horizon (§7.3). The stalled-worker actor exercises it; `revalidate-before-effect` is caught. **Rev 5:** the seal and the horizon are proven too (`seal-by-version` 5 / 30,000, `staging-quiet` 9 / 30,000), and both rest on a third rule the model found: sign only before `SafeUntil` (model 14, CC14) | |
| | N3 | declare-lost starts a second writer | claim, with moves | compute fencing only (§7.10, CC5) | |
| | N4 | unjournaled claim and record gaps | claim | intent in the start CAS and a recovery owner per state (claim C2, §8); the model's `recovery-pass` mutation is caught in about half of all seeds | |
| | N5 | lazy legacy adoption | claim | `touched.complete` (claim §9) **plus CC4** discovery at adoption; `legacy-discovery` caught | |
| @@ -1621,3 +1839,26 @@ New findings: |
| The joint review's verdict was "not ready" for both specs and asked for |
| the expanded simulation before planning. That simulation now exists and |
| passes; the claim changes it requires are §19.4. |
| + |
| +### 22.4 The coordinator's round on rev 4 (claim rev 3, 8b5bccb2) |
| + |
| +| request | rev 5 | |
| +|---|---| |
| +| exercise the eight rules rev 4 listed as gaps, each caught alone or in a stated pair | six caught alone; `exec-admission` and `hold-op-open` caught only together with `start-check`, a triple, 5 / 100,000, with every pair of the three at 0 / 100,000 (§14.2.5). The model's new actors: paused staging signers, paused registry writes woken at the worst moment, an obligation runner beside the role worker, admission from cached reads, stalled door reads | |
| +| seal by version and the staging horizon first (N2's remaining half) | both proven, and the rule under them found: a staging writer signs only before `SafeUntil` (model 14, §7.3, CC14) | |
| +| prove CC11 | proven: dropping it loses acknowledged writes on a cold restore (model 15) | |
| +| re-run 30,000 seeds, at least one 100,000-seed run, the full mutation sweep | 30,000 and 100,000 seeds, zero violations; the sweep is §14.2.5 | |
| +| re-encode against claim rev 3 | done (§14.2.2): C3 limited to the operation's own version-bound calls; verification after `evidence time + H`, stragglers as a further retirement event; `quiet_after` kept across `free`, installs waiting for it; destinations keyed by source version; one `claim.Adopt` with discovery parks; declare-lost by compute fencing only, the lease for liveness; the purge journal's phases; `RenderHeld` and `MaterializeHeld` before every probe | |
| +| mark §15 step 8 done with the real-AWS results | done (§15 step 8): every assumption holds | |
| + |
| +Found on the way: |
| + |
| +- **Model 16** (§14.2.4): a push's open must accept a row already open. |
| + Push is F7's; recorded there through I-10 (§17). |
| +- **`readiness-quiet` and the `quiet_after` install wait cannot bite under |
| + claim rev 3** (§14.2.5). Kept as defence in depth; noted for platform-1e |
| + (§19.4). |
| +- **`restore-lineage-filter` adds nothing the model can see** once the |
| + horizon holds (§14.2.5). Rev 4 counted it as a pair; rev 5 does not. |
| +- **`door-by-version` went unexercised** at 100,000 seeds until door reads |
| + were given real stalls (§14.2.2); it is caught again, 25 / 30,000. |