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).