# PERS4 runtime evidence [Raw logs, mutation results, independent reviews and decision trail](https://files.bagerbach.com/pers4-raw-evidence-u8cv2esuu3rb.zip). Baseline commit `76427654558bcfd43353664371d2767ad4de0ef2` from `origin/master`. Final branch commit `cd59a6765929eb43d329d6f77d4176dcaef1dd28`. Linux, Node 22.23.2, isolated Obsidian 1.14.4. All runtime commands use the repository runner and this worktree's private vault. The fixture library has a Recovery feed at `https://example.com/recovery.xml` and a saved episode with progress at 42 of 90 seconds. Recovery tests use local data only. Adapter read failures simulate loss of read access without changing the file. The idle rename test uses actual malformed JSON and a filesystem rename, with no edit or synthetic callback after the rename. ## Before Seven runtime cases failed on master in `master-final-e2e.log`. Seven new unit cases failed in `master-unit.log`. Existing writer and import tests passed, 52 tests in `master-existing-unit.log`. | Issue | Action | Master result | | --- | --- | --- | | #429 | Replace the file with schema 99 while a checkpoint is pending, wait 21 seconds | Reads continue, no Notice, zero writes, protected SHA-256 unchanged | | #429 | Replace the file with schema 1 while a checkpoint is pending, wait 21 seconds | Reads continue, one restart Notice, zero writes, protected SHA-256 unchanged | | #430 | Restore reads, import settings using the real file picker and confirmation | Import fails, skip forward remains 15 instead of 37 | | #430 | Restore reads, select a test secret through the real OpenAI Link dialog and save | Reference remains empty | | #431 | Malformed file triggers refusal, move it away while idle, wait 12 seconds | File remains missing, zero writes | | #431 | Restore reads and hide the document before the retry interval | Volume remains 0.8 on disk instead of 0.42 | | #432 | Start with malformed JSON | File protected, Notice omits restore-access-and-reload instruction | The test logs include adapter read/write counts, exact Notice text, and file SHA-256 values at each sampled stage. Expected console errors show the injected read failures or unsupported schema. The E2E harness also watches uncaught renderer errors. ## Healthy session comparison `.audit/PERS4-normal-session.mjs` restores the same starting data, loads a real 60-second WAV fixture through the PodNotes URI, verifies playback advances, plays six seconds, skips forward, plays six more seconds, pauses, flushes, then waits eleven seconds idle. The recorder targets only PodNotes' data.json. It starts after seeding and stops in a finally block. Master completed 3 writes with zero failures, zero writes in flight, and zero additional idle writes. Playback reached 5.776 seconds before skipping and paused at about 26.726 seconds. See `master-normal-session.log` and `master-writes.json`. Master bundle is 310775 raw bytes and 98183 gzip bytes at level 9. Limits are 330000 and 100000 bytes. ## Branch All seven regression journeys pass in Obsidian 1.14.4. The corrected key assertion was separately rerun on the saved master bundle and failed with an empty saved reference. The branch saves a PodNotes-owned reference whose value equals the selected fixture secret. The full fresh-vault E2E run passed all 173 tests in 23 files, then stopped its instance. Both independent full-diff reviewers found no production defect. Both fresh focused reviewers accepted the test-only follow-up with no findings requiring action. Claude found one missing startup-to-schema-stop test; the added case passes and rejects the wrong startup-only behavior. The unit suite before that addition passed 2539 tests, the tooling suite passed 13, and lint, formatting, typecheck, build and docs build passed. The final unit run passes 2540 tests in 121 files. Focused writer tests pass 40 cases. Lint, formatting and typecheck pass after the added test. The full minimum-version suite passed all 173 tests in 23 files on Obsidian 1.13.7, in 633.34 seconds. The runner stopped its instance afterward. The 1.14.4 run took 641.55 seconds. These are completion times, not a performance comparison. The branch completed exactly the same 3 writes and 7914 total bytes as master. Both have 0 failed writes, 0 inflight writes, an intact recorder, an unchanged plugin instance, and 0 additional writes during the eleven-second idle window. Bundle size is 311304 raw and 98452 gzip bytes, a delta of +529 raw and +269 gzip bytes. One verification take overlapped two drivers and was discarded. The reported take ran the regression suite to completion before starting the write-count driver through a shell success dependency. ## Recorded disk and Notice evidence ### Master | Stage | Reads | Writes | SHA-256 | | --- | --- | --- | --- | | idle-refused | 4 | 0 | 021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96 | | idle-recovered | 4 | 0 | missing | | schema-99-refused | 2 | 0 | 2e549d62b6a21912394286b06c7db2a7f369f2617ea2a5d037eb6af22df4e38a | | schema-99-waiting | 4 | 0 | 2e549d62b6a21912394286b06c7db2a7f369f2617ea2a5d037eb6af22df4e38a | | schema-1-refused | 1 | 0 | fbd9ceb8fe32e398a815c47a0da5281391e89d67e8bf18b9826162a3d38d0488 | | schema-1-waiting | 3 | 0 | fbd9ceb8fe32e398a815c47a0da5281391e89d67e8bf18b9826162a3d38d0488 | | import-recovered | 2 | 0 | eaf03c7a40ce63909d101af783ceefe076a86427a6713af2cf3d7d9bfc5d01ab | | key-recovered | 2 | 0 | eaf03c7a40ce63909d101af783ceefe076a86427a6713af2cf3d7d9bfc5d01ab | | visibility-recovered | 2 | 0 | eaf03c7a40ce63909d101af783ceefe076a86427a6713af2cf3d7d9bfc5d01ab | | startup-refused | - | - | 021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96 | Captured Notices: - PodNotes could not read .obsidian/plugins/podnotes/data.json, so it leaves the file unchanged and saves nothing until the file can be read again. - .obsidian/plugins/podnotes/data.json was replaced by a copy from an older version of PodNotes, likely synced from another device. PodNotes saves nothing until you restart Obsidian, which updates the file and keeps its API keys. - Could not import PodNotes settings or persist its rollback. The safest recovered settings remain active for this session. - Could not save the API key selection or its rollback. The previous selection remains active for this session. - PodNotes could not read .obsidian/plugins/podnotes/data.json, so it left the file unchanged and started with default settings. Nothing is saved until the file can be read again, or until you delete it and restart Obsidian. ### Branch | Stage | Reads | Writes | SHA-256 | | --- | --- | --- | --- | | idle-refused | 2 | 0 | 021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96 | | idle-recovered | 4 | 1 | eaf03c7a40ce63909d101af783ceefe076a86427a6713af2cf3d7d9bfc5d01ab | | idle-once | 4 | 1 | eaf03c7a40ce63909d101af783ceefe076a86427a6713af2cf3d7d9bfc5d01ab | | schema-99-refused | 1 | 0 | 2e549d62b6a21912394286b06c7db2a7f369f2617ea2a5d037eb6af22df4e38a | | schema-99-waiting | 1 | 0 | 2e549d62b6a21912394286b06c7db2a7f369f2617ea2a5d037eb6af22df4e38a | | schema-1-refused | 1 | 0 | fbd9ceb8fe32e398a815c47a0da5281391e89d67e8bf18b9826162a3d38d0488 | | schema-1-waiting | 1 | 0 | fbd9ceb8fe32e398a815c47a0da5281391e89d67e8bf18b9826162a3d38d0488 | | import-recovered | 4 | 1 | ac3735a51fd297d1b6c70624ca41773836ecf0d442fa6b51de029c82f7d68396 | | key-recovered | 4 | 1 | 982e582a40bf4b9d00d37aa7709a7eba5fc6adc0cd244d77e8e916c31b8269a8 | | visibility-recovered | 6 | 1 | 82c0a20c9dd84fc506bc8e24ea0a613fb44983659c88293c9ee1dfccb4cc427d | | startup-refused | - | - | 021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96 | Captured Notices: - PodNotes could not read .obsidian/plugins/podnotes/data.json, so it leaves the file unchanged. It tries again automatically and saves your changes once the file can be read. - .obsidian/plugins/podnotes/data.json was written by a newer version of PodNotes, likely synced from another device. PodNotes saves nothing until you update PodNotes. - .obsidian/plugins/podnotes/data.json was replaced by a copy from an older version of PodNotes, likely synced from another device. PodNotes saves nothing until you restart Obsidian, which updates the file and keeps its API keys. - Imported PodNotes settings. - PodNotes could not read .obsidian/plugins/podnotes/data.json, so it left the file unchanged and started with default settings. Nothing is saved until you restore read access to the file and restart Obsidian. ## Screenshots - Startup before: https://files.bagerbach.com/master-startup-notice-whi3k4itv4ny.png - Startup after: https://files.bagerbach.com/branch-startup-notice-mfctzwr67nyr.png - Import before: https://files.bagerbach.com/import-recovered-wm9avzepc0st.png - Import after: https://files.bagerbach.com/import-recovered-kvb2629nzi29.png - Newer-schema Notice: https://files.bagerbach.com/schema-99-refused-o7x4r6zrp2b6.png All screenshots were captured from the same worktree's isolated app and inspected. The standalone startup capture is used because the automated startup screenshot could be blank despite the Notice journal recording it. ## Runtime commands ```sh fnm exec --using 22 -- npm run test:e2e -- tests/e2e/save-refusal-recovery.test.ts fnm exec --using 22 -- node scripts/obsidian-e2e.mjs test tests/e2e/save-refusal-recovery.test.ts fnm exec --using 22 -- node .audit/PERS4-normal-session.mjs master fnm exec --using 22 -- node .audit/PERS4-normal-session.mjs branch fnm exec --using 22 -- node .audit/PERS4-startup-notice.mjs branch fnm exec --using 22 -- npm run test:e2e PODNOTES_OBSIDIAN_APP=/opt/Obsidian/obsidian fnm exec --using 22 -- npm run test:e2e ``` The runner-only invocation during the comparison uses an already-built bundle. The corrected key test was also run once with the saved master bundle, then the branch bundle was restored before branch verification. ## Known limits Desktop quit and pagehide still forbid starting a write. If the unreadable file is moved away and Obsidian exits before a retry observes the change, the app cannot restore that file during shutdown. Automatic recovery polls every ten seconds because the native watcher does not report rename-away or permission changes. This is a platform limitation, not a new write-on-quit path. The existing import rollback failure text still says to change a setting to save again. That sentence is outside this unit's narrow UI refusal-check scope and should be removed in a scoped follow-up. Recovery no longer requires that edit. ## Mutation verification [Commands and failing tests for all thirteen mutations](https://files.bagerbach.com/pers4-mutations-4z508xmixalm.log). The first blocked-timer mutation initially passed; moving its timer assertion before the strict save made it fail. The other twelve variants all failed their named behavior tests. Review added a fourteenth mutant that prevents a schema stop replacing startup protection. Its regression test fails with expected "update PodNotes", received "could not read at startup". [Startup-to-schema-stop mutation and failure](https://files.bagerbach.com/pers4-mutation14-mxskod5oi41f.log). ## Review record GPT Astra and Claude Fable reviewed the full diff independently. Claude identified the missing startup-to-schema-stop test. A fresh Opus verifier added it and tested M14; fresh Astra and Fable reviewers both accepted the test and proof. The four original nits were dismissed because current messages are stable and distinct, retry logging is existing development behavior, no allocation problem was measured, and malformed data is intentionally a retryable read failure. The final production diff matches the first reviewed diff exactly. Fix Root Causes put refusal decisions in the writer. Model the Domain separated retry and stop outcomes and required a refusal for startup without a baseline. Prove It Works required the real Obsidian comparisons and direct disk evidence. ## Delegation record - Investigation and QuickAdd history ran on claudeAgent/claude-opus-5-5. - Design candidates ran on claudeAgent/claude-fable-5-1 and codex_pool/gpt-6-astra. - Design cross-judgment ran on claudeAgent/claude-opus-5-5. - Implementation ran on claudeAgent/claude-opus-5-5. - Comment review ran on claudeAgent/claude-opus-5-5. - Full and focused reviews ran on claudeAgent/claude-fable-5-1 and codex_pool/gpt-6-astra. - Final transition test, mutation and minimum-version runtime verification ran on claudeAgent/claude-opus-5-5. - Baseline reproduction, 1.14.4 runtime comparison, healthy write measurements, whole-branch checks and final integration ran in the parent thread. ## Final state All delegated agents were explicitly stopped and confirmed without pending child runs before the fix commit. The worktree is clean. The isolated runner dry-run reports pids none. Another worktree owns the remaining Obsidian processes on this host and was left alone.