spec: move-any-app rev 7.5: joint round 4, claim rev 6.2 encoded (#175)
Written against claim rev 6.2 (30d572c4). R4-1: the restore contract
is one implementable contract: the sealed transfer is a verified final
snapshot, generation.json names it by key, version, TXID and checksum,
and the restore is litestream v0.5.14's planner from that snapshot,
overlap allowed, failing closed on a missing snapshot, a gap or a
checksum mismatch; the content comparison is gone; §7.6 produces what
the restore consumes. R4-2: installation stamps as claim rev 6.2 states
them, op ids opaque. R4-3: the live set is {authoritative region,
location, generation} with a phase table naming the authoritative
region; claim §6.2a cited for the sweep-side rule. R4-4: retirement
under the purge's own claim, encoded. Q2: the absent-claim branch is
explicit. The random generator's marker-hold and targeted-freeze
schedules, the retention fixture and two successful-move oracles let
the sweep catch generation-conditional, declare-lost-compute and
sweep-namespace alone; the three-rule door combination is reported as
not minimal.
The model (5de710b2, e08c5fd0) encodes all of it. Results: 30,000 and
100,000 seeds clean; 75 rules, 60 caught alone, 12 in stated
combinations, 2 liveness, 1 unreachable, none unexercised. §22.11 has
every disposition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+459
−226
docs/superpowers/specs/2026-10-02-move-any-app-design.md+459 −226
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 02866d4cd..1cf1ffaa4 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,7 +1,36 @@ |
| # Move any app or instance to another region: Phase 1, a safe single-instance move |
| **Date:** 2026-10-03 |
| -**Revision:** 7.4. |
| +**Revision:** 7.5. |
| + |
| +- **Rev 7.5** answers the moves side of **joint round 4** |
| + (`CODEX-FINDINGS-JOINT-R4.md`, cited "R4-n") and is written against |
| + **claim spec rev 6.2** (30d572c4), which answers the claim side (R4-2 |
| + installation stamps, R4-3 the region-qualified live set, R4-4 |
| + retirement under purge) and the moves session's three questions (rev |
| + 6.1, 79a5243c); §22.11 has every disposition. **R4-1 (P1):** the restore |
| + contract is one implementable contract (§5.1): the sealed transfer is a |
| + verified final snapshot, `generation.json` names it by key, version, |
| + TXID and checksum, and the restore is litestream v0.5.14's planner from |
| + that snapshot, overlap allowed, failing closed on a missing snapshot, a |
| + gap or a checksum mismatch; §7.6 produces what the restore consumes. |
| + The model's databases are key-value maps with inserts, updates and |
| + deletes, its replica objects are LTX files with ranges, page images, |
| + checksums and compaction, and its restore is the planner, so a |
| + compacted continuation and a genuine gap each have a witness |
| + (`restore-overlap`, `restore-gap-closed`). **R4-2:** installation stamps |
| + as claim rev 6.2 states them, the op counter gone (§6.4); a stale writer |
| + across a retire-and-reinstall is the new witness (`fence-stamp`). |
| + **R4-3:** the live set is `{authoritative region, location, |
| + generation}`, with a phase table naming the authoritative region |
| + (§5.1), and a witness for an old regional PUT after adoption |
| + (`live-set-region`). **R4-4:** retirement under the purge's own claim, |
| + encoded. The random generator gains marker-hold and targeted-freeze |
| + schedules, a retention fixture and two successful-move oracles, so |
| + `generation-conditional`, `declare-lost-compute` and `sweep-namespace` |
| + are now caught by the sweep as well. Results: 30,000 and 100,000 seeds |
| + clean; 75 rules, 60 caught alone, 12 in stated combinations, 2 |
| + liveness, 1 unreachable, none unexercised (§14.2.5). |
| - **Rev 7.4** answers the moves side of **joint round 3** |
| (`CODEX-FINDINGS-JOINT-R3.md`, cited "R3-n") and is written against |
| @@ -221,8 +250,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 6 at 3ea3f90c** on `design-replica-ownership`. Rev 7.4 is |
| - written against rev 6; rev 7.2 and 7.3 against rev 5.2 (465fee96); rev 7.1 against rev 5.1 (64eec93b); rev 7 |
| + **rev 6.2 at 30d572c4** on `design-replica-ownership`. Rev 7.5 is |
| + written against rev 6.2; rev 7.4 against rev 6 (3ea3f90c); rev 7.2 and 7.3 against rev 5.2 (465fee96); rev 7.1 against rev 5.1 (64eec93b); rev 7 |
| against rev 5 (4a458a9c); rev 6 against rev 4 (a3c86fa5); rev 5 against rev 3 |
| (8b5bccb2); rev 4 against rev 2 (cf3a7861). |
| @@ -325,7 +354,10 @@ adopted them; rev 5's CC14 is in claim rev 3.1 and rev 4; rev 6's CC15 to |
| CC19 are in claim rev 5, rev 7's CC20 and CC21 in claim rev 5.1 (CC21 |
| as a purge fence), and rev 7.1's CC22 in claim rev 5.2, with C5a (§19.4). |
| Claim rev 6 (3ea3f90c) answers the claim side of joint round 3 (R3-2, |
| -R3-3, R3-4) with no request from this spec; rev 7.4 encodes it (§6.4, |
| +R3-3, R3-4) with no request from this spec; rev 7.4 encodes it. Claim rev |
| +6.2 (30d572c4) answers joint round 4's claim side (R4-2 installation |
| +stamps, R4-3 the region-qualified live set, R4-4 retirement under purge) |
| +and the moves session's three questions; rev 7.5 encodes it (§5.1, §6.4, |
| §14.2.2, §19.4). |
| Until platform-1e adopts them, this spec states each as the behaviour it |
| relies on. |
| @@ -491,78 +523,111 @@ by the commit's claim CAS (§7.9), by a push's probe CAS (F7), and by a |
| dirty rollback's source probe (§7.8). Restore reads it from the same fresh |
| admission read as `holder.location` (claim C3a). |
| -**The restore contract (J2-R2-1; R3-1).** A restore reads |
| +**The restore contract (J2-R2-1; R3-1; R4-1).** A restore reads |
| `holder.location`, generation `holder.generation`, in the box's own |
| bucket, and nothing else. It never picks "the newest object under the |
| -prefix". Rev 7.3 said it "replays every replica object of that generation, |
| -current and noncurrent", which is not an algorithm (R3-1): a generation |
| -can hold the installed snapshot, later replication, a duplicate of the |
| -snapshot from a copy that completed late, and an LTX PUT that completed |
| -after a later one. **The algorithm:** |
| - |
| -1. **The snapshot is immutable and named.** `generation.json` records the |
| - installed snapshot's `versionId` and its TXID (`snapshot_version`, |
| - `snapshot_txid`), taken from the install manifest. The restore opens |
| - exactly that version. A second `base` version under the generation (a |
| - late copy) is never read. |
| -2. **Continuation is by TXID.** Every LTX object in the generation carries |
| - its transaction range `[min, max]` in its key, as litestream writes |
| - them. The restore takes, in TXID order, the objects whose `min` is |
| - `snapshot_txid + 1` and then each next contiguous range, opening each |
| - by the `versionId` the listing returned (noncurrent versions included, |
| - so a key a later PUT overwrote still contributes its version). |
| -3. **Duplicates and older checkpoints are skipped.** A version whose |
| - range the chain already covers is either a duplicate (identical |
| - content, by its checksum) or an older checkpoint of the same |
| - transactions; either way it adds nothing and is skipped. |
| -4. **A conflict refuses.** A version whose range overlaps the chain but |
| - whose content differs from what the chain holds at those TXIDs is a |
| - conflict: the restore stops with a member-safe reason and pages. It |
| - never guesses which history is right; under this spec no such |
| - version can exist within one generation, because one executor writes |
| - a generation (INV-1) and every other write lands in its own generation. |
| -5. **A gap ends the chain.** The restore stops at the first missing TXID |
| - and reports the gap. Litestream does not advance past a failed upload, |
| - so a gap within a generation means a replica object was lost, which |
| - this spec treats as a lost checkpoint (INV-2's RPO line, §14.2.3). |
| +prefix". Rev 7.4's algorithm required each next LTX object to start at |
| +`current TXID + 1`, which is not how LTX works (R4-1): compaction merges |
| +files into one covering `[Min of first, Max of last]` whose page images |
| +are the state at `Max`, deletes the inputs, and so leaves overlapping |
| +ranges and no individual intermediate objects. A snapshot ending at 100 |
| +with one current continuation `[91, 110]` is a healthy replica; rev 7.4 |
| +would have refused it, or restored through 100 and let a move commit |
| +without transactions 101 to 110. Rev 7.5 takes **one implementable |
| +contract: the sealed transfer is a verified final snapshot, and the |
| +restore is litestream's own planner over that snapshot and the |
| +generation's later files**, exactly as `litestream` v0.5.14 and |
| +`superfly/ltx` v0.5.1, the versions this repository pins, behave. |
| + |
| +1. **The sealed transfer is a snapshot.** At the cut, after the final |
| + checkpoint, the source writes a snapshot of the stopped database: one |
| + LTX file at litestream's snapshot level (9), `MinTXID = 1`, `MaxTXID = |
| + N`, every page, with its `PostApplyChecksum`. It seals that object by |
| + key and `versionId` (`seal.db`, §7.4). Nothing else of the replica is |
| + transferred: not the level-0 and level-1 files, which the snapshot |
| + subsumes. `N` is the **required final TXID** of the transfer. |
| +2. **The install copies the snapshot into the generation**, by |
| + `versionId`, to `<location>g-<move id>/ltx/9/<1>-<N>.ltx` (litestream's |
| + layout under the generation prefix), verifying its size and its |
| + trailer checksum, and records in the install manifest the key, the |
| + `versionId` the copy created, `N` and the checksum (§7.6 `copying`). |
| +3. **`generation.json` names the snapshot**: `snapshot_key`, |
| + `snapshot_version`, `snapshot_txid = N`, `snapshot_checksum`. These |
| + are the snapshot identity; a second `base` version under the |
| + generation, from a copy that completed late, is never read. |
| +4. **The restore is litestream's planner** (`replica.go` |
| + `CalcRestorePlan`), with one substitution: the snapshot it starts from |
| + is the one `generation.json` names, opened by `versionId`, not "the |
| + latest snapshot under the prefix". From `current = N` it takes, among |
| + the generation's current files at every level, the file with |
| + `MinTXID ≤ current + 1` and `MaxTXID > current` (overlap allowed: |
| + `ltx.IsContiguous`), preferring the highest `MaxTXID`, then the lowest |
| + `MinTXID`, then the higher level, applies it, sets `current = |
| + MaxTXID`, and repeats until nothing extends the range. A compacted |
| + `[91, 110]` over a snapshot at 100 is taken: its pages are the state |
| + at 110, so applying them over the state at 100 is exact. Files are |
| + taken from the current listing, as litestream lists them; nothing |
| + reads noncurrent LTX versions, because compaction's deletes make the |
| + superseded files noncurrent on purpose. |
| +5. **Fail closed.** The restore refuses, with a member-safe reason and a |
| + page, and never produces a database, when: the snapshot version is |
| + missing or its `MaxTXID` is not `snapshot_txid`; a current file starts |
| + beyond `current + 1` once nothing extends the range (litestream's |
| + "non-contiguous ltx files": a gap); or the database checksum after the |
| + last applied file is not that file's `PostApplyChecksum`. "Compare |
| + content at those TXIDs" is gone: compacted objects do not retain |
| + intermediate states, and the checksum is the validation LTX provides. |
| + A restore that falls short of `snapshot_txid` is a refusal, not a |
| + result. |
| +6. **What a gap means.** Litestream does not advance past a failed |
| + upload, and a timed-out upload is retried before the next range, so a |
| + gap in a generation's current listing is a lost or not-yet-landed |
| + object, never a healthy replica. The refusal holds until the object |
| + lands, or pages an operator. |
| So: |
| - a request that completes late can only add an object to **its own** |
| generation. A late install copy repeats the sealed snapshot (another |
| - `base` version, never opened); a late LTX PUT from an old executor lands |
| - in a generation the claim no longer names. Neither is ever restored |
| - into the current state; |
| + version, never opened); a late LTX PUT from an old executor lands in a |
| + generation the claim no longer names. Neither is ever restored into the |
| + current state; |
| - the claim moves to a new generation only after that generation is fully |
| installed and probed, so a restore never meets a half-written one; |
| - what lands in a dead generation after its retirement is residue, not |
| data: the straggler sweep (§6.4) retires it. |
| **Against litestream.** `lease.Restore` (`internal/lease/lease.go:404`) |
| -delegates to litestream, whose S3 client opens LTX keys without a |
| -`VersionId` and plans a transaction range from the keys it lists. Phase 1 |
| -needs two changes there: the snapshot opened by `versionId`, and the |
| -listing taken with versions so an overwritten key's older version is |
| -still a candidate for step 2. Both are implementation work in the restore |
| -plan (§15 step 8 verifies the S3 behaviour they rely on; rows 2, 3 and |
| -10 of claim §13). Until then a restore from a generation with a reused |
| -LTX key could miss the version a late PUT overwrote. |
| - |
| -**In the model** (§14.2.2) a history is an ordered list of transaction |
| -ids and every LTX object carries the whole history up to its checkpoint, |
| -so step 2 reads "a version continues the chain if its history extends |
| -it", step 3 "is a prefix of it", and step 4 "is neither". Rev 7.3's model |
| -took the union of every version's writes, which no restore can implement |
| -and which hid any conflict; the model drops the contract |
| -(`restore-by-generation`: the newest object under the location) and loses |
| -acknowledged writes (§14.2.5). |
| +delegates to `Replica.Restore`, whose planner lists each level without |
| +versions and opens LTX files by key. Phase 1 needs two changes there: the |
| +snapshot opened by the `versionId` `generation.json` names, and the plan |
| +rooted at that snapshot rather than the latest one listed. The rest of |
| +the planner, overlap rule, preference order and the non-contiguous error |
| +included, is used as it is. The repository's compaction ladder |
| +(`lease.go:55`: levels 0 to 3) is what produces the overlapping ranges |
| +step 4 handles. §15 step 8 verifies the S3 behaviour (claim §13 rows 2, |
| +3 and 10). |
| + |
| +**In the model** (§14.2.2) a database is a map of keys to values; |
| +transactions insert, update and delete; an LTX file carries its range, |
| +the final image of every key it touches and the checksum after `Max`; |
| +compaction merges three level-0 files into a level-1 file and deletes the |
| +inputs; a snapshot is the file `[1, TX]` with every page. The restore is |
| +step 4 and step 5 as written. The model drops the contract |
| +(`restore-by-generation`: the newest object under the location) and |
| +loses acknowledged writes; it drops the overlap rule (`restore-overlap`: |
| +rev 7.4's `min == current + 1`) and loses a compacted continuation; it |
| +drops the gap refusal (`restore-gap-closed`) and wakes a host without |
| +durable transactions (§14.2.5). Rev 7.4's model carried a full |
| +transaction history in every object, which no LTX file has, and so could |
| +not show any of this (R4-1). |
| **Markers.** A generation is named in three markers. None is a record |
| field. |
| | marker | where | written | content | |
| |---|---|---|---| |
| -| `generation.json` | `<location>generation.json`, beside `lock.json` | **last**, after the install manifest is complete and verified (§7.6); **conditionally**: If-Match the marker it supersedes, or If-None-Match when there is none (model 29) | `{"op","incarnation","host","box","installed_at","manifest_sha256","snapshot_version","snapshot_txid"}`: the last two name the installed snapshot's `versionId` and TXID, which the restore starts from (R3-1) | |
| +| `generation.json` | `<location>generation.json`, beside `lock.json` | **last**, after the install manifest is complete and verified (§7.6); **conditionally**: If-Match the marker it supersedes, or If-None-Match when there is none (model 29) | `{"op","incarnation","host","box","installed_at","manifest_sha256","snapshot_key","snapshot_version","snapshot_txid","snapshot_checksum"}`: the snapshot identity the restore starts from (R3-1, R4-1) | |
| | `<dbPath>.gen` | beside the local database | after a restore completes | `{"op","incarnation","location"}` | |
| | `.files-gen` | inside the files directory | **last**, after files, `<host>.env`, timers and the carried registry columns are all durable (§7.6) | `{"op","incarnation"}` | |
| @@ -581,6 +646,25 @@ content digest, and from F4's definition of the files directory (§6.1). |
| Absent everywhere means origin. The claim's `gen` is a per-write version |
| (claim spec §2), not a generation, so it never appears in a marker. |
| +**The authoritative region (claim rev 6.2 §6.2a, R4-3).** The live set is |
| +`{authoritative region, holder.location, holder.generation}` plus the |
| +versions this spec names for the generation (the snapshot version in |
| +`generation.json`, noncurrent included). Objects of the generation are |
| +live only in the authoritative region's bucket; a matching key in any |
| +other region is residue, and the sweep parks it. The authoritative region |
| +is the region of the holder's current installation: |
| + |
| +| claim state | authoritative region for `holder.generation` | |
| +|---|---| |
| +| `serving`, `pushing` | `op.exec`'s region | |
| +| `moving` (before the flip), `reverting` | the source's region | |
| +| `landing` (after the flip, before the commit) | the source's region: `holder.generation` is still the pre-move generation, and the target's new generation is not yet named | |
| +| `landing` after the commit, until the claim releases | the target's region | |
| +| `restoring`, clean | the source's region | |
| +| `restoring`, dirty, until the source's probe names the return generation | the source's region | |
| +| `restoring`, dirty, after the source's probe | the source's region, for the return generation | |
| +| `creating`, `deleting`, `parking`, `purging`, free | none: nothing is live | |
| + |
| **The start check (INV-4, J2-R2-8).** Before every spawn (`Start`, `Seed`, |
| `ProbeWake`), the activator requires: |
| @@ -807,23 +891,32 @@ Phase 1 uses the claim spec's park (§6.2 there) and local retirement |
| 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. **The marker is scoped to an installation** (claim rev 6 §6.4, |
| - R3-3): `{incarnation, op id of the installation being retired}`. It |
| - refuses a writer carrying that incarnation and an operation at or |
| - before the retired one. A later **new installation** of the same |
| - incarnation on this box, a move back to a region the instance left, |
| - replaces the marker under the guard, in the landing's `checking` phase, |
| - conditional on a fresh claim read showing the new operation current and |
| - this box its target; the marker then names the new op, and the retired |
| - installation's workers, carrying the old op, stay refused. Every |
| - host-guard writer (config sync, release adoption) records the operation |
| - it validated under when it selects a row, and the guard compares that |
| - with the marker. Rev 7.3's marker fenced the whole incarnation, and the |
| - model's move back never passed its probe: it timed out and rolled back, |
| - which a green sweep did not distinguish from success. The model now |
| - requires a successful UK→US→UK round trip as an unmutated witness |
| - (§14.2.5, `fence-by-installation`). How operations are ordered in |
| - production is a question for the claim spec (§19.4). |
| + refused. **Installation stamps** (claim rev 6.2 §6.4, R3-3, R4-2): an |
| + installation's stamp is the claim `gen` captured at the CAS that made it |
| + current on this box (a create's serving CAS, a landing's flip), kept |
| + for as long as the installation exists, serving included. Each box |
| + keeps, under the guard, a record per host incarnation: |
| + `{active_stamp | none, retired_stamps}`. Every host-state writer |
| + (config sync, release adoption, the activator's admission) carries |
| + `{incarnation, stamp}` from the record it read under the guard when it |
| + selected its work, and is admitted only if its stamp **equals** the |
| + active stamp; a lower stamp, a retired stamp, or any stamp while the |
| + active stamp is `none` is refused. Retirement moves the active stamp |
| + into the retired set and sets `none`. A new installation of the same |
| + incarnation, a move back to a region the instance left, sets the active |
| + stamp to its own captured stamp in the landing's `checking` phase, under |
| + the guard, after a fresh claim read shows its operation current and |
| + this box the landing participant. Op ids are opaque and never compared |
| + (claim rev 6.2 Q1). Rev 7.4's marker compared operations by an order |
| + the production system does not have and admitted its own installation |
| + only by a `- 1` in the model (R4-2); rev 7.3's marker fenced the whole |
| + incarnation, and the move back never passed its probe. The model keeps |
| + the stamp record exactly as claim rev 6.2 states it, requires the |
| + UK→US→UK round trip to serve on UK again unmutated, records a landing |
| + probe refused by its own installation record as a violation, and shows |
| + a stale config pass admitted across a retire-and-reinstall only when |
| + the stamp check and the host guard's fresh check are both gone |
| + (§14.2.5, `fence-by-installation`, `fence-stamp`). |
| No park destination is an ancestor of a live prefix (codex 8). **A park is |
| its own journaled phase**, complete before any install into the vacated |
| @@ -846,11 +939,23 @@ error; a child prefix of 64 hex characters is an incarnation location, |
| residue unless it is `holder.location`; a level directory directly under |
| the host prefix is the legacy location, residue unless `holder.location` |
| is the host prefix itself; and within `holder.location`, every generation |
| -other than `holder.generation` is residue. The version `generation.json` |
| -names as the snapshot (§5.1) is in the live set and is never touched. The |
| -model keeps the real cap and shows a replication PUT of an incarnation 33 |
| -recreates old landing after the list forgot it (`sweep-namespace`, |
| -§14.2.5). Being an obligation, it |
| +other than `holder.generation` is residue. **The live set is |
| +region-qualified** (claim rev 6.2 §6.2a, R4-3): `{authoritative region, |
| +holder.location, holder.generation}` plus the versions §5.1 names for the |
| +generation, the snapshot version `generation.json` names included, is |
| +protected in the authoritative region's bucket only (the table in §5.1); |
| +a matching key in any other region is residue and is parked, so a |
| +replica PUT for a legacy host serving in UK that lands in US after US's |
| +retirement is parked, not exempted. Claim §6.2a owns the sweep-side rule: |
| +the sweep never parks or deletes an object version in the live set, never |
| +touches the version `generation.json` names, never treats the marker as |
| +residue, reads the live set under the same fresh claim observation it |
| +uses for everything else, and touches nothing in a location whose marker |
| +disagrees with `holder.generation`, raising an alert instead. The model |
| +keeps the real cap and shows a replication PUT of an incarnation 33 |
| +recreates old landing after the list forgot it (`sweep-namespace`), and |
| +an old US PUT for a legacy host serving in UK parked only when the live |
| +set is region-qualified (`live-set-region`, §14.2.5). Being an obligation, it |
| holds off any new operation over that region until it acknowledges (CC11), |
| and every delete names a snapshot version, so nothing written later is |
| touched. |
| @@ -1159,11 +1264,11 @@ journaled in `<DataDir>/.moves/<host>/<move id>/` (F12). |
| |---|---|---|---| |
| | `checking` | read `stopped-source.json`; require the claim's `op.kind = landing` with this move id and execution holder = this box; require this box in `touched.boxes` (CAS-add before any local write); read `move.json` and require its incarnation; acquire the lease on `instances/<host>/` in its own bucket (claim spec C3) and hold it through `published` | | replayable | |
| | `parking` | bucket-park whatever is at `holder.location` in its own bucket into `superseded/<move id>/<host>/` (an earlier stay of this incarnation) | the park manifest exists **and** its deletion set is deleted | replayable by the claim's rule (manifest present: replay the deletion set) | |
| -| `copying` | per-key copy of `replica/`, by the sealed `versionId`s, into this move's own generation, `<location>g-<move id>/`, verifying each size; then write the install manifest `<location>g-<move id>/install.json` (every key and size) | the install manifest exists | replayable: staging is immutable after the flip, the generation is new, and the copy never deletes. A copy that completes late lands in this generation again (§5.1) | |
| -| `published` | write `<location>generation.json` with op, incarnation and the install manifest's sha256 | `generation.json` names this move | replayable | |
| +| `copying` | copy the **sealed snapshot** (`seal.db`: one level-9 LTX file `[1, N]`, by `versionId`, §5.1 step 1) into this move's own generation at `<location>g-<move id>/ltx/9/<1>-<N>.ltx`, verifying its size and trailer checksum; then write the install manifest `<location>g-<move id>/install.json` with the snapshot's key, the `versionId` the copy created, `N` and the checksum (R4-1) | the install manifest exists | replayable: staging is immutable after the flip, the generation is new, and the copy never deletes. A copy that completes late lands in this generation again as another version, which nothing reads (§5.1) | |
| +| `published` | write `<location>generation.json` with op, incarnation, the install manifest's sha256 and the snapshot identity: `snapshot_key`, `snapshot_version`, `snapshot_txid`, `snapshot_checksum` (§5.1) | `generation.json` names this move | replayable | |
| | `local` | local-park any warm database (with `<dbPath>.gen`), files directory and `<host>.env` into `.parked/superseded-<move id>/`; extract the archive's files; write `<host>.env` from the archive; `ReplaceTimers` from the snapshot (§9.4); set `Replication` and `NextDue`; remove the host's restore-verification entry (§6.3); fsync all; **then** write `.files-gen` | `.files-gen` names this op and incarnation, and the journal records each sub-step | each sub-step is idempotent and journaled; `.files-gen` is written last, so its presence vouches for all of them (codex R2-6) | |
| | `configured` | render through §9.1 and materialize through §9.2; **for a forward install (this landing, a push)** the move-bound ack's bundle etags must equal `move.inputs` | ack matches | replayable; a mismatch is a rollback ("configuration changed during the move"), and the source's recovery then runs on the new bundle (§7.7, J2-R2-5). **A recovery install (§7.8) runs this phase on current inputs and records them; it is never gated on `move.inputs`** (R2-5) | |
| -| `restored` | restore cold from `holder.location`, generation `<move id>` (the restore contract, §5.1), write `<dbPath>.gen`, `CheckDB` (`backend_exec.go:313`), record `landing.installed_db_sha256` | | replayable until `probing` | |
| +| `restored` | restore cold from `holder.location`, generation `<move id>` (the restore contract, §5.1: the planner from the named snapshot, which at install is the snapshot alone and must reach `snapshot_txid`, or the phase fails and rolls back), write `<dbPath>.gen`, `CheckDB` (`backend_exec.go:313`), record `landing.installed_db_sha256` | | replayable until `probing` | |
| | `probing` | the probe (§7.7) | | **never replays an earlier phase** (INV-7); a crash restarts only the probe | |
| | `probed` | probe passed | | | |
| @@ -1228,7 +1333,7 @@ held** (§9.6, §19 K9): the target row is not retired by the region change |
| |---|---| |
| | `releasing` | row `releasing` with `hold_op = rollback id`; `ReconstructStopEvidence` | |
| | `released` | digests after a final checkpoint: database sha256, files plaintext, timers. **Clean** when all three equal what was installed (`landing.installed_db_sha256`, `source.files.plain`, `source.timers_sha256`) or `landing_phase` never reached `probing`; otherwise **dirty**. Write `stopped-target.json`; CAS the claim's execution holder to `none` | |
| -| `returning` (dirty only) | the handoff back: mirror its `instances/<host>` into the source bucket's `moves/<host>/<rollback id>/replica/`, seal and publish its files archive and timers snapshot under `files/` and `timers/`, write `move.json` | |
| +| `returning` (dirty only) | the handoff back: write a snapshot of its stopped database (one level-9 LTX file `[1, N']`, §5.1 step 1) into the source bucket's `moves/<host>/<rollback id>/db`, seal it by `versionId`, publish its files archive and timers snapshot under `files/` and `timers/`, write `move.json` | |
| | `returned` (dirty only) | every artifact published | |
| | `cleaning` | only after the source records `recovered_at` (below): **bucket-park its `instances/<host>/` and its staging `moves/<host>/<move id>/` into `superseded/<move id>/<host>/`**, local-park its database, `<dbPath>.gen`, files and `<host>.env`, delete its row (conditional on `hold_op = rollback id`), clear its timers rows | |
| | `cleaned` | CAS `rollback.target_parked_at` | |
| @@ -1873,6 +1978,75 @@ only behaviour-affecting map iteration. |
| real console cannot see; the release check still omits timers and |
| unreplicated local writes; same-operation workers still share a lease |
| identity; the timing scale is unchanged. |
| + |
| +**Rev 7.5: joint round 4 and claim rev 6.2.** |
| + |
| +- **The database and its replica have LTX semantics (R4-1, |
| + `ltx_test.go`).** A database is a map of keys to values with a lineage |
| + TXID; a transaction inserts a key named by its write, or (every fifth |
| + write) updates the newest live key, or (every fifth but one) deletes |
| + the oldest, so a restore's fidelity depends on final page images, not |
| + on a list of ids. A replica object is an LTX file: a level (0, 1, or 9 |
| + for a snapshot), a transaction range, the final image of every key it |
| + touches (0 is a delete), and the database checksum after `Max`. The |
| + oracle alone knows which acknowledged writes a file covers. The |
| + serving process ships its pending transactions as one level-0 file, |
| + retries the same range widened after a timeout, and compacts three |
| + current level-0 files into one level-1 file before deleting the inputs |
| + (as `ltx.Compactor` and litestream's retention do; the versions stay |
| + noncurrent, which the park's version-exact snapshot still sees). The |
| + sealed transfer and the dirty return are snapshots of the stopped |
| + database. **Restore is litestream's planner** (§5.1 step 4) from the |
| + snapshot `generation.json` names, with the gap refusal and the |
| + checksum check (step 5). The oracle replays the incarnation's |
| + acknowledged transactions in order into the state a restore must equal: |
| + at every release and at the end a cold restore of the live generation |
| + must reach every durable write's TXID and equal that state (INV-2, |
| + cold); the serving box's own database must equal the replay of every |
| + acknowledged write, a write that never reached a database counting as |
| + the insert the application believed it made (INV-2, local). Rev 7.4's |
| + objects carried the full transaction history and could show neither a |
| + compacted continuation nor a gap. |
| +- **Installation stamps (R4-2, claim rev 6.2 §6.4).** Each box keeps |
| + `{active_stamp | none, retired_stamps}` per incarnation. A box's claim |
| + read carries the active stamp at the read; admission compares it with |
| + the active stamp now, so a stale worker's read from before a |
| + retirement is refused. Config-sync and adoption passes record the stamp |
| + they selected under and the host guard admits them only at the active |
| + stamp. A create's installation stamp is the gen that made it serving |
| + here; a landing's is the flip CAS's gen (`op.FlipGen`), set in |
| + `checking` under the guard on a fresh read. Retirement (local park, |
| + quarantine) moves the active stamp into the retired set. The op counter |
| + ordering, the `- 1`, and "an empty serving op means now" are gone. The |
| + oracle records a landing probe refused by its own installation record |
| + (INV-3, the R3-3 fault) and a host-guard write by a pass whose stamp is |
| + not the active one (INV-6). |
| +- **The live set is region-qualified (R4-3, claim rev 6.2 §6.2a).** A |
| + generation's files and marker are live only in the authoritative |
| + region's bucket; `deadGeneration` returns residue for a matching key in |
| + any other region. |
| +- **Retirement under purge (R4-4, claim rev 6.2 §5.3 step 4).** Under |
| + `deleting` and `purging` alike, the record version is recorded in |
| + `op.intent` first (`RecVer`), the record is deleted only if its version |
| + matches, and the CAS to `parking` is legal only from the same kind, |
| + with the record authoritatively gone, and, for a purge, with the purge |
| + journal at `torn-down` or later. A purged host never returns to |
| + `serving`. |
| +- **Orphan cleanup with no claim object** is explicit (claim rev 6.2 |
| + §5.1 step 5, Q2): a record with no claim is a legacy host, adopted |
| + (§9), never an orphan. |
| +- **The random generator (R4 §3).** Three additions, each drawn from the |
| + seed's RNG in half the seeds: a **marker-hold** schedule holds a |
| + `generation.json` publication until a newer marker is current, which |
| + is model 29's interleaving; a **targeted freeze** freezes the landing |
| + target in its probe or settle window and keeps it frozen until the |
| + source runs again, which is model 5's; and a **retention-boundary |
| + fixture** starts a seed with `retired_incarnations` at its cap and a |
| + replication object of an incarnation the list forgot, as a straggler. |
| + Two oracles make a rolled-back move an unaccepted terminal where it |
| + should be: the probe-fence oracle above, and the round-trip witness. |
| + These reshuffle every seed against rev 7.4; the witnesses are the |
| + stable evidence. |
| paginated listings, separate service, wall and monotonic clocks, |
| enrollment and console and updater versions, spawn separate from |
| readiness, and same-operation lease identities. |
| @@ -1974,7 +2148,28 @@ adds and one it makes stricter: |
| app is idle. Writes past the last completed checkpoint are lost by |
| design and declared so; every durable write must come back. |
| -**Rev 7.4, on the final model (claim rev 6, the restore chain, the |
| +**Rev 7.5, on the final model (claim rev 6.2: installation stamps, the |
| +region-qualified live set, retirement under purge; LTX files with |
| +compaction and litestream's restore planner; the marker-hold and |
| +targeted-freeze schedules and the retention fixture): 30,000 seeds, zero |
| +violations; 100,000 seeds, zero violations** (4 m 46 s and 15 m 03 s: |
| +page images and checksums cost about nine times rev 7.4's run time). |
| +Across the 100,000 seeds: 155,367 completed operations, among them 1,851 |
| +completed moves, 1,890 clean and 88 dirty rollbacks, 377 pre-flip |
| +reverts, 315 declared losses, 30,228 pushes, 89,170 delete-park-free |
| +cycles and 1,412 lost disks; 12,568 LTX compactions; 38,599 requests |
| +completed after `H` (the held marker publications among them); 33,549 |
| +staging signatures were refused as stale; 30,730 creates restarted on a |
| +412; 121 targets were stopped mid-return. No restore was refused for a |
| +gap or a checksum mismatch with every rule in place. Rev 7.5's first |
| +baseline run found a model fault the oracle was built for: after a cold |
| +restore the model's replicator kept position 0 and shipped a `[1, TX]` |
| +file carrying only the new pages; the planner preferred it, the checksum |
| +check refused the restore, and the oracle reported the durable writes |
| +lost (2 / 30,000, 5 / 100,000). The restored database now replicates from |
| +the TXID it reached, as litestream's `-txid` sidecar has it. |
| + |
| +**Rev 7.4, on claim rev 6 (the restore chain, the |
| installation-scoped fence, the ordered orphan cleanup, the namespace |
| sweep, public delivery): 30,000 seeds, zero violations; 100,000 seeds, |
| zero violations** (42 s and 100 s). Across the 100,000 seeds: 165,084 |
| @@ -2081,169 +2276,172 @@ Each was a failing seed first, then a rule; each rule's mutation is in |
| #### 14.2.5 Mutation results |
| Every rule was dropped in turn, with the one prebuilt binary of §14.2.1, |
| -on the final rev 7.4 model, with the strict classification, **in two |
| +on the final rev 7.5 model, with the strict classification, **in two |
| ways**: the **random sweep**, 3,000 seeds each, 30,000 for every rule not |
| caught at 3,000, 100,000 for every rule not caught at 30,000, 30,000 for |
| each combination and 100,000 for the rarest; and the rule's **witness |
| scenarios** (§14.2.2, `witness_test.go`) under the same mutation, for the |
| -25 rules that have them. A rule is caught if either catches it; |
| +29 rules that have them. A rule is caught if either catches it; |
| `mutate.sh` reports the two separately, and this section does too. The |
| -model has **71 rules**: rev 7.3's 68 plus `fence-by-installation`, |
| -`orphan-order` and `sweep-namespace` (claim rev 6). No run was an ERROR. |
| -Seed numbers reproduce any sweep line with `HOSTLIFE_SEED`; |
| -`HOSTLIFE_WITNESS=<rule>` replays a witness with its trace. Rev 7.3's |
| -counts are in the rev 7.3 spec (dc7f2a63); the orphan plans reshuffled |
| -the seeds (§14.2.3), so sweep counts moved by a few seeds while every |
| -witness verdict is unchanged. |
| +model has **75 rules**: rev 7.4's 71 plus `fence-stamp` (claim rev 6.2 |
| +§6.4), `live-set-region` (claim rev 6.2 §6.2a), `restore-overlap` and |
| +`restore-gap-closed` (§5.1). No run was an ERROR. Seed numbers reproduce |
| +any sweep line with `HOSTLIFE_SEED`; `HOSTLIFE_WITNESS=<rule>` replays a |
| +witness with its trace. Rev 7.4's counts are in the rev 7.4 spec |
| +(0e41b685); the new schedules and fixture reshuffled every seed, and the |
| +LTX model changed what many mutations break, so sweep counts moved; every |
| +witness verdict is unchanged, and three rules the sweep could not reach |
| +before are caught by it now. |
| | | rules | |
| |---|---| |
| -| caught alone by the sweep at 3,000 seeds | 48 | |
| -| caught alone by the sweep at 30,000, and by their witness | 5 (`lost-quarantine`, `markers-last`, `rollback-journal`, `seal-by-version`, `orphan-order`) | |
| -| caught alone by their witness only; the sweep misses them at 30,000 or 100,000 | 4 (`generation-conditional`, `declare-lost-compute`, `fence-by-installation`, `sweep-namespace`) | |
| -| caught only in a stated combination: the witness holds alone and under every proper subset, and violates with the partners | 11 | |
| +| caught alone by the sweep at 3,000 seeds | 53 | |
| +| caught alone by the sweep at 30,000, and by their witness | 4 (`fence-by-installation`, `orphan-order`, `rollback-journal`, `staging-quiet`) | |
| +| caught alone by the sweep at 100,000, and by its witness | 1 (`declare-lost-compute`) | |
| +| caught alone by their witness only; the sweep misses them at 30,000 | 2 (`live-set-region`, `restore-gap-closed`) | |
| +| caught only in a stated combination: the witness holds alone and under every proper subset, and violates with the partners | 12 | |
| | liveness only | 2 (`readiness-key`, `config-before-probe`) | |
| | unreachable under claim rev 5 | 1 (`readiness-quiet`) | |
| | unexercised | **0** | |
| +**What the generator changes bought (R4 §3).** `generation-conditional` |
| +was witness-only at 0 / 100,000 in rev 7.4; the marker-hold schedule |
| +makes it 13 / 3,000 and 383 / 100,000. `declare-lost-compute` was |
| +witness-only at 0 / 100,000; the targeted freeze makes it 3 / 100,000. |
| +`sweep-namespace` was witness-only at 0 / 30,000; the retention fixture |
| +makes it 88 / 3,000. `fence-by-installation` was witness-only at 0 / |
| +30,000; the probe-fence oracle makes it 2 / 30,000, because a move back |
| +that rolls back is a violation now, not a converged terminal. Two rules |
| +stay witness-only: `live-set-region` (the sweep's legacy fixture never has |
| +an old regional PUT land after discovery) and `restore-gap-closed` (no |
| +seed loses a replica object with every rule in place). |
| + |
| **The witnesses.** One scenario per rule the sweep catches rarely or only |
| -in combination, and one per rule claim rev 6 or this round added; two |
| -for `registration-check`, one per partner. "Alone" is the witness under |
| -the rule's own mutation; "with partners" under the stated combination. |
| -The gate also runs every proper subset of each combination and requires |
| -the witness to hold there, so a rule a combination names is shown |
| -necessary; each witness violates with the failure it names, not merely |
| -some invariant (R3 review). |
| +in combination, and one per rule a review round added; two for |
| +`registration-check`, one per partner. "Alone" is the witness under the |
| +rule's own mutation; "with partners" under the stated combination. The |
| +gate also runs every proper subset of each combination and requires the |
| +witness to hold there; each witness violates with the failure it names. |
| | rule | the scenario | witness alone | witness with partners | sweep alone | |
| |---|---|---|---|---| |
| -| `generation-conditional` (model 29) | push p1's first `generation.json` PUT is accepted and its response lost; the agent publishes again and p1 completes; push p2 completes; the stale PUT lands | **violated**: the marker names p1, the claim p2, and the serving box cannot start | | 0 / 100,000 | |
| -| `lost-return-park` (model 27) | the target writes in the settle window, the settle check fails, the target is stopped after its first return PUT and declared lost | **violated**: INV-10, the lane in the source region at the release | | 3 / 3,000, 32 / 30,000, 112 / 100,000 | |
| -| `declare-lost-compute` (model 5) | the target is frozen in its settle window and its execution lease expires; the VM resumes once the source has recovered | **violated**: INV-1, two boxes run the host | | 0 / 100,000 | |
| -| `watchdog-independent` (§7.1) | as above, with the agent alone paused, so the application keeps running | held: with compute fencing the operator refuses, and the paused box is never declared lost | with `declare-lost-compute`: **violated**, INV-1; a watchdog inside the paused agent never fences | 0 / 100,000; the pair 3 / 100,000 | |
| -| `lost-quarantine` (CC12) | the target is stopped while landing, declared lost, and started again with its old disk | **violated**: INV-10, a database and files for a host served elsewhere | | 0 / 3,000, 12 / 30,000 | |
| -| `markers-last` (R2-6) | the source has a registered timer; the target's agent crashes between extracting the files and writing the marker | **violated**: INV-2, the timer is lost | | 0 / 3,000, 10 / 30,000 | |
| -| `rollback-journal` (R2-3) | a dirty rollback; the source's agent crashes after its probe spawned the instance, which wrote and replicated into the return generation | **violated**: INV-2 on cold restore; the restart at `claimed` parks the return generation | | 0 / 3,000, 3 / 30,000 | |
| -| `seal-by-version` (J-N2) | a pre-cut staging round completes after the seal; the source wrote and replicated between them | **violated**: INV-2 on cold restore; the landing installs the stale round | | 0 / 3,000, 15 / 30,000 | |
| -| `staging-quiet` (J-N2) | a staging round completes after the landing's staging park acknowledged, before the release | **violated**: INV-10 at the release | | 1 / 3,000, 10 / 30,000 | |
| -| `dirty-return` (INV-2) | the target writes during its probe and is rolled back | **violated**: INV-2, the write is lost | | 2 / 3,000, 14 / 30,000 | |
| -| `commit-before-open` (INV-5; R3-5) | a public request reaches the target's edge while its probe runs; the mutation opened the row at the probe, and the edge routes on the row | **violated**: INV-5, a public request reached the landing target before the commit | | 10 / 3,000, 115 / 30,000 (rev 7.3: never alone) | |
| -| `claim-cas-opid` (C2) | a clean rollback; a second source goroutine reads the claim just before the release and stalls; the host is released, a purge claims it, the goroutine resumes | held: revalidation stops it | with `revalidate-before-effect`: **violated**, the stale release overwrites the purge and the store is torn down under a serving host | 0; the pair 1,006 / 30,000 | |
| -| `exec-admission`, `hold-op-open`, `start-check-claim-gen` (J2-R2-8) | an open from an earlier operation, paused on the source, lands after the commit while the source's row is still held; a request wakes the source | held, each, and under every pair | all three: **violated**, INV-1, the source serves beside the target | 0 each; any pair 0 / 30,000; the triple 7 / 30,000, 19 / 100,000 | |
| -| `start-check` (INV-4) | the same, dropping the whole start check | held, and under every pair | with `exec-admission` and `hold-op-open`: **violated**, INV-1 | the triple 9 / 30,000 | |
| -| `strict-spawn` (J-D3) | the source runs a main process and a descendant placed as its parent was; the move cuts | held: the whole-tree cleanup kills the escaped descendant | with `evidence-whole-tree`: **violated**, INV-1 | 0; the pair 128 / 30,000 | |
| -| `touched-incomplete` (J-N5) | a legacy host with a pre-release replica in another region is adopted, then deleted | held: discovery parked the other region at adoption | with `legacy-discovery`: **violated**, INV-10 after `free` | 0; the pair 9,092 / 30,000 | |
| -| `door-reread` (claim §6.5 O2) | a listing authorized before the delete selects after a recreate into the same location | held: with C3a the new incarnation's location differs | with `location-per-incarnation`: **violated**, a read authorized for inc 1 returns inc 2's data | 0; the pair 36 / 30,000 (8 door violations) | |
| -| `door-by-version` (claim §6.5) | a listing selects and signs before the delete; the file request opens after the recreate | held, as above | with `location-per-incarnation`: **violated**, the same; `door-reread` is not needed | 0; the pair 29 / 30,000 (2 door violations), with `door-reread` too 55 / 30,000 (27) | |
| -| `registration-check` (CC21) | the first create of a host registers and its console pauses; a purge of the app drains the expired registration, tears the store down and finishes; the creator resumes | held: the fence and the ETag condition refuse the CAS, and the 412 restarts the create on a store that exists again | with `fence-etag`: **violated**; with `purge-claim-fence`: **violated**; a host creating on a torn-down store | 0 / 30,000; the pairs 240 and 313 / 30,000 | |
| -| `fence-by-installation` (claim rev 6 §6.4, R3-3) | UK→US, then US→UK: the second landing must replace the marker UK wrote when it retired the first installation; **unmutated, the instance must serve on UK again, running** | **violated**: the move back never serves on UK (the probe is refused, the cut deadline passes, it rolls back); INV-3 at the witness's milestone | | 0 / 30,000: the sweep cannot tell a rolled-back move from a served one | |
| -| `orphan-order` (claim rev 6 §5.1 step 5, R3-2) | a cancelled create's record lands late; the recovery pass starts its cleanup and pauses after its first observation; a create of the next incarnation removes the orphan itself, claims and publishes; the paused cleanup resumes | **violated**: the cleanup, judging the new record by the stale claim, deletes the record of the incarnation the claim names | | 0 / 3,000, 2 / 30,000 | |
| -| `sweep-namespace` (claim rev 6 §6.2a, R3-4) | a replication PUT of incarnation 1 is accepted and completes after 33 delete-and-recreate cycles, by which time `retired_incarnations` no longer lists 1 | **violated**: INV-10, residue the sweep never finds | | 0 / 30,000: no seed recreates 33 times | |
| -| `recovery-current-inputs` (§7.7, §7.8; R2-5) | a bundle is published after admission; the target writes; the rollback is dirty; the source's recovery install must render current inputs | **violated**: `stuck`, the source could not recover | | 51 / 3,000, 506 / 30,000 | |
| +| `generation-conditional` (model 29) | push p1's first `generation.json` PUT is accepted and its response lost; the agent publishes again and p1 completes; push p2 completes; the stale PUT lands | **violated**: the marker names p1, the claim p2, and the serving box cannot start | | 13 / 3,000, 114 / 30,000, 383 / 100,000 | |
| +| `lost-return-park` (model 27) | the target writes in the settle window, the settle check fails, the target is stopped after its first return PUT and declared lost | **violated**: INV-10, the lane in the source region at the release | | 5 / 3,000, 35 / 30,000, 113 / 100,000 | |
| +| `declare-lost-compute` (model 5) | the target is frozen in its settle window and its execution lease expires; the VM resumes once the source has recovered | **violated**: INV-1, two boxes run the host | | 0 / 30,000, 3 / 100,000 | |
| +| `watchdog-independent` (§7.1) | as above, with the agent alone paused, so the application keeps running | held | with `declare-lost-compute`: **violated**, INV-1 | 0 / 100,000; the pair 1 / 30,000, 4 / 100,000 | |
| +| `lost-quarantine` (CC12) | the target is stopped while landing, declared lost, and started again with its old disk | **violated**: INV-10, local state for a host served elsewhere | | 3 / 3,000, 7 / 30,000 | |
| +| `markers-last` (R2-6) | the source has a registered timer; the target's agent crashes between extracting the files and writing the marker | **violated**: INV-2, the timer is lost | | 1 / 3,000, 6 / 30,000 | |
| +| `rollback-journal` (R2-3) | a dirty rollback; the source's agent crashes after its probe spawned the instance, which wrote and replicated into the return generation | **violated**: INV-2 on cold restore | | 0 / 3,000, 8 / 30,000 | |
| +| `seal-by-version` (J-N2) | a pre-cut staging round completes after the seal; the source wrote and replicated between them | **violated**: INV-2 on cold restore; the landing installs the stale snapshot | | 3 / 3,000, 19 / 30,000 | |
| +| `staging-quiet` (J-N2) | a staging round completes after the landing's staging park acknowledged, before the release | **violated**: INV-10 at the release | | 0 / 3,000, 6 / 30,000 | |
| +| `dirty-return` (INV-2) | the target writes during its probe and is rolled back | **violated**: INV-2, the write is lost | | 8 / 3,000, 58 / 30,000 | |
| +| `commit-before-open` (INV-5; R3-5) | a public request reaches the target's edge while its probe runs | **violated**: INV-5 | | 11 / 3,000, 109 / 30,000 | |
| +| `claim-cas-opid` (C2) | a stalled source goroutine with the rollback's last read resumes after a purge claimed the host | held | with `revalidate-before-effect`: **violated**, the store is torn down under a serving host | 0; the pair 1,365 / 30,000 | |
| +| `exec-admission`, `hold-op-open`, `start-check-claim-gen` (J2-R2-8) | a paused open from an earlier operation lands after the commit while the source's row is still held; a request wakes the source | held, each, and under every pair | all three: **violated**, INV-1 | 0 each; any pair 0 / 30,000; the triple 11 / 30,000, 21 / 100,000 | |
| +| `start-check` (INV-4) | the same, dropping the whole start check | held, and under every pair | with `exec-admission` and `hold-op-open`: **violated**, INV-1 | the triple 11 / 30,000 | |
| +| `strict-spawn` (J-D3) | the source runs a main process and a descendant placed as its parent was; the move cuts | held | with `evidence-whole-tree`: **violated**, INV-1 | 0; the pair 828 / 30,000 | |
| +| `touched-incomplete` (J-N5) | a legacy host with a pre-release replica in another region is adopted, then deleted | held | with `legacy-discovery`: **violated**, INV-10 after `free` | 0; the pair 7,407 / 30,000 | |
| +| `door-reread` (claim §6.5 O2) | a listing authorized before the delete selects after a recreate into the same location | held | with `location-per-incarnation`: **violated**, a read authorized for inc 1 returns inc 2's data | 0; the pair 432 / 30,000 (196 door violations) | |
| +| `door-by-version` (claim §6.5) | a listing selects and signs before the delete; the file request opens after the recreate | held | with `location-per-incarnation`: **violated**, the same; `door-reread` is not needed | 0; the pair 385 / 30,000 (60 door violations) | |
| +| `registration-check` (CC21) | the first create of a host registers and its console pauses; a purge of the app drains the expired registration, tears the store down and finishes; the creator resumes | held | with `fence-etag`: **violated**; with `purge-claim-fence`: **violated** | 0 / 30,000; the pairs 271 and 269 / 30,000 | |
| +| `fence-by-installation` (claim rev 6.2 §6.4, R3-3) | UK→US, then US→UK; **unmutated, the instance must serve on UK again, running** | **violated**: the landing probe on UK is refused by the box's own installation record (INV-3) | | 0 / 3,000, 2 / 30,000 | |
| +| `fence-stamp` (claim rev 6.2 §6.4, R4-2) | a config-sync pass selects the row under the first installation and pauses; UK→US→UK; a new bundle is published; the pass takes the guard | held: the host guard's fresh re-validation refuses its stale bundle | with `hostguard`: **violated**, INV-6, a pass of a retired installation wrote host state | 0 / 30,000; the pair 61 / 30,000, which is `hostguard`'s own 61: the sweep does not separate them, the witness does | |
| +| `orphan-order` (claim rev 6 §5.1 step 5, R3-2) | the recovery pass's cleanup pauses after its first observation; a create removes the orphan, claims and publishes; the paused cleanup resumes | **violated**: the record of the incarnation the claim names is deleted | | 0 / 3,000, 2 / 30,000 | |
| +| `sweep-namespace` (claim rev 6 §6.2a, R3-4) | a replication PUT of incarnation 1 completes after 33 recreates dropped it from `retired_incarnations` | **violated**: INV-10, residue the sweep never finds | | 88 / 3,000, 866 / 30,000 (the retention fixture) | |
| +| `live-set-region` (claim rev 6.2 §6.2a, R4-3) | a legacy host serving in UK with an earlier replica in US; discovery parks US; an old US replication PUT lands after | **violated**: INV-10, residue the US sweep exempts by key alone | | 0 / 30,000 | |
| +| `restore-overlap` (§5.1, R4-1) | a generation's snapshot ends at TXID 11; the only continuation is a compacted file `[10, 15]`; the disk is lost and the host wakes cold | **violated**: INV-2, the wake loses four acknowledged writes (rev 7.4's `min == current + 1` discards the file) | | 27 / 3,000, 186 / 30,000 | |
| +| `restore-gap-closed` (§5.1, R4-1) | the same generation with `[12, 12]` missing and `[13, 13]` current | **violated**: a restore across the gap returns a database at TXID 11 | | 0 / 30,000 | |
| +| `recovery-current-inputs` (§7.7, §7.8; R2-5) | a bundle is published after admission; the target writes; the rollback is dirty | **violated**: `stuck` | | 47 / 3,000, 488 / 30,000 | |
| **The purge fence, the ETag condition and C5a.** `purge-claim-fence` |
| -(CC21) is caught alone by the sweep: 26 / 3,000, 266 / 30,000. |
| -`fence-etag` (CC22): 18 / 3,000, 195 / 30,000. `claim-never-deleted` |
| -(C5a): 8 / 3,000, 77 / 30,000 (54 of the 77 on the main host while |
| -`creating`, 23 on the second host). `registration-check`, the creator's |
| -re-read, is not caught alone by the sweep at 3,000 or 30,000 seeds, and |
| -its witness holds alone: with the fence and the ETag condition in place a |
| -drained registration always means a changed claim ETag, so the CAS fails |
| -whether or not the creator re-reads. The re-read alone, which is what |
| -rev 7's CC21 asked for, is shown insufficient in the combination table |
| -(`purge-claim-fence` + `fence-etag`, 307 / 30,000). |
| +(CC21) is caught alone by the sweep: 29 / 3,000, 240 / 30,000. |
| +`fence-etag` (CC22): 24 / 3,000, 223 / 30,000. `claim-never-deleted` |
| +(C5a): 5 / 3,000, 46 / 30,000. `registration-check` is not caught alone |
| +by the sweep at 3,000 or 30,000 seeds, and its witness holds alone; the |
| +re-read alone is shown insufficient (`purge-claim-fence` + `fence-etag`, |
| +331 / 30,000). |
| **Rev 6's unexercised three, now caught (the biased schedule, §14.2.2).** |
| | rule | result | what the model finds | |
| |---|---|---| |
| -| `location-per-incarnation` (claim C3a) | **caught alone**: 3 / 3,000, 29 / 30,000 | an incarnation's database holds writes of the previous incarnation: a replication PUT accepted before the delete completes after the recreate into the shared location, and the new incarnation's restore replays it; the cold-restore check at a later release and the start check both see it. With the restore chain (R3-1) the same PUT can also make the chain refuse a conflicting history: the sweep reports it as `stuck` | |
| -| `door-reread` (claim §6.5 O2) | with `location-per-incarnation`: 36 / 30,000, of which 8 are door violations; witness violated | a listing authorized for the old incarnation selects, after the recreate, the new incarnation's object in the shared location, and signs it | |
| -| `door-by-version` (claim §6.5) | with `location-per-incarnation`: 29 / 30,000, of which 2 are door violations, and with `door-reread` as well 55 / 30,000 (27); witness violated with C3a alone | a handle signed before the delete opens the location's current object after the recreate | |
| +| `location-per-incarnation` (claim C3a) | **caught alone**: 31 / 3,000, 385 / 30,000 | with the shared location, the planner takes the previous incarnation's files (highest `MaxTXID` wins), and the cold restore equals neither incarnation's acknowledged writes; the C3a and door oracles see the rest | |
| +| `door-reread` (claim §6.5 O2) | with `location-per-incarnation`: 432 / 30,000, of which 196 are door violations; witness violated | a listing authorized for the old incarnation selects the new incarnation's object | |
| +| `door-by-version` (claim §6.5) | with `location-per-incarnation`: 385 / 30,000, of which 60 are door violations; witness violated with C3a alone | a handle signed before the delete opens the key's current object after the recreate | |
| -Neither door rule can be violated while C3a holds, under any schedule: a |
| -listing selects only under O1's location, and no later incarnation ever |
| -writes there. They are defence in depth behind C3a, and each is shown |
| -load-bearing once C3a is dropped, by its witness and by the sweep. |
| +The three-rule combination `door-by-version` + `door-reread` + |
| +`location-per-incarnation` that rev 7.4 also reported is **not a minimal |
| +combination** (R4 §3): each door rule's two-rule pair with C3a already |
| +fails, so the three-rule row is a wider set and inherits no claim that its |
| +proper subsets hold. It is no longer run. |
| -**Caught alone: 57 of 71** (48 by the sweep at 3,000; 5 by the sweep at |
| -30,000 and by their witness, marked †; 4 by their witness alone, marked |
| -‡, with the sweep's count beside). |
| +**Caught alone: 60 of 75** (53 by the sweep at 3,000; 4 by the sweep at |
| +30,000 and by their witness, marked †; 1 at 100,000 and by its witness, |
| +marked ††; 2 by their witness alone, marked ‡). |
| | rule | seeds failing | | rule | seeds failing | |
| |---|---|---|---|---| |
| -| `recovery-pass` | 2,501 / 3,000 | | `fresh-incarnation` | 41 / 3,000 | |
| -| `legacy-discovery` | 569 / 3,000 | | `location-per-incarnation` | 3 / 3,000 | |
| -| `park-by-version` | 2,063 / 3,000 | | `park-all-versions` | 677 / 3,000 | |
| -| `park-skip-lock` | 2,869 / 3,000 | | `retirement-ids` | 283 / 3,000 | |
| -| `quiet-before-ack` | 184 / 3,000 | | `straggler-sweep` | 133 / 3,000 | |
| -| `sweep-staging` | 42 / 3,000 | | `restore-by-generation` | 2 / 3,000 | |
| -| `hostguard` | 5 / 3,000 | | `purge-reserve-first` | 2,279 / 3,000 | |
| -| `purge-adopts` | 10 / 3,000 | | `purge-journal` | 1,969 / 3,000 | |
| -| `admission-registration` | 36 / 3,000 | | `registration-recovery` | 1,957 / 3,000 | |
| -| `purge-claim-fence` | 26 / 3,000 | | `fence-etag` | 18 / 3,000 | |
| -| `claim-never-deleted` | 8 / 3,000 | | `cancelled-creates` | 373 / 3,000 | |
| -| `orphan-no-deadline` | 60 / 3,000 | | `orphan-order` † | 2 / 30,000 | |
| -| `evidence-whole-tree` | 2 / 3,000 | | `evidence-from-state` | 2,778 / 3,000 | |
| -| `floor-boot-recovery` | 2,538 / 3,000 | | `participant-rows` | 4 / 3,000 | |
| -| `revalidate-before-effect` | 18 / 3,000 | | `sign-before-safeuntil` | 14 / 3,000 | |
| -| `sign-at-check` | 11 / 3,000 | | `sdk-no-resign` | 11 / 3,000 | |
| -| `lost-quarantine` † | 12 / 30,000 | | `cut-deadline` | 10 / 3,000 | |
| -| `revert-journal` | 17 / 3,000 | | `rollback-journal` † | 3 / 30,000 | |
| -| `dirty-return` | 2 / 3,000 | | `commit-before-open` | 10 / 3,000 | |
| -| `lost-return-park` | 3 / 3,000 | | `target-park` | 42 / 3,000 | |
| -| `abort-target-owner` | 1,061 / 3,000 | | `timer-freeze` | 6 / 3,000 | |
| -| `timer-canonical` | 3 / 3,000 | | `markers-last` † | 10 / 30,000 | |
| -| `source-readiness` | 7 / 3,000 | | `seal-by-version` † | 15 / 30,000 | |
| -| `staging-quiet` | 1 / 3,000 | | `fresh-admission` | 16 / 3,000 | |
| -| `stale-hold-release` | 2 / 3,000 | | `obligations-block-ops` | 22 / 3,000 | |
| -| `claim-op-exclusive` | 157 / 3,000 | | `recovery-current-inputs` | 51 / 3,000 | |
| -| `files-carried` | 79 / 3,000 | | `generation-conditional` ‡ | witness; sweep 0 / 100,000 | |
| -| `declare-lost-compute` ‡ | witness; sweep 0 / 100,000 | | `fence-by-installation` ‡ | witness; sweep 0 / 30,000 | |
| -| `sweep-namespace` ‡ | witness; sweep 0 / 30,000 | | | | |
| - |
| -`fence-by-installation` and `sweep-namespace` are liveness and |
| -long-horizon properties the sweep cannot reach: a rolled-back move back |
| -converges like a served one, and no seed recreates a host 33 times. Their |
| -witnesses are the evidence, and the first is also the unmutated |
| -round-trip test claim rev 6 asks for. |
| - |
| -**Caught only in a combination: 11 rules.** Each partner's own count, on |
| +| `recovery-pass` | 2,465 / 3,000 | | `fresh-incarnation` | 52 / 3,000 | |
| +| `legacy-discovery` | 384 / 3,000 | | `location-per-incarnation` | 31 / 3,000 | |
| +| `park-by-version` | 2,522 / 3,000 | | `park-all-versions` | 574 / 3,000 | |
| +| `park-skip-lock` | 2,721 / 3,000 | | `retirement-ids` | 333 / 3,000 | |
| +| `quiet-before-ack` | 211 / 3,000 | | `straggler-sweep` | 781 / 3,000 | |
| +| `sweep-staging` | 51 / 3,000 | | `sweep-namespace` | 88 / 3,000 | |
| +| `restore-by-generation` | 189 / 3,000 | | `restore-overlap` | 27 / 3,000 | |
| +| `restore-gap-closed` ‡ | witness; sweep 0 / 30,000 | | `hostguard` | 9 / 3,000 | |
| +| `purge-reserve-first` | 2,167 / 3,000 | | `purge-adopts` | 7 / 3,000 | |
| +| `purge-journal` | 1,883 / 3,000 | | `admission-registration` | 40 / 3,000 | |
| +| `registration-recovery` | 2,117 / 3,000 | | `purge-claim-fence` | 29 / 3,000 | |
| +| `fence-etag` | 24 / 3,000 | | `claim-never-deleted` | 5 / 3,000 | |
| +| `cancelled-creates` | 374 / 3,000 | | `orphan-no-deadline` | 50 / 3,000 | |
| +| `orphan-order` † | 2 / 30,000 | | `evidence-whole-tree` | 67 / 3,000 | |
| +| `evidence-from-state` | 2,614 / 3,000 | | `floor-boot-recovery` | 2,519 / 3,000 | |
| +| `participant-rows` | 3 / 3,000 | | `revalidate-before-effect` | 78 / 3,000 | |
| +| `sign-before-safeuntil` | 20 / 3,000 | | `sign-at-check` | 14 / 3,000 | |
| +| `sdk-no-resign` | 14 / 3,000 | | `lost-quarantine` | 3 / 3,000 | |
| +| `declare-lost-compute` †† | 3 / 100,000 | | `cut-deadline` | 13 / 3,000 | |
| +| `revert-journal` | 13 / 3,000 | | `rollback-journal` † | 8 / 30,000 | |
| +| `dirty-return` | 8 / 3,000 | | `commit-before-open` | 11 / 3,000 | |
| +| `lost-return-park` | 5 / 3,000 | | `target-park` | 52 / 3,000 | |
| +| `abort-target-owner` | 940 / 3,000 | | `timer-freeze` | 4 / 3,000 | |
| +| `timer-canonical` | 7 / 3,000 | | `markers-last` | 1 / 3,000 | |
| +| `source-readiness` | 11 / 3,000 | | `seal-by-version` | 3 / 3,000 | |
| +| `staging-quiet` † | 6 / 30,000 | | `fresh-admission` | 16 / 3,000 | |
| +| `stale-hold-release` | 5 / 3,000 | | `obligations-block-ops` | 20 / 3,000 | |
| +| `claim-op-exclusive` | 79 / 3,000 | | `recovery-current-inputs` | 47 / 3,000 | |
| +| `files-carried` | 77 / 3,000 | | `generation-conditional` | 13 / 3,000 | |
| +| `fence-by-installation` † | 2 / 30,000 | | `live-set-region` ‡ | witness; sweep 0 / 30,000 | |
| + |
| +**Caught only in a combination: 12 rules.** Each partner's own count, on |
| the same seeds, shows what the combination adds; the witness column is |
| -the rule's witness under the combination, having held under every |
| -proper subset in the gate. |
| +the rule's witness under the combination, having held under every proper |
| +subset in the gate. |
| | combination | sweep | partners alone (sweep) | witness | what the model finds | |
| |---|---|---|---|---| |
| -| `purge-claim-fence` + `fence-etag` (**the re-read alone**, rev 7's CC21 as proposed) | 307 / 30,000 | `purge-claim-fence` 266, `fence-etag` 195, `registration-check` 0 | (the two `registration-check` rows below) | the creator pauses after its re-read; purge drains, tears down and finishes; the CAS from free succeeds: a store destroyed under a creating or serving host. The re-read is not a fence | |
| -| `registration-check` + `purge-claim-fence` | 313 / 30,000 | `purge-claim-fence` 266 | violated | the same, without the re-read's early stops | |
| -| `registration-check` + `fence-etag` | 240 / 30,000 | `fence-etag` 195 | violated | the same: the fence alone cannot refuse a creator that resumes after its release | |
| -| `exec-admission` + `hold-op-open` + `start-check-claim-gen` | 19 / 100,000 (7 / 30,000) | every pair 0 / 30,000 | violated | two writers, Astra's J2-R2-8 trace (rev 6) | |
| -| `exec-admission` + `hold-op-open` + `start-check` | 9 / 30,000 | (above) | violated | the same, dropping the whole start check | |
| -| `door-reread` + `location-per-incarnation` | 36 / 30,000 | `location-per-incarnation` 29 | violated | door reads of another incarnation's data (8) | |
| -| `door-by-version` + `location-per-incarnation` | 29 / 30,000 | `location-per-incarnation` 29 | violated | door reads of another incarnation's data (2): the sweep rarely stalls a signed handle across a recreate; the witness does it every time | |
| -| `door-by-version` + `door-reread` + `location-per-incarnation` | 55 / 30,000 | `door-reread` + `location` 36 | violated | more door violations (27 against 8) | |
| -| `claim-cas-opid` + `revalidate-before-effect` | 1,006 / 30,000 | `revalidate-before-effect` 197 | violated | a stale purge step destroys a store; the witness's stale release overwrites a purge | |
| -| `strict-spawn` + `evidence-whole-tree` | 128 / 30,000 | `evidence-whole-tree` 11 | violated | two writers | |
| -| `touched-incomplete` + `legacy-discovery` | 9,092 / 30,000 | `legacy-discovery` 6,037 | violated | a freed host's residue | |
| -| `declare-lost-compute` + `watchdog-independent` | 3 / 100,000 | `declare-lost-compute` 0 / 100,000 (its witness alone: violated), `watchdog-independent` 0 / 100,000 | violated | two writers after a paused or frozen target resumes | |
| - |
| -`commit-before-open` + `dirty-return` (124 / 30,000) is no longer a |
| -combination the model needs: `commit-before-open` is caught alone by |
| -INV-5 (115 / 30,000 and its witness), and the pair's extra seeds are |
| -`dirty-return`'s. |
| +| `purge-claim-fence` + `fence-etag` (**the re-read alone**, rev 7's CC21 as proposed) | 331 / 30,000 | `purge-claim-fence` 240, `fence-etag` 223, `registration-check` 0 | (the two `registration-check` rows below) | a store destroyed under a creating or serving host | |
| +| `registration-check` + `purge-claim-fence` | 269 / 30,000 | `purge-claim-fence` 240 | violated | the same | |
| +| `registration-check` + `fence-etag` | 271 / 30,000 | `fence-etag` 223 | violated | the same | |
| +| `exec-admission` + `hold-op-open` + `start-check-claim-gen` | 21 / 100,000 (11 / 30,000) | every pair 0 / 30,000 | violated | two writers, Astra's J2-R2-8 trace | |
| +| `exec-admission` + `hold-op-open` + `start-check` | 11 / 30,000 | (above) | violated | the same, dropping the whole start check | |
| +| `door-reread` + `location-per-incarnation` | 432 / 30,000 | `location-per-incarnation` 385 | violated | door reads of another incarnation's data (196) | |
| +| `door-by-version` + `location-per-incarnation` | 385 / 30,000 | `location-per-incarnation` 385 | violated | door reads of another incarnation's data (60); the sweep count is the partner's, the witness is the evidence | |
| +| `claim-cas-opid` + `revalidate-before-effect` | 1,365 / 30,000 | `revalidate-before-effect` 610 | violated | a stale step destroys a store | |
| +| `strict-spawn` + `evidence-whole-tree` | 828 / 30,000 | `evidence-whole-tree` 504 | violated | two writers | |
| +| `touched-incomplete` + `legacy-discovery` | 7,407 / 30,000 | `legacy-discovery` 3,814 | violated | a freed host's residue | |
| +| `declare-lost-compute` + `watchdog-independent` | 1 / 30,000, 4 / 100,000 | `declare-lost-compute` 3 / 100,000, `watchdog-independent` 0 / 100,000 | violated | two writers after a paused or frozen target resumes | |
| +| `fence-stamp` + `hostguard` | 61 / 30,000 | `hostguard` 61, `fence-stamp` 0 | violated | a stale pass writes host state for a retired installation (INV-6); in the sweep every seed is `hostguard`'s, and the witness is what shows the stamp load-bearing | |
| **Liveness only: 2.** `readiness-key` (T2) and `config-before-probe`. |
| They have no witness: neither can violate an invariant. |
| **Unreachable: `readiness-quiet` (T12).** Every retirement acknowledges |
| -only after its own horizon, and CC11 holds; the biased schedules do not |
| -change that, because they delay only data writes, never an |
| -acknowledgement. No witness: there is no interleaving to force. |
| +only after its own horizon, and CC11 holds. No witness: there is no |
| +interleaving to force. |
| **Seeds:** 300 in the gate, with every witness under every subset (about |
| a second); the 30,000- and 100,000-seed baselines, this sweep and the |
| -witnesses are the evidence for rev 7.4. |
| +witnesses are the evidence for rev 7.5. |
| #### 14.2.6 What the model does not prove |
| @@ -2267,22 +2465,24 @@ witnesses are the evidence for rev 7.4. |
| budget (320), the reverse of production (over 15 minutes against 120 s). |
| Results about orderings carry over; results about which wait fits inside |
| which window do not (J2 §2). |
| -- **Restore fidelity (R3-1).** The model's transactions are append-only |
| - ids; a history is an ordered list, and a conflict is a history that is |
| - neither an extension nor a prefix of the chain. Updates, deletes and |
| - the content of a transaction are not modelled, so the chain proves |
| - that no two histories are ever merged and that every durable write |
| - stays reachable, which is what the proofs use; it does not validate |
| - litestream's LTX planning against updates and deletes. That is |
| - implementation work against the real restore (§5.1, §15 step 8). |
| -- **Observation boundaries not taken in rev 7.4** (R3 review): the |
| - target's cleanup is queued at its release, not after the source's |
| +- **Restore fidelity (R3-1, R4-1).** The model's LTX files have ranges, |
| + final page images, checksums and compaction, and its restore is |
| + litestream's planner (§5.1), so a compacted continuation, a gap and a |
| + stale image are all representable and each has a witness. What the |
| + model's pages are not is bytes: a key's image is one integer, a |
| + checksum is a digest of the whole state, and page size, WAL salts and |
| + the lock page are absent. The claim it supports is that the planner as |
| + specified restores exactly the acknowledged transactions up to the |
| + TXID it reaches and refuses otherwise; byte-level LTX decoding is |
| + validated against the real restore (§15 step 8). |
| +- **Observation boundaries not taken in rev 7.4 or 7.5** (R3 review): |
| + the target's cleanup is queued at its release, not after the source's |
| `recovered_at` (§7.8); a create refuses while a record PUT is pending |
| on the server, which a real console cannot see; the release check runs |
| the cold-restore oracle only, not the local and timer acknowledgement |
| check, so a later delete could erase evidence of an earlier loss; two |
| - workers of one operation share a lease identity; the local-exclusion |
| - passes carry an operation sequence now but no release version. |
| + workers of one operation share a lease identity; the passes carry an |
| + installation stamp now but no release version. |
| - **Adversarial scheduling is partial.** Paused opens, door reads, |
| staging signers and an earlier incarnation's replica writes wake at the |
| worst moment the model knows of; everything else wakes at random. The |
| @@ -2531,13 +2731,27 @@ model encodes that text (§14.2.2) and gives C5a its own mutation |
| spec.** Rev 6 answers R3-2 (orphan cleanup in a fixed order: record, then |
| the claim fresh, then `DeleteIfMatch`), R3-3 (the installation-scoped |
| retired marker) and R3-4 (the sweep discovers locations from the |
| -namespace; `retired_incarnations` is anti-reuse only). Rev 7.4 encodes |
| -all three (§6.4, §14.2.2) and asks platform-1e three questions, in |
| -`moves-claim-questions.md`: how operations are ordered for "at or |
| -before" in production (the model uses its op counter); whether §5.1 step |
| -5 presupposes a claim object exists (a record with no claim is a legacy |
| -host to adopt, §9); and which spec owns the statement that a sweep never |
| -touches the version `generation.json` names. |
| +namespace; `retired_incarnations` is anti-reuse only). Rev 7.4 encoded |
| +all three and asked platform-1e three questions. |
| + |
| +**Claim rev 6.2 (30d572c4), after joint round 4: no request from this |
| +spec.** Rev 6.1 (79a5243c) answered the three questions: operations are |
| +ordered by claim `gen`, op ids opaque; a record with no claim object is a |
| +legacy host, never an orphan; the claim owns the sweep-side rule for the |
| +live set and this spec owns generation-specific live-object selection. |
| +Rev 6.2 then took joint round 4's claim side: **installation stamps** |
| +(§6.4 there: the stamp is the claim `gen` captured at the CAS that made |
| +the installation current, a writer is admitted only at the active stamp, |
| +retirement sets `none`, a reinstall sets its own stamp under the guard), |
| +the **region-qualified live set** (§6.2a there: `{authoritative region, |
| +holder.location, holder.generation}` plus the versions this spec supplies, |
| +protected in the authoritative region only; the authoritative region is |
| +`op.exec`'s while serving, or what this spec's phase table names, §5.1), |
| +and **retirement under purge** (§5.3 step 4 there: the record version in |
| +`op.intent`, `DeleteIfMatch`, `purging → parking` only with the record |
| +gone and the journal at `torn-down`; a purged host never returns to |
| +`serving`). Rev 7.5 encodes all of it (§5.1, §6.4, §14.2.2) and raises no |
| +new claim question (`moves-claim-questions.md`, rev 7.5 heading). |
| **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 |
| @@ -2794,3 +3008,22 @@ Astra's verdicts were "not ready" for all three artifacts. Claim rev 6 |
| | witnesses accept any violation; `reach` turns a missed milestone into INV-3; subsets not run | every witness names its failure and must violate with it; a missed milestone is INV-3 only where the milestone is the property (the round trip); the gate runs every proper subset of each combination and requires the witness to hold there (§14.2.1) | |
| | `commit-before-open` wrongly among "caught" | it now has its own violation (INV-5) and witness; the pair is retired | |
| | model fidelity: restore, admission, cross-object atomicity, local exclusion, same-operation workers, oracle, late creates, cleanup ordering, timing | restore and public admission taken (above); passes carry an operation sequence (R2-3, partly); the rest is listed as not taken in §14.2.6, each with what it hides | |
| + |
| +### 22.11 Joint round 4 (claim 3ea3f90c, moves rev 7.4 0e41b685, model 0e41b685) |
| + |
| +Astra's verdicts were "not ready" for all three artifacts, on narrower |
| +grounds: the production fence contract, executable restore semantics, and |
| +two lifecycle transitions. Claim rev 6.1 (79a5243c) answered the moves |
| +session's three questions; claim rev 6.2 (30d572c4) took R4-2, the claim |
| +half of R4-3, and R4-4. Rev 7.5 answers the moves side and encodes rev 6.2. |
| + |
| +| finding | rev 7.5 | |
| +|---|---| |
| +| R4-1 (P1) the restore can discard a valid compacted continuation; "compare content at those TXIDs" is undefined for compacted objects | one implementable contract (§5.1): the sealed transfer is a verified final snapshot, `generation.json` names it by key, version, TXID and checksum, the restore is litestream v0.5.14's planner from that snapshot (overlap allowed, highest max first), and it fails closed on a missing snapshot, a gap or a checksum mismatch; the content comparison is gone; §7.6's `copying` produces the snapshot the restore consumes; §7.8's return is a snapshot too. The model's LTX files have ranges, page images, checksums and compaction, with updates and deletes; two witnesses: a compacted `[10,15]` over a snapshot at 11 restores through 15 (rev 7.4's rule discards it and loses four writes), and a genuine gap refuses (§14.2.5) | |
| +| R4-2 (P2) the replacement fence rejects its own installation if read literally; the op counter and "empty op means now" have no production contract | claim rev 6.2's installation stamps, encoded exactly (§6.4, §14.2.2): the active stamp is the claim gen captured at the installing CAS, writers are admitted only at equality, retirement sets none, a reinstall sets its own stamp on a fresh read; `- 1` and the counter are gone, op ids are opaque; a stale config pass across UK→US→UK is the new witness (`fence-stamp`), admitted only when the stamp check and the host guard's fresh check are both gone; the probe-fence oracle makes a fenced landing a violation in the sweep too | |
| +| R4-3 (P2) the sweep exempts the current generation in the wrong region | the live set is `{authoritative region, location, generation}` plus the versions this spec names, protected in the authoritative region only; the phase table names the authoritative region for every claim state (§5.1); claim §6.2a is cited for the sweep-side rule; `deadGeneration` checks the region for every key; witness: an old US replication PUT for a legacy host serving in UK (`live-set-region`) | |
| +| R4-4 (P2) purge invokes a retirement whose entry condition it invalidated | claim rev 6.2 §5.3 step 4, encoded: `deleteSteps` records the record version in `op.intent`, deletes conditionally, and moves `purging → parking` only with the record gone and the journal at `torn-down`; a purged host never returns to `serving` (§14.2.2) | |
| +| Q2: orphan cleanup with no claim | the branch is explicit in the model and in §19.4: a record with no claim object is a legacy host, never an orphan (claim rev 6.2 §5.1 step 5) | |
| +| §3: the random generator lacks marker and freeze schedules, a successful-move assertion and a retention fixture | all four added (§14.2.2): the marker-hold and targeted-freeze schedules, the retention-boundary fixture, and two oracles that refuse a rolled-back move as a terminal where it should have served; `generation-conditional`, `sweep-namespace` and `restore-overlap` are caught by the sweep alone now (§14.2.5) | |
| +| §3: the three-rule door combination is not minimal | the results table says so: `door-by-version` + `location-per-incarnation` is the minimal pair and the three-rule row is reported as a wider combination whose two-rule subsets already fail (§14.2.5) | |
| +| §3: ids-only restoration is a fidelity gap | closed with R4-1: transactions update and delete, and the oracle compares states, not id sets (§14.2.6 says what a page is not) | |