GENERATED VIEW · PRIVATE ARCHIVE · DO NOT SERVE
RobCo Industries · Archive Museum

AUDIT_U10_followups.md



RELEASE

planning/2.8.5/audits/AUDIT_U10_followups.md

sha256 138fc8a07413eac4 · 7515 bytes · original held in the private archive

# Independent Audit — commit `1f72eaf` (U9 follow-up close-out: F1 nativeUseItem + F2 boot fast-path heal) **Auditor:** Opus (Protocol 8 stage 3, independent). Formed from the diff first; commit body read last, only to confirm the implementer knew what I found. **Base:** `d95e5d7` → `1f72eaf` on `dev`. Tests 2997 → **3002** (+5), suites unchanged at **222**, cache r11 → **r12**. **Both runners verified locally: Node 3002/3002 PASS, PowerShell 3002/3002 PASS — exact parity.** ## TOP LINE: **ACCEPT** Both handed findings (F1, F2) are genuinely and correctly closed. The implementer took the harder, honest path on F2 — it made the CODE back the CHANGELOG claim rather than softening the wording, and it did so correctly: the fast-path reconcile is placed **after** inventory is hydrated, so it heals stale references without any risk of wiping legitimately-held gear. The 5 new tests are real behavioral guards (three execute the actual source in a VM sandbox), including the exact plain-reload-heal test and a boot false-positive guard. No gate weakening. No overstatement this time — the ARCHITECTURE.md prose is scrupulously honest about what was broken and what is now true. --- ## VERIFICATION OF EACH CLAIM ### 1. ⚠ Is the CHANGELOG claim now TRUE on every load path? — **YES, on all of them.** Claim: *"A save from before this fix self-heals the first time it's loaded."* I traced every path a campaign can enter memory and confirmed the reconciler runs on each: | Load path | Where reconcile runs | Verdict | | --------- | -------------------- | ------- | | **Plain reload of existing v8 save (fast-path)** — the one that was broken | `_hydrateStateFromStorage()` `ui-core.js:374`, direct call | ✓ NOW HEALS | | Fresh / cold boot (no data) | `!loadedOk` branch → `migrateState()` `ui-core.js:407` → `reconcileEquipped(s)` `state.js:2086` | ✓ | | v7 → v8 one-time migration | `migrateState()` `ui-core.js:385` | ✓ | | Cloud pull | `migrateState()` per campaign `cloud.js:868`, then reload → fast-path | ✓ | | File import — v8 container | `_writeImportedContainer` + reload → fast-path `:374` | ✓ | | File import — envelope / bare state | `autoImportState()` → `reconcileEquipped` `api-import.js:516` | ✓ | | Save-slot load / restore version | `_applySlotEnvelope()` → `migrateState()` `ui-saves.js:420` | ✓ | | Game/context switch | `onGameContextChange()` writes + `location.reload()` → fast-path `:374` | ✓ | **No load path still fails to heal.** The claim is now literally true, where before the fix it was false on the single most common path (plain reload). This is the correct resolution of the F2 overstatement. ### 2. ⚠ Could the boot-path reconcile ever WIPE legitimately-held gear? — **NO.** - **Ordering is correct.** In the v8 fast-path, `state = { ...state, ...activeCampaign }` (`ui-core.js:362`) merges the saved campaign's `inventory` into `state` **before** `reconcileEquipped(state)` runs (`:374`). Inventory is fully present, synchronously, from the parsed JSON. There is no async gap. - **Reconcile only clears a slot when NO inventory entry matches by name** (`inv.some(it => it && it.name === name)`, `state.js:1977`). There is no secondary item store, so an unmatched name genuinely means the item is not held — the intended heal, never legit gear. - **Edge cases hold.** Save with no `inventory` key → base default `inventory: []` survives the merge; but any save carrying a non-null `equipped` is v1.6.4+ and always has an inventory sibling (equipped postdates inventory). Save with no `equipped` → base default all-null → reconcile no-ops. Corrupted payload → caught by `try/catch`, quarantined, boots fresh (`ui-core.js:377`). First boot with no save → `!loadedOk` branch, never touches the fast-path. - **Over-fire check:** a stack reduced 2→1 keeps its equipped reference (name unchanged, exact `===` match). Confirmed by test 221.16 (below) executing the real fast-path against a still-held item. ### 3. F1 — USE path genuinely covered, call AFTER removal? — **YES.** `nativeUseItem()` `ui-render-inventory.js:420-421`: `state.inventory.splice(idx, 1);` then `if (reconcileEquipped(state)) renderEquipped();` — reconcile lands immediately after the splice. I swept **all five** `inventory.splice` sites (economy:42/610, inventory:61/77/420); every one is followed by a `reconcileEquipped` call, plus `autoImportState`'s wholesale replace. The enumeration is now complete — no missed removal path remains. ### 4. The 5 new tests (221.12–221.16) — **REAL behavioral guards, not filler.** - **221.12** static: `nativeUseItem` splice→reconcile call-site. - **221.13** behavioral: runs the REAL `nativeUseItem` in a VM against the contrived same-name aid/weapon collision; asserts `state.equipped.weapon` clears and `renderEquipped()` fires once. - **221.14** static: fast-path `_migrateEventLog → reconcileEquipped` call-site. - **221.15** ★ behavioral: runs the REAL `_hydrateStateFromStorage()` against a stored v8 payload whose `equipped.weapon` names an item absent from inventory; asserts it clears **without migrateState ever running**. *This is exactly the plain-reload-heal test the orchestrator required — the F2 regression cannot silently return.* - **221.16** ★ behavioral: runs the REAL fast-path against a still-held item; asserts the slot is **kept** — the boot-wipe false-positive guard. Both runners carry all 16 labels (221.1–221.16); the PowerShell behavioral trio runs via the same node-shell-out mechanism as the originals. Suite header count comment updated 11→16 in both. ### 5. Gate weakened? — **NO.** No assertion loosened, deleted, or made vacuous; no fixture altered to make code pass. The only test changes are additive (5 new tests). `test.html` was not touched this commit. ### 6. Protocol 2a count sync — **CLEAN.** 2997→3002 synced across CLAUDE.md (all sites), RULES.md, README.md, ARCHITECTURE.md (file tree + rollback checklist + inline prose), CHANGELOG `[Unreleased]` header. No stale 2997/2999 anywhere (the "3000" hits are PIP-BOY model names). Cache r11→r12 (served `js/` changed — Protocol 1). Suites unchanged at 222 (tests added to existing Suite 221). ### 7. Doc honesty — **EXEMPLARY.** ARCHITECTURE.md now explicitly documents that the "self-heals on load" claim *was only true for cloud-pull/file-import/save-slot-load* and that the v8 fast-path skipped `migrateState`, and states the fast-path now calls the reconciler directly. This is the opposite of the overstatement pattern from prior audits — the implementer documented its own prior gap plainly. --- ## FINDINGS **None.** No BLOCKER, no SHOULD-FIX, no NOTE worth the owner's attention. This is a clean, minimal, well-tested close-out of both handed findings. ## Answers to the orchestrator's questions - **CHANGELOG claim true on every load path?** Yes — verified on all 8 entry paths, including the plain reload that was broken. Not overstated. - **Could the boot reconcile wipe legit gear?** No. Reconcile runs after inventory is merged; only clears names with no inventory match; no secondary store; edge cases (empty/absent inventory, legacy shape, corrupt payload, first boot) all safe. Proven by behavioral test 221.16. - **Real test for the plain-reload heal?** Yes — 221.15 executes the real fast-path and asserts the heal with migrateState provably uninvolved. Will not silently regress. - **Anything meaningful to flag?** No.
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).