diff --git a/tools/corpus_engine.py b/tools/corpus_engine.py index 89b1e670..9910f01f 100755 --- a/tools/corpus_engine.py +++ b/tools/corpus_engine.py @@ -79,6 +79,12 @@ SAVE_CLAIM_HISTORY_RE = re.compile( r"histor(?:y|ical))\b", re.I, ) +SAVE_LIVE_MIGRATION_AUTHORITY_RE = re.compile( + r"`?save[.]rs`?[^.;!?]{0,160}\b(?:authority|authoritative|owns?)\b" + r"[^.;!?]{0,160}\b(?:each|every)\s+(?:save\s+)?version\s+" + r"migrat(?:e|es|ed|ing|ion|ions)\b", + re.I | re.S, +) BINDING_DOORWAY_RE = re.compile( r"^Type: (law|spec)$|^Status:|^## Decisions log|^## Acceptance criteria", re.M, @@ -427,28 +433,51 @@ class Engine: ) def validate_wiki_save_version_claims(self) -> None: - """Binding wiki pages may not claim numbered save migrations still run. + """Binding wiki pages may not claim retired save migrations still run. Only the exact current SAVE_VERSION loads (pre-release rider, 2026-07-16); the numbered migration ladder lives in git history. A sentence that couples an old vN to a live persistence/migration verb without naming that retirement asserts dead behavior. Dated logs and - the process board are historical records and exempt. + the process board are historical records and exempt. The same gate + rejects generic claims that save.rs currently owns what each version + migrates: that wording escaped the numbered-version check after the + retired authority class had already recurred across current pages. """ save_rs = self.root / "crates/misaligned-core/src/save.rs" if not save_rs.exists(): return - const = re.search( - r"pub const SAVE_VERSION: u32 = (\d+)", save_rs.read_text() - ) + save_text = save_rs.read_text() + const = re.search(r"pub const SAVE_VERSION: u32 = (\d+)", save_text) if const is None: return current = int(const.group(1)) + exact_current_only = ( + re.search(r"probe[.]version\s*!=\s*SAVE_VERSION", save_text) is not None + and re.search(r"\bmigrate_save_state\b", save_text) is None + ) for path in self.wiki_md_files(): rel = path.relative_to(self.root).as_posix() if rel.startswith(("wiki/log/", "wiki/process/")): continue text = self.read(path) + if exact_current_only: + for claim in SAVE_LIVE_MIGRATION_AUTHORITY_RE.finditer(text): + paragraph_start = text.rfind("\n\n", 0, claim.start()) + 2 + paragraph_end = text.find("\n\n", claim.end()) + if paragraph_end == -1: + paragraph_end = len(text) + paragraph = text[paragraph_start:paragraph_end] + if SAVE_CLAIM_HISTORY_RE.search(paragraph): + continue + line = text.count("\n", 0, claim.start()) + 1 + compact = " ".join(claim.group(0).split()) + self.bad( + f"{rel}:{line} presents save.rs as the live authority " + f"for per-version migration ('{compact}') but the " + "pre-release loader accepts only the exact current " + "schema — name that gate or explicit release-era history" + ) for chunk_match in re.finditer(r"[^.;]+", text): chunk = chunk_match.group(0) nums = SAVE_VERSION_NUM_RE.findall(chunk) diff --git a/tools/test_corpus_engine.sh b/tools/test_corpus_engine.sh index 17cd5f03..7ad09ac9 100755 --- a/tools/test_corpus_engine.sh +++ b/tools/test_corpus_engine.sh @@ -324,9 +324,41 @@ setup_base "$root" mkdir -p "$root/crates/misaligned-core/src" cat > "$root/crates/misaligned-core/src/save.rs" <<'EOF' pub const SAVE_VERSION: u32 = 33; +fn parse_save(probe: Probe) { + if probe.version != SAVE_VERSION {} +} EOF cp "$root/wiki/vision/law.md" "$root/wiki/vision/law.md.orig" +# A generic claim that save.rs still owns per-version migrations fails even +# when it carries no old numbered version for the earlier detector to match. +cat >> "$root/wiki/vision/law.md" <<'EOF' + +`save.rs` remains the authority on what each version migrates. +EOF +assert_fails wiki-save-authority-stale --root "$root" --corpus +out=$(python3 "$engine" --root "$root" --corpus 2>&1 || true) +echo "$out" | grep -q 'presents save.rs as the live authority for per-version migration' || { + echo "FAIL: stale generic save-authority message missing" + echo "$out" + fail=1 +} +# The exact-current authority asserted by the live loader passes. +cp "$root/wiki/vision/law.md.orig" "$root/wiki/vision/law.md" +cat >> "$root/wiki/vision/law.md" <<'EOF' + +`save.rs` remains the authority on the exact current schema accepted. +EOF +assert_ok wiki-save-authority-current --root "$root" --corpus +# Explicit retirement history in a binding page remains legal. +cp "$root/wiki/vision/law.md.orig" "$root/wiki/vision/law.md" +cat >> "$root/wiki/vision/law.md" <<'EOF' + +The former `save.rs` authority on what each version migrates is retired to git +history. +EOF +assert_ok wiki-save-authority-historical --root "$root" --corpus # A binding page claiming an old numbered save migration still runs fails. +cp "$root/wiki/vision/law.md.orig" "$root/wiki/vision/law.md" cat >> "$root/wiki/vision/law.md" <<'EOF' Save v13 migrates the old Social mode spellings. diff --git a/wiki/log/2026-07-19-player-contract-save-authority.md b/wiki/log/2026-07-19-player-contract-save-authority.md new file mode 100644 index 00000000..a5cb4e52 --- /dev/null +++ b/wiki/log/2026-07-19-player-contract-save-authority.md @@ -0,0 +1,37 @@ +# The player contract names the current save authority + +``` +Type: log +``` + +## Intent + +Re-audit the highest-severity player law, repair its stale save authority +sentence, and make the recurring claim mechanically reject itself. + +## Finding + +The player-contract tick rechecked the highest-severity law against the live +product and save path. The direct dependency graph remains local and carries +no network or analytics client. Save writes still sync a sibling `.tmp`, copy +one `.bak`, and atomically rename without truncating the current save. The load +gate, however, now accepts only the exact `SAVE_VERSION` and has no migration +entry point. + +The law retained one sentence from the deleted ladder: “`save.rs` remains the +authority on what each version migrates.” That was no longer a true account of +current behavior. The rider now says `save.rs` owns the exact current schema +accepted during pre-release, then requires every later post-release format +change to carry its predecessor forward. + +## Defense + +This is another instance of a recurring mechanical class already seen in +current architecture and mechanic pages: migration wording survives after the +runtime authority moves. The existing corpus checker caught stale numbered +claims but could not see this generic one. It now derives the exact-current-only +state from the live load gate and rejects a binding page that presents +`save.rs` as the current authority on what each or every version migrates. +Fixtures pin the stale generic form, the exact-current form, and explicit +retirement history, while dated logs and process records remain historical +evidence. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 02f1e904..0f9e5b58 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ add or amend a session log, then re-run the generator. +## 2026-07-19 - The player contract names the current save authority + +- Intent: Re-audit the highest-severity player law, repair its stale save authority sentence, and make the recurring claim mechanically reject itself. +- Log: [wiki/log/2026-07-19-player-contract-save-authority.md](2026-07-19-player-contract-save-authority.md) + ## 2026-07-19 - Overt re-hide needs an entry-specific rule - Intent: Re-audit the overt-phase contract against detection, social knowledge, routed evidence, rollback, and the phase-transition laws before treating its READY acceptance criteria as executable. diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index f3ae7ae0..a44c3cfb 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -39,7 +39,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/gameplay/overt-phase.md` | 2026-07-19 | issue | re-audit: the spec says containment and the voluntary reveal both end concealment and make all observers Convinced, then promises a re-hide outcome without defining which entry can return, what raises the durable suspicion floors, or which concealment systems resume. Filed decision-required Tangled issue #13 (containment-only recommended) and marked criterion 5 [OPEN]; the prior dependency, sensor-cut, rollback, and hunter-machine boundaries still stand — [log](../log/2026-07-19-overt-rehide-decision.md) | | `wiki/gameplay/horizon.md` | 2026-07-15 | finding | law verifies: B1 milestone list matches shipped systems (sinks-not-modes, $0 start, rollback classification honestly spec-deferred), B2/B3 match board stages, deferred list agrees with presence.md and cyber-conflict.md, guardrails hold; the finding was in run-shape.md — its repaired roadmap pointer missed that horizon.md itself carries the "Roadmap (v2)" milestone cut; re-pointed to name both the milestone map and the dispatch board | | `wiki/vision/premise.md` | 2026-07-15 | issue | pitch, felt fantasy, pillars, and the four re-cut villain layers verify against the tree and sibling laws; the machine-axis section retains facility-era vocabulary (lair/minions/Milestone 1-2, humanity->machine ascension) contradicting horizon.md's demolition and run-shape's fingerprints-never-classes — filed decision-required Tangled issue #10 with options and recommendation | -| `wiki/vision/player-contract.md` | 2026-07-15 | finding | all five clauses verify (no network deps in the product graph, v-ladder migrations, atomic .tmp rename + one .bak generation in save.rs, honesty via public corpus); the surplus find was the doorway save-version claim gone stale a second time (v29 vs SAVE_VERSION 31) — fixed and, per recurrence-promotes-to-the-gate, corpus_engine now checks the doorway against save.rs — [log](../log/2026-07-15-doorway-save-gate.md) | +| `wiki/vision/player-contract.md` | 2026-07-19 | finding | the local/no-telemetry dependency boundary and atomic `.tmp` + one `.bak` save path still verify, but the continuity rider's authority sentence still said `save.rs` decides what “each version migrates” after the numbered ladder was retired. The law now assigns `save.rs` the exact current schema gate and requires every later post-release format to carry its predecessor forward; recurrence extends the save-claim checker to reject generic live per-version-migration authority while the loader is exact-current-only — [log](../log/2026-07-19-player-contract-save-authority.md) | | `wiki/vision/scale.md` | 2026-07-15 | finding | self-similar interfaces, hall aggregation proof, and all six character spec docs verify; the maintenance-dependency law ("every murder is deferred decay") has no mechanical dispatch — machine-work.md carries the decided clause inside an IMPLEMENTED spec with no criterion, code, or work order; the clause now states that honestly and names hardware-capability-bodies as its dispatch home — [log](../log/2026-07-15-decay-honesty.md) | | `wiki/vision/design-judgment.md` | 2026-07-15 | finding | taste calibration agrees with the current corpus (witness summary matches the reconciled narration.md, pacing targets match act-one, rejects match shipped shape); one line predated the Ears-first amendment ("getting one camera" as the opening beat) — re-worded to earning one sense, ear then camera | | `wiki/vision/simulation-laws.md` | 2026-07-17 | finding | all five laws verify against audited systems (automation prices, addressed latency, device-resident work with sited signatures, ActionDesc receipts, legibility); resolved the missing placeholder home with one canonical live/retired registry, enumerated the three current REAL stand-in families, and reconciled stale billboard/core/terminal claims — [log](../log/2026-07-17-placeholder-registry.md) | diff --git a/wiki/vision/player-contract.md b/wiki/vision/player-contract.md index 7132b00e..d158df36 100644 --- a/wiki/vision/player-contract.md +++ b/wiki/vision/player-contract.md @@ -25,10 +25,11 @@ highest-severity tick findings. development saves may break without a migration when compatibility would slow structural work (Cameron: development speed outranks carrying legacy save weight). Breaking is a choice made in the landing - commit, never an accident, and save.rs remains the authority on what - each version migrates. From the first public release the clause binds - in full. A player's persistent world is the point of - the game; breaking it breaks the contract. Saves also survive the act of + commit, never an accident, and `save.rs` remains the authority on the + exact current schema accepted. From the first public release the clause + binds in full: every later format change must carry its predecessor + forward. A player's persistent world is the point of the game; breaking + it breaks the contract. Saves also survive the act of saving: the game never truncates the existing save in place. Every save write goes to a sibling temp file that is synced and atomically renamed over the target, and the prior save is first rotated (by copy) to exactly