{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"d76a2a79-7394-420a-99d2-df2ba6a23f45","kind":"audit","nodes":[{"acceptedSubmissionHash":"0cd092863d7fc9b439422d320bf769d43aa1da6715daab3ebff653de6b19f4fd","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"1f054b575f831f7721e3d25b8b1d5af449cd171b349155cc969e36d93e366326","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"73463325b9200243ae7bc76d4ee79e7ee7d478a7b98a33f740762078451ecf87","dependsOn":["audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"e3896314076881081b43f71b8067b48418f6ff382dc9c86302db922a9c69ddf4","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"df0892ab0164d201183f36b5d09aa14500f1c13ea24970ead80248b5993efba1","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"}],"objective":"IMD Ember World — ninth offline audit / closure of the latest Audit8 findings (World / Member M1 only).\n\nQuestion: Does this exact candidate close the three Low and two Info findings from the latest eighth Audit, with bounded regression evidence? Seek new or reopened findings of any severity; do not promise a pass or zero findings.\nPeriod: latest Audit8 through the source-freeze and release measurement timestamps in this pinned snapshot, as of 2026-10-05. Length/format: Markdown finding table, precise reproductions and unlimited evidence appendix; preserve code, hashes and URLs.\n\nExact public snapshot: https://github.com/tungweb3/imd-ember-world-review/tree/347268a7ecae700088547c2402db9a3eb07a6fd2\nPrevious public: 88c130283efc45260f9e00da8d2d3055c38483bd\nPrivate source provenance: 8c3b60171a22b3ce71854f12282e629bbf5ca06f (not a public checkout URL).\nTEAM measured current Worker: 06cbc8fe-112f-4a11-b84f-42907179afff, 100% traffic; record 20261005T011008Z-8c3b601.\n\nRead Submission9/README.md, REVIEW_INPUTS.md, PRIOR_REVIEWS.md, FinalClosure/ closure/test/artifact/build/served/reviewer evidence, SERVED_EXPECTATIONS.json and manifests/submission9-published-source.json. Use this exact pin; older namespaces are historical.\n\nUnofficial TypeScript Cloudflare Worker/React SIWE; NO SOLIDITY. M1 writes persistent profiles. Scope Auth/server authority, ownership/index/budget/freshness and artifact/runner. Exclude Genesis/Mint, Ember Coin, Fren Pet, full 3D/scene/media/avatar/selfie and private backups. Public141 sources:125exact,16redacted/57masked lines; no private Git/full frontend. Unavailable full compilation is not a pass.\n\nOffline/local synthetic tests only. Public source/prior-document GETs and fresh dependency downloads are permitted. No live site/API tests or writes, real wallet signatures/logins, approvals, transfers, minting, payments, job submissions or deployments. Never request owner credentials/private databases.\n\nFresh public checkout, Node 24.x, then in source/: npm ci --ignore-scripts; node scripts/review-tests.mjs --check; npm run test:review; node scripts/verify-artifact-closure.mjs. Use real locked viem 2.56.9 and all 23 selected test files. No missing-module stubs, private source selectors, substitute crypto/Worker/SQLite, omitted failing files, hidden skips or leaked outputs. Windows file-symlink controls require real capability; report actual environmental failures honestly. Artifact default creates no saved scheduler output; opt-in sanitized artifacts remain under the explicit private source/tmp policy.\n\nTEAM final exact-source measurements: Node 24.19.0; private full 1663/1663, supported runner on private source 613/613, fresh clean private-source checkout 613/613, real filesystem artifact suite 18/18, standalone and clean verifiers 13/13; all acceptance runs have zero fail/cancel/skip/todo. These are NOT a direct runtime rerun of the filtered public-byte checkout; selected-source correspondence is verified separately. Independently run the public runner. Historical failures and vulnerable-baseline expected exit 1 remain in TEST_RESULTS.json. Core 500 campaigns /428 distinct digests and additional 90 /54 are separate identities, not 590 unique proof cases or exhaustive state-machine proof.\n\nLatest Audit8 job7716c3f5-5d6c-4953-a643-141da678d051 reviewed public88c130..., completed2026-10-04T19:26:33.961Z, published19:26:58Z. Immutable original: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md ; SHA2568c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc. Earlier Report8 job38438c89-b34c-4d38-8ae8-027c9d175fb1 completed19:13:53.530Z and does not supersede the later Audit.\n\nReview the five mechanisms hardest, including reverse controls:\n1. Post-D1 ownership proof expiry: strict proof age must be rechecked after all awaits. Test29999/30000/30001ms, sold seat, enrichment delay, refused/failed refresh lane, rollback/NaN/Infinity and latest recovery. Expired/unavailable is not complete-empty/not-owned authority. Do not extend producer checkedAt or renew authority on same-block deltas.\n2. First locked-provider account event: CookieA + provider[] + first accountsChanged(B), without actual observedA, must not revoke sessionA. Cookie identity is not an earlier wallet observation. Genuine observedA-toB, A-lock-B, explicit selection/grant, passive replacement after observedA, restart/stop and delayed cleanup must retain correct context/nonce/address fencing.\n3. Slow fresh overlap: twenty early joined same-context fresh requests with four pages at7475/7500ms share one admitted cycle/four pages/one budget/one proof/one epoch while that proof remains fresh. Different/late intent or changed roster re-evaluates after success/failure. Preserve strict proof TTL, original producer/index timestamp, bounded failure and later retry. This is not a global RPC ceiling or cross-isolate lock.\n4. Dangling artifact final-link escape: exercise actual dangling/existing final file links, directory links, nonregular targets, parent/target substitution and safe hardlink replacement. No outside-root writes. Review lstat identities, exclusive regular sibling temp, fsync and validated replacement. Respect documented locally controlled private-root assumption; portable Node does not prove hostile concurrent ancestry-swap or SMB/NFS safety.\n5. General diagnostic path redaction: actual persisted nested strings must mask arbitrary POSIX, Windows, UNC and file URLs, including spaces/parentheses, C://, D:/// and rooted backslash forms. Preserve network URLs, relative identifiers, structured actions/events/nonces and actual saved-trace replay. Assess conservative same-line masking policy without treating deliberately synthetic test paths as machine leaks.\n\nOnly authenticated-address ownerOf grants ownership; index/roster/D1/name are candidates. Keep original Auth lifecycle cleanup and strict authority boundaries, retained verify/lock503/later-valid controls, nonce ownership, one primary cleanup plan per event and no old-flow cross-revocation. Do not infer production concurrency or real-wallet correctness from offline client/Worker/SQLite fixtures.\n\nFor every finding give severity, blocker rationale, pinned location/prior link, exact event order, actual relevant database rows, prompt/challenge/verify/logout/hint/index/budget/RPC counts, reproduction/exit/hash or argument, fixed/partial/open/accepted limit/policy/unknown and what could not be checked. Separate fresh REVIEWER measurements, TEAM observations, inherited historical evidence, inference and unknowns.\n\nRequest separate SOURCE-CLOSURE PASS/BLOCKED/UNKNOWN and RELEASE-READINESS PASS/BLOCKED/UNKNOWN verdicts. Production upload/build/record persistence and verification are distinct. Current stage-separated deployment consumed exact prior privileged local full-suite receipts; canonical npm run deploy was NOT invoked. The older canonical overall exit1 after successful upload remains historical. Deployment evidence is TEAM readback, not your authenticated production measurement. Completed/accepted and Low/Info labels do not certify approval, endorsement, zero vulnerabilities or fund safety.","parentJobId":null,"planHash":"0b102cfd946e61336f72ca41bc71a093daa045e166ae5866bc6e43c7c42f14b8","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"d76a2a79-7394-420a-99d2-df2ba6a23f45","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51224","feedbackHash":"19fc5373c10b3036c3c3d8a87c92a9a3d4f5a56c3cbbe30bbf80fe14be02d60d","nodeKey":"audit_economics","submissionHash":"0cd092863d7fc9b439422d320bf769d43aa1da6715daab3ebff653de6b19f4fd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51539","feedbackHash":"b0e617ee32a5a2174a0fe4e1e56831ffd33da34438cc5be6f640515074f7b02a","nodeKey":"audit_flow","submissionHash":"1f054b575f831f7721e3d25b8b1d5af449cd171b349155cc969e36d93e366326","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51455","feedbackHash":"2a10e891d7d9718fd8d4fc7afea62d7bb93e02b1f4f86a37e19099e409434cc0","nodeKey":"audit_judge","submissionHash":"73463325b9200243ae7bc76d4ee79e7ee7d478a7b98a33f740762078451ecf87","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52073","feedbackHash":"85e217ec653dc558d9dd879f731543e16f83260c66a7fba9dac6ac29098d6260","nodeKey":"audit_math","submissionHash":"e3896314076881081b43f71b8067b48418f6ff382dc9c86302db922a9c69ddf4","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52089","feedbackHash":"389885b2851539bd5673ab5c47e63d1310bd35ba1ffb643d1bf171b9c819902a","nodeKey":"audit_permissions","submissionHash":"df0892ab0164d201183f36b5d09aa14500f1c13ea24970ead80248b5993efba1","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"5f8639a03afc2adcf36ecbbbc906039311d0b1a74edd2d16c86c13567f9a6726","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5dc170d977094c92","findings":[{"citation":"resolved","description":"OPEN, new scoped lifetime counterexample related to Audit8 Low2's restart/stop reverse control; the original idle first-unlock counterexample is fixed. Public pin 347268a7ecae700088547c2402db9a3eb07a6fd2. start()/stop() reset observedAccount and binding generations but retain s.account. On restarting the same instance, the non-discovery bind branch records the new eth_accounts result in observedAccount, but only updates the public account if the old public account is falsy. Consequently a prior A account remains displayed after the current provider reports B, and statusOf/ownerAddress still grant A's owner UI rather than mismatch. A subsequent explicit signIn uses stale click.account A and takes the same-session fast path without requesting B's account or signature. This is a SOURCE-CLOSURE blocker for the requested correct wallet-context fencing, despite Low severity: no asset transfer, forged server session, or server-side cross-address write is demonstrated. The stale public-account condition predates this candidate, so this is not claimed to have been introduced by the Audit9 change. The supplied same-client-restart test at source/tests/auth-audit8.test.mjs:100 emits a second accountsChanged(null) after restart, which clears the stale state and masks this case. Minimal repair: clear the previous lifetime's public wallet observation before binding and/or apply the current fenced eth_accounts result (including empty) consistently to public and internal observation state, preserving the cookie session and showing mismatch for a first B observation; do not turn a previous lifetime's A into automatic logout authority. Preserve bind/event sequence, nonce cleanup, and delayed old-callback fences. Prior original: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md (Low2; SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc). Reviewer measurement uses the real pinned AuthClient, Worker handler, viem 2.56.9, and migration-backed node:sqlite harness on Linux/Node v24.21.0; actual extension/React scheduling, live D1, and production state are not measured. Exact reproduction JavaScript SHA256 (UTF-8, including final newline): 8dbe9b8c9d9af1e4193443c8d3ce2ab00f33275acdcc6320b088d6349f5a03ff.","line":290,"path":"source/src/world/auth.ts","reproduction":"Run the following via node --input-type=module on stdin from source/ after npm ci --ignore-scripts. No source selectors or source edits. Order: synthetic EOA A signs in; seat 7 ownerOf is A; start and observe A; stop; provider changes to B while listeners are removed; restart the same client; let its second eth_accounts(B) and canonical cookie restore settle; click signIn. Expected account=B and status=mismatch, cookie A preserved until an explicit context-resolved action. Actual account=A/status=owner, even after the click. A fresh-client control over the same cookie/provider correctly yields B/mismatch. The final assertion fails with exit 1 on the pinned code. Measured A=0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a, B=0x1563915e194d8cfba1943570603f7606a3115508. Example actual SQLite session row before AND after: {nonce:'745200747c2c4d578248719d59d62d33',address:A,expires_at:1791201600000,revoked_at:null}; challenge row before AND after: {nonce:'745200747c2c4d578248719d59d62d33',used_at:1790596800000,invalidated_at:null}. Nonces are newly random synthetic test values on each run. Sessions created/live/revoked=1/1/0, challenges total/used/pending/invalidated=1/1/0/0. Setup challenge/verify=1/1. Measured client prompt/connect/challenge/verify/logout/broadcast-hint=0/0/0/0/0/0; client session GETs=3; home GETs=2 (one fresh and one ordinary); index/budget/ownership eth_call totals=1/1/1. Stop selects one cleanup plan {reason:'stop',kind:'none'}. No member/profile writes or funds change.\n\nimport assert from 'node:assert/strict';\nimport {privateKeyToAccount} from 'viem/accounts';\nimport {setup,provider,tab,ready,flush,rows,logouts,prompts,routeEvents,fakeImd,fakeChain,statusOf} from './tests/auth-r7-fixtures.mjs';\nconst A=privateKeyToAccount('0x'+'11'.repeat(32)),B=privateKeyToAccount('0x'+'22'.repeat(32));\nconst a=A.address.toLowerCase(),bb=B.address.toLowerCase(),owners=[];owners[7]=a;\nconst w=setup({chain:fakeChain({owners:{7:a}}),imd:fakeImd({seats:{7:'707'},owners,online:[7]})}),b=w.browser(),p=provider(A);\nlet budget=0;w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\nassert.equal((await b.signIn(A)).verify.status,200);\nconst q=tab(w,b,p);\ntry{\n await ready(q);assert.equal(statusOf(q.c.state,w.clock.now()),'owner');\n const before=rows(w);q.stop();p.switchTo(B);q.restart();await flush(40);\n await q.signIn();await flush(20);\n const state={account:q.c.state.account,sessionAddress:q.c.state.session?.address,status:statusOf(q.c.state,w.clock.now())};\n const counts={setupChallenge:1,setupVerify:1,prompts:prompts([p]),connects:p.calls.filter(x=>x==='eth_requestAccounts').length,\n  sessionReads:routeEvents(q,'/api/auth/session').length,homeReads:q.events.filter(e=>e.kind==='route'&&e.path.startsWith('/api/me/home')).length,\n  challenge:routeEvents(q,'/api/auth/challenge').length,verify:routeEvents(q,'/api/auth/verify').length,\n  logout:logouts(q).length,hints:q.channels.flatMap(c=>c.messages).length,\n  index:w.chain.state.calls.filter(c=>!c.body).length,budget,rpc:w.chain.state.calls.filter(c=>c.body).length};\n const fresh=tab(w,b,p);await ready(fresh);\n const control={account:fresh.c.state.account,status:statusOf(fresh.c.state,w.clock.now())};fresh.stop();\n assert.equal(control.account,bb);assert.equal(control.status,'mismatch');\n assert.equal(rows(w).counts.live,1);assert.equal(logouts(q).length,0);\n console.log(JSON.stringify({provider:bb,before,after:rows(w),state,counts,control}));\n assert.equal(state.account,bb,'restart must adopt the current binding account, not the previous lifetime account');\n assert.equal(state.status,'mismatch');\n}finally{q.stop();}\n","severity":"low","snippet":"      }else if(a){this.observedAccount=a;if(!this.s.account){this.lifecycle.discovered(p,a);this.set({account:a});}}","title":"Restarted AuthClient keeps the previous wallet account and reports owner mode after eth_accounts observes a different wallet"}],"hash":"0cd092863d7fc9b439422d320bf769d43aa1da6715daab3ebff653de6b19f4fd","nodeId":"cd43760b-a66d-4695-82b5-8050a1e73b3b","outcome":"completed","summary":"The five original Audit8 triggers pass bounded regression checks, but **complete closure is blocked by one additional Low finding**.\n\nRecorded the finding, exact reproduction, database rows, counts, and remediation in [`.imd-findings.json`](/home/imd1/.identitymd/work/d76a2a79-7394-420a-99d2-df2ba6a23f45/cd43760b-a66d-4695-82b5-8050a1e73b3b/.imd-findings.json). Product source and configuration remain unchanged.\n\n| Verdict | Result | Rationale |\n|---|---|---|\n| SOURCE-CLOSURE | **BLOCKED** | The required restart wallet-context control has a reproducible gap. |\n| RELEASE-READINESS | **BLOCKED** | The source finding remains unresolved. Independent production/build verification also remains **UNKNOWN**. |\n\nReviewed public pin: `347268a7ecae700088547c2402db9a3eb07a6fd2`. The fetched [latest Audit8 original](https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md) matched SHA-256 `8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc`.\n\n| Finding | Severity/status | Location | Concrete failure |\n|---|---|---|---|\n| F1 — Restart retains the previous wallet account | **Low / Open**; additional scoped counterexample related to Audit8 Low2 | [auth.ts:290](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L290) | Start with authenticated owner A; stop the client; switch the provider to B; restart the same client. Its `eth_accounts` reads B, but public state retains A and reports `owner`. Another sign-in click still uses A. |\n\nThe failing line is:\n\n```ts\n      }else if(a){this.observedAccount=a;if(!this.s.account){this.lifecycle.discovered(p,a);this.set({account:a});}}\n```\n\n`start()` resets the internal observation but retains `s.account`. The guarded assignment therefore ignores B when the previous lifetime left A populated. Expected behavior is B/`mismatch`, while preserving cookie A until an appropriate explicit action. A fresh-client control using the same cookie and provider produces that expected result.\n\nThis blocks the requested wallet-context closure. It demonstrates incorrect owner UI and sign-in behavior, **not** forged server authentication, asset transfer, or cross-address profile writes. The stale-account condition predates this candidate. The supplied restart test emits another `accountsChanged(null)` after restart, clearing the stale state and missing this order.\n\n| Latest Audit8 mechanism | Bounded disposition | Fresh reviewer evidence |\n|---|---|---|\n| Low1: post-D1 proof expiry | **Fixed** | Age `29999 ms`: HTTP 200. Ages `30000/30001 ms`: HTTP 503, `OWNERSHIP_UNAVAILABLE`. Later proof removes the sold seat. Rollback, NaN, Infinity and lane controls passed. |\n| Low2: first locked-provider event | **Original trigger fixed; broader closure partial** | Cookie A + initially empty provider + first B preserves A without logout. Genuine observed switches pass. F1 exposes the separate restart gap. |\n| Low3: slow fresh overlap | **Fixed** | Twenty early callers, four pages at either `7475` or `7500 ms`: one index cycle, four pages, one budget charge, one proof, one epoch. Independent-context, failure and retry controls passed. |\n| Info4: dangling final artifact link | **Fixed within documented local-root assumption** | Real Linux dangling/existing links, directory links, substitutions, nonregular targets and hardlink replacement passed; outside targets remained untouched. |\n| Info5: diagnostic path redaction | **Fixed for exercised forms** | Persisted nested POSIX, Windows, UNC, file-URL, spaced and parenthesized paths were masked. Network URLs, structured identities and saved-trace replay remained intact. |\n\nFresh REVIEWER measurements used a new public checkout, Linux, Node `v24.21.0`, and locked viem `2.56.9`:\n\n| Check | Exit | Result |\n|---|---:|---|\n| `npm ci --ignore-scripts` | 0 | Locked dependencies installed |\n| `node scripts/review-tests.mjs --check` |","treeHash":null,"usage":{"cachedInputTokens":2760192,"inputTokens":184805,"model":"gpt-6-astra","outputTokens":15684,"runtime":"codex","turns":8,"wallClockMs":673099}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"699fb03b7f482edb","findings":[{"citation":"resolved","description":"OPEN, new reverse-control counterexample related to latest Audit8 finding 2. Exact pin 347268a7ecae700088547c2402db9a3eb07a6fd2. start()/stop() reset observedAccount but retain s.account. On restarting the same AuthClient after the wallet changed while unsubscribed, non-discovery bind records eth_accounts=B in observedAccount but refuses to replace non-null s.account=A. The current lifetime therefore has two contradictory wallet identities. With cookie A, the UI misses the required mismatch; with cookie B, a subsequent genuinely observed B-to-A event is discarded by accountChanged's a===s.account early return (line 584), leaving session B live instead of sending its address-conditional cleanup. This is a client lifecycle/cleanup violation, not a demonstrated server authentication bypass or fund loss. It blocks complete SOURCE-CLOSURE of the required restart/genuine-switch controls despite the original first-unlock reproduction being fixed. The stale displayed-account behavior predates this patch; this finding does not assert that Submission9 introduced it. Minimal repair: reconcile/reset the displayed wallet account on each binding/lifetime, including empty eth_accounts replies, without deriving cleanup authority from the cookie or prior lifetime. Preserve current binding/event fences. Prior: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md (#2; SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc). REVIEWER offline actual AuthClient/Worker/node:sqlite measurement, Node 24.21.0 and locked real viem 2.56.9; no real browser or production claim. Reproduction JavaScript SHA256 (UTF-8 including final newline): 914b22440ba06ab15ec8fb77ea5c8334f9dae6cd78b7ad12ce9c9cddd475624b.","line":290,"path":"source/src/world/auth.ts","reproduction":"Run from source/ after npm ci --ignore-scripts, using node --input-type=module and the following stdin. Exit 0 asserts the defect. Sequence: sign in A; start and observe A; stop; switch provider to B while unsubscribed; sign in B using the same synthetic browser jar (another-context login); restart the same client; await both fresh eth_accounts=B and canonical cookie-B restoration; emit accountsChanged(A). Expected before event: account B/session B. Expected after event: exactly one expectedAddress=B logout, B session revoked, A session unaffected. Actual: account stays A after restart, so the B-to-A event returns at line 584; zero logout, prompts, new challenge/verify or broadcast. SQLite sessions created/live/revoked=2/2/0; challenges total/used/pending/invalidated=2/2/0/0, unchanged by the event. Setup has two real local challenge/sign/verify pairs; no live signatures. Empty-world fixture requires zero ownership RPCs; two index reads/budget charges across the two restored addresses, zero added by the event. The script prints actual relevant session/challenge rows (no tokens or keys).\n\nActual measured rows from one run: sessions (nonce,address,expires_at,revoked_at) = (c9ec1db0705ebd5aee02e73ca9cb8e84,0xfd9cdee08c6d5a4ca6b15f490e3e6c4c3e90adea,1791201600000,NULL) and (43db2e54cc16b70b4bf2e558224c300b,0x4155be26de5b9ed4a425e73bb71b2354dd93f308,1791201600000,NULL). Both matching login_challenges have used_at=1790596800000 and invalidated_at=NULL. These are generated offline fixture identities, not production records. Independent reverse control using a new AuthClient instead of restarting the old instance produced one logout and sessions created/live/revoked=2/1/1.\n\nimport assert from 'node:assert/strict';\nimport {setup,newAccount,provider,tab,ready,until,flush,rows,logouts,routeEvents,prompts} from './tests/auth-r7-fixtures.mjs';\nconst w=setup(), A=newAccount(), B=newAccount(), b=w.browser(), p=provider(A);\nlet budget=0;w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\nassert.equal((await b.signIn(A)).verify.status,200);\nconst q=tab(w,b,p);await ready(q);\nq.stop();p.switchTo(B);\nassert.equal((await b.signIn(B)).verify.status,200);\nq.restart();await until(()=>q.c.state.session?.address===B.address.toLowerCase()&&!q.c.state.checking);\nassert.equal(p.calls.filter(m=>m==='eth_accounts').length,2);\nassert.equal(q.c.state.session.address,B.address.toLowerCase());\nassert.equal(q.c.state.account,A.address.toLowerCase());\nconst before=rows(w),calls=w.chain.state.calls.length;assert.equal(budget,2);\np.switchTo(A);await flush(50);\nassert.deepEqual(rows(w),before);\nassert.equal(logouts(q).length,0);\nassert.equal(q.c.state.session.address,B.address.toLowerCase());\nassert.equal(w.chain.state.calls.length,calls);\nassert.equal(prompts([p]),0);\nassert.equal(routeEvents(q,'/api/auth/challenge').length,0);\nassert.equal(routeEvents(q,'/api/auth/verify').length,0);\nconsole.log(JSON.stringify({rows:rows(w),account:q.c.state.account,session:q.c.state.session.address,logouts:logouts(q),broadcast:q.channels.flatMap(c=>c.messages),budget,index:calls,rpc:w.chain.state.calls.filter(c=>c.body).length}));\nq.stop();\n","severity":"low","snippet":"      }else if(a){this.observedAccount=a;if(!this.s.account){this.lifecycle.discovered(p,a);this.set({account:a});}}","title":"Restart retains the prior wallet account and suppresses genuine account-switch cleanup"},{"citation":"resolved","description":"PARTIAL/REOPENED latest Audit8 #2. Pin 347268a7ecae700088547c2402db9a3eb07a6fd2. wasFlow at line 587 includes every retained owner, including one from a stopped lifetime with its nonce cleanup still pending. start() correctly clears observedAccount, but the first accountsChanged(C) in the new lifetime nevertheless gets displayed-session cleanup authority over newly restored cookie A via wasFlow. Reproduced with account=null before the event, so this is independent of the stale displayed-account finding. The old B owner is retained only to complete its own nonce-fenced cleanup; it must not authorize revocation of A, whose verify belongs to another flow. The Worker correctly enforces expectedAddress=A, but the client should never send that assertion here. SOURCE-CLOSURE blocker for unintended session revocation / old-flow cross-context authority, even though the ordinary cookie-A/locked[]/first-B case passes. No fund loss or cryptographic bypass is demonstrated. Minimal repair: separate current click/lifetime account-change authority from detached retained-owner cleanup responsibility; preserve the old owner's nonce-specific cleanup and do not turn an unobserved first account into a displayed-session switch. Prior immutable Audit8 #2: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md (SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc). REVIEWER local actual AuthClient/Worker/SQLite reproduction with real locked viem 2.56.9 and Node 24.21.0; extension/browser/production scheduling is unmeasured. Reproduction JavaScript SHA256 (UTF-8 including final newline): bdee49b0d2d73ace9f372b5afad00caf78b2ca3a1cefcde418bab330cf716e00.","line":589,"path":"source/src/world/auth.ts","reproduction":"Run from source/ using node --input-type=module with the stdin below. Exit 0 asserts the defect and a no-event control. Event order: B sign-in commits and sets cookie B while its fetch result is withheld; lock the provider; stop the client, retaining B's cleanup owner and holding its expectedNonce=B logout before Worker execution; another synthetic context signs in A using the shared jar; restart the same client with eth_accounts=[]; wait for canonical A and home; deliver this lifetime's first accountsChanged(C). Expected: retain A, show mismatch, no new logout or signed-out hint; old B cleanup remains bound to its nonce. Actual: a second logout with expectedAddress=A returns 204, clears the A cookie, and revokes A's row. Before/after SQLite sessions created/live/revoked=2/2/0 -> 2/1/1; B revoked_at remains NULL, A revoked_at=1790596800000. Both challenge rows remain used_at=1790596800000, invalidated_at=NULL; total/used/pending/invalidated=2/2/0/0. The control omitting the first C event keeps both rows live. Counts before releasing the held requests: one old pending nonce logout plus one new address logout (control: only the old pending logout); one signed-out broadcast (control zero); one client prompt/challenge/verify from B setup, plus one direct local A challenge/verify; the C event adds zero prompts/challenges/verifies/index/budget/RPC. Total index/budget/RPC=1/1/0. Actual relevant rows are printed; no tokens/keys are printed. This is a response-delivery/cleanup-delay counterexample, not a live D1 concurrency measurement.\n\nActual measured event-case rows: old B nonce=61deeb0119a2a8b26217b88b48781331,address=0x3d92915825a39200aa51467e692617f3512be3a9; new A nonce=b38250698d0cb2b1aba3ea6760efe215,address=0x2b5a3650e876290db6041da8a2923ab01c0a1547. Both sessions expires_at=1791201600000. The first C event left B revoked_at=NULL and changed A revoked_at from NULL to 1790596800000. Both corresponding login_challenges stayed used_at=1790596800000,invalidated_at=NULL. These are generated offline fixture identities, not production records.\n\nimport assert from 'node:assert/strict';\nimport {setup,newAccount,provider,tab,ready,defer,until,flush,rows,logouts,prompts,routeEvents} from './tests/auth-r7-fixtures.mjs';\nfor(const emit of [false,true]){\n const w=setup(),A=newAccount(),B=newAccount(),C=newAccount(),b=w.browser(),p=provider(B),v=defer(),d=defer();let committed=false,held=false,budget=0;\n w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\n const q=tab(w,b,p,{beforeSend:async(path,init)=>{if(path==='/api/auth/logout'&&JSON.parse(init.body).expectedNonce){held=true;await d.promise;}},intercept:async(path,r)=>{if(path==='/api/auth/verify'){committed=true;await v.promise;}return r;}});\n await ready(q);const flow=q.signIn();await until(()=>committed);p.switchTo(null);q.stop();await until(()=>held);\n assert.equal((await b.signIn(A)).verify.status,200);q.restart();await until(()=>q.c.state.session?.address===A.address.toLowerCase()&&!q.c.state.checking);\n assert.equal(q.c.state.account,null);\n const before=rows(w),chainBefore=w.chain.state.calls.length;\n if(emit){p.switchTo(C);await until(()=>logouts(q).some(e=>e.addressAssertion&&e.finished));}await flush(30);\n const after=rows(w),a=after.sessions.find(s=>s.address===A.address.toLowerCase());\n assert.equal(a.revoked_at,emit?w.clock.now():null);\n assert.equal(logouts(q).filter(e=>e.addressAssertion).length,emit?1:0);\n assert.equal(w.chain.state.calls.length,chainBefore);\n console.log(JSON.stringify({emit,before,after,prompts:prompts([p]),challenge:routeEvents(q,'/api/auth/challenge').length,verify:routeEvents(q,'/api/auth/verify').length,logout:logouts(q).map(({assertedNonce,...e})=>e),broadcast:q.channels.flatMap(c=>c.messages),budget,index:w.chain.state.calls.filter(c=>!c.body).length,rpc:w.chain.state.calls.filter(c=>c.body).length}));\n d.resolve();v.resolve();await flow;await flush(30);q.stop();\n}\n","severity":"low","snippet":"    const other=!!a&&!!this.s.session&&this.s.session.address!==a&&(wasFlow||changed);","title":"A retained verify owner from a stopped lifetime authorizes logout on the new lifetime's first wallet observation"},{"citation":"resolved","description":"OPEN, additional output-sink counterexample related to latest Audit8 #4. Exact pin 347268a7ecae700088547c2402db9a3eb07a6fd2. createArtifactStore.write now rejects dangling/existing final file links, but the supported replay CLI imports only sanitizeArtifact and calls writeFileSync(out, ...) directly when replay fails with an invariant and --minimize is present. A pre-existing dangling core500-failure-minimized.json link therefore creates its target outside the configured scheduler directory; the output entry remains a symlink. It also bypasses the source/tmp path restriction, exclusive sibling creation, fsync and safe replacement. This is local opt-in artifact hygiene (Info), not a remote Worker/wallet exploit; default scheduler output remains disabled. It prevents declaring the artifact/runner containment requirement completely closed. Minimal repair: route CLI minimization output through the same validated artifact-store writer, retaining source/tmp containment and rejecting nonregular final entries; keep the original replay file separate. The existing 13-group verifier and 18 artifact tests exercise the store rather than this CLI write sink. Prior immutable Audit8 #4: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md (SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc). Assumes the same preplanted-link case as the original finding; no hostile concurrent ancestry race or SMB/NFS claim. REVIEWER measured Linux real-file-symlink reproduction. All physical probe files stay below the real checkout source/tmp and are removed; the nested synthetic source models the prohibited outside-root destination. Reproduction JavaScript SHA256 (UTF-8 including final newline): 3686121bfe5dfbce44c018268c835eeb8759c73dfdf37ed1ed0d8526272c7271.","line":13,"path":"source/scripts/replay-auth-trace.mjs","reproduction":"After npm ci --ignore-scripts, run this from source/ via node --input-type=module stdin. Outer script exits 0 asserting the defect; CLI correctly reports the deliberately introduced HARNESS-CAUSAL failure and exits 1, but nevertheless follows the final link. Generate real runSeed(0) trace (46 actions, 10 Worker calls), append a duplicate already-completed worker action (47 actions) to reach the ordinary failure/minimization branch, and save through the real sanitizer/store. Create S/tmp/store/core500-failure-minimized.json -> S/outside.json with absent target; S is nested under the real source/tmp. The repaired store rejects the same symlink. CLI --replay S/tmp/store/core500-failure-original.json --minimize S/tmp/store/core500-failure-minimized.json instead creates S/outside.json and leaves the link intact. Output contains invariant HARNESS-CAUSAL and minimization.reproduced=true (measured attempts=14, invalid=5). Expected: reject the linked final entry and leave outside target absent. This intentional invalid replay is solely a sink-reachability control, not an asserted Auth regression or an empty successful replay. The path mechanism itself uses no auth/session/ownership authority; replay uses fresh ephemeral fixture databases, no persistent M1 or production rows, and its internal prompt/challenge/verify counts are not evidence of this filesystem defect.\n\nimport assert from 'node:assert/strict';\nimport {mkdirSync,mkdtempSync,symlinkSync,readFileSync,existsSync,lstatSync,rmSync} from 'node:fs';\nimport {resolve,join} from 'node:path';\nimport {spawnSync} from 'node:child_process';\nimport {runSeed} from './tests/auth-scheduler-driver.mjs';\nimport {createArtifactStore} from './tests/auth-artifacts.mjs';\nmkdirSync('tmp',{recursive:true});const root=mkdtempSync(resolve('tmp/reviewer-replay-'));\ntry{\n const trace=await runSeed(0,{retainTrace:true});assert.ok(trace.metrics.workerCalls>=2);\n trace.actions.push({type:'worker',id:trace.actions.find(a=>a.type==='worker').id});\n const store=createArtifactStore({sourceDir:root,requestedDir:'tmp/store'});\n store.write('core500-failure-original.json',{trace});\n const input=join(store.directory,'core500-failure-original.json'),out=join(store.directory,'core500-failure-minimized.json'),outside=join(root,'outside.json');\n symlinkSync(outside,out,'file');assert.equal(existsSync(outside),false);\n assert.throws(()=>store.write('core500-failure-minimized.json',{}),/not a regular file/);\n const cli=spawnSync(process.execPath,['scripts/replay-auth-trace.mjs','--replay',input,'--minimize',out],{encoding:'utf8'});\n assert.equal(cli.status,1);assert.equal(existsSync(outside),true);assert.ok(lstatSync(out).isSymbolicLink());\n const saved=JSON.parse(readFileSync(outside,'utf8'));\n console.log(JSON.stringify({exit:cli.status,stderr:cli.stderr.trim(),createdOutsideConfiguredStore:true,outputStillSymlink:true,invariant:saved.invariant,workerCalls:trace.metrics.workerCalls,actions:trace.actions.length,minimized:saved.trace.minimization}));\n}finally{rmSync(root,{recursive:true,force:true});}\n","severity":"info","snippet":"    const minimized=await (audit8?minimizeAudit8Failure:minimizeFailure)(error.schedulerTrace??trace,error.invariant);writeFileSync(out,JSON.stringify(sanitizeArtifact({invariant:error.invariant,trace:minimized}),null,2)+'\\n');}","title":"Replay minimization bypasses the repaired artifact store and follows a dangling output symlink"}],"hash":"1f054b575f831f7721e3d25b8b1d5af449cd171b349155cc969e36d93e366326","nodeId":"2f9b6248-f61c-4195-a9ba-f99a0cab1598","outcome":"completed","summary":"**This candidate does not fully close Audit8.** I substantiated **two Low findings and one Info finding**, saved with complete executable reproductions in [.imd-findings.json](/home/imd/.identitymd/work/d76a2a79-7394-420a-99d2-df2ba6a23f45/2f9b6248-f61c-4195-a9ba-f99a0cab1598/.imd-findings.json). No source or configuration files changed.\n\n| Verdict | Result | Basis |\n|---|---|---|\n| SOURCE-CLOSURE | **BLOCKED** | Restart and detached-cleanup cases violate Auth requirements; replay minimization bypasses artifact containment. |\n| RELEASE-READINESS | **BLOCKED** | The Auth findings remain release blockers. Independent production/build correspondence remains **UNKNOWN**; supplied deployment measurements are TEAM evidence. |\n\nReview pin: `347268a7ecae700088547c2402db9a3eb07a6fd2`. The comparison is the latest [Audit8 original](https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md), whose downloaded SHA-256 matched `8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc`.\n\n| Finding | Severity / status | Pinned location | Concrete failure and required correction |\n|---|---|---|---|\n| F1: Restart retains an obsolete displayed wallet account | **Low — open** | [auth.ts:290](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L290) | Restart records `eth_accounts=B` internally but preserves displayed account A. A subsequent genuine B→A event hits the repeated-account early return and omits session-B cleanup. Reconcile displayed account state when binding a new lifetime. |\n| F2: Detached cleanup gives the first wallet observation logout authority | **Low — partial/reopened Audit8 #2** | [auth.ts:589](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L589) | An old lifetime’s retained B verification owner makes `wasFlow` true. After restart with locked provider and restored cookie A, the first observed C revokes A. Separate current-lifetime switch authority from detached nonce-cleanup responsibility. |\n| F3: Replay minimization follows a dangling output link | **Info — open, related to Audit8 #4** | [replay-auth-trace.mjs:13](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/scripts/replay-auth-trace.mjs#L13) | The CLI directly calls `writeFileSync(out, ...)`, bypassing the repaired artifact store. A dangling minimized-output link creates its external target. Use the validated artifact writer for this output too. |\n\nF1 and F2 block the requested Auth closure despite their Low severity. Neither demonstrates a cryptographic bypass or fund loss. F3 concerns explicitly enabled local artifacts; it does not independently demonstrate a remote release exploit.\n\nThe five prior mechanisms have these bounded dispositions:\n\n| Latest Audit8 finding | Disposition | Fresh reviewer evidence |\n|---|---|---|\n| #1 Low: post-D1 proof expiry | **Fixed within tested boundaries** | Age 29,999 ms returns 200; 30,000/30,001 ms return `503 OWNERSHIP_UNAVAILABLE`. Later recovery rejects the sold seat. Rollback, NaN, Infinity and refused/failed-lane controls pass. |\n| #2 Low: first locked-provider observation | **Partial** | Original cookie-A/locked[]/first-B case passes, as do supplied genuine-switch controls. F1/F2 expose additional restart failures. |\n| #3 Low: slow fresh overlap | **Fixed within tested boundaries** | Twenty early joined requests at four pages × 7,475/7,500 ms share one admission, four pages, one budget charge, one proof and one epoch. Independent-context, failure and later-retry controls pass. |\n| #4 Info: dangling artifact final link | **Partial across artifact entry points** | The store rejects the original escape and passes replacement controls. F3 reaches an unprotected CLI output path. |\n| #5 Info: diagnostic path redaction | **Fixed within tested fo","treeHash":null,"usage":{"cachedInputTokens":4396032,"inputTokens":212744,"model":"gpt-6-astra","outputTokens":27379,"runtime":"codex","turns":8,"wallClockMs":1074316}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7989aa5d838d11d9","findings":[{"citation":"resolved","description":"OPEN new scoped temporal counterexample adjacent to latest Audit8 #1 (https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md). The strict 30-second proof recheck at line 354 fixes the prior sold-seat reproduction, but the final eligibility computation and the no-eligible-seat lane predicate at line 349 still use req.now from before awaits. A registered offline seat whose owner-specific D1 sighting expires during enrichment still grants eligible=1/size=s. Low: inaccurate household authority; no forged ownerOf, asset transfer or unauthorized M1 mutation demonstrated. SOURCE-CLOSURE blocker for current eligibility. Evaluate the final counting/lane predicate at the live post-wait clock while preserving the inclusive 24-hour comparator and original proof/index timestamps. This is scoped pre-existing behavior, not asserted to have been introduced by this patch. Independently reproduced on public 347268a7ecae700088547c2402db9a3eb07a6fd2, Linux Node v24.21.0, real locked viem 2.56.9, actual Worker and migration-backed node:sqlite; production D1 timing, full frontend and real wallets remain unmeasured. Exact source SHA256: db7c60509de363c925c938c44ba7e40721a1271f6e558e62e2022abbb2b3c317. The immutable prior Audit8 document was independently fetched: 32883 bytes, SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc.","line":355,"path":"source/server/ownership.ts","reproduction":"From source/ in a fresh exact public checkout, after npm ci --ignore-scripts, run the JavaScript below with node --input-type=module on stdin. REVIEWER exit 1 at the final expected-behavior assertion (actual 1, expected 0); earlier assertions verify the boundary controls. Request starts t=1790596800000 with seat 7/agent707 offline, ownerOf=A, last_online_at=t-86400000+1. Delay the real sightings read by advancing the injected clock 2ms. Actual HTTP200/eligible1/counts=true/size=s at sighting age86400001ms, with proof age2ms and checkedAt=t/block21000000. Immediate second request returns eligible0 without another index/budget/RPC. Delays0/1/2/5000ms yield ages86399999/86400000/86400001/86404999, first eligible1/1/1/1 and next1/1/0/0. Each run has setup challenge/verify1/1; home2; prompt/logout/hint0/0/0; index/budget/ownerOf eth_call1/1/1 and no eth_getCode. Actual 2ms SQLite rows: seat_presence=(7,0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a,1790510400001,1790510400001); session nonce=c4f58a8e817fd3b8cbc4dfa8e5e5e118,address=A,expires_at=1791201600000,revoked_at=NULL; matching challenge used_at=1790596800000,invalidated_at=NULL. Counts created/live/revoked1/1/0; challenges total/used/pending/invalidated1/1/0/0. Nonces vary on rerun. No member writes.\n\nimport assert from 'node:assert/strict';\nimport {privateKeyToAccount} from 'viem/accounts';\nimport {setup,fakeChain,fakeImd,rows} from './tests/auth-r7-fixtures.mjs';\nconst A=privateKeyToAccount('0x'+'11'.repeat(32)),a=A.address.toLowerCase();\nconst observed=[];\nfor(const delay of [0,1,2,5000]){\n  const owners=[];owners[7]=a;\n  const w=setup({chain:fakeChain({owners:{7:a}}),imd:fakeImd({seats:{7:'707'},owners,online:[]})}),b=w.browser();\n  let budget=0;w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\n  assert.equal((await b.signIn(A)).verify.status,200);\n  const t=w.clock.now(),seen=t-86400000+1;\n  w.db.raw.prepare('INSERT INTO seat_presence(token_id,owner,last_online_at,updated_at) VALUES(7,?,?,?)').run(a,seen,seen);\n  const prepare=w.db.prepare.bind(w.db);let once=true;\n  w.db.prepare=sql=>{const wrap=s=>({...s,bind:(...args)=>wrap(s.bind(...args)),all:async()=>{\n    if(once&&sql.startsWith('SELECT token_id,last_online_at')){once=false;w.clock.advance(delay);}return s.all();\n  }});return wrap(prepare(sql));};\n  const response=await b.get('/api/me/home'),home=await response.json();\n  const next=await(await b.get('/api/me/home')).json();\n  const result={delay,http:response.status,now:w.clock.now(),sightingAge:w.clock.now()-seen,home,next,\n    rows:rows(w),presence:w.db.raw.prepare('SELECT * FROM seat_presence').all(),\n    counts:{setupChallenge:1,setupVerify:1,home:2,prompt:0,logout:0,hint:0,\n      index:w.chain.state.calls.filter(c=>!c.body).length,budget,rpc:w.chain.state.calls.filter(c=>c.body).length},\n    rpcMethods:w.chain.state.calls.filter(c=>c.body).map(c=>JSON.parse(c.body).method)};\n  console.log(JSON.stringify(result));observed.push(result);\n  assert.equal(home.eligible,1);assert.equal(next.eligible,delay<=1?1:0);\n  assert.equal(home.checkedAt,t);assert.equal(budget,1);\n}\nassert.equal(observed[2].home.eligible,0,'offline sighting older than 24h must not count after D1 await');\n\n\nReproduction JavaScript SHA256 (UTF-8 including final newline): 4e684e1f923761730153a3354954c00f810297dddf655632be4024489fad1634.","severity":"low","snippet":"    const seats=proof.ids.map(id=>this.status(id,world.agents.get(id),seen.get(id),req.now)),eligible=seats.filter(s=>s.counts).length;","title":"Post-await household eligibility uses request-start time and counts sightings older than 24 hours"},{"citation":"resolved","description":"OPEN, merged duplicate restart claims from math/economics/permissions/flow. Related to latest Audit8 #2 (https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md). start()/stop() reset observedAccount but preserve s.account. The non-discovery bind reply records B internally but only updates the public/lifecycle account when the old public account is falsy; an empty reply is ignored. After observing A, stopping, changing to B while unsubscribed and restarting the same instance, account remains A and statusOf/ownerAddress reports owner for cookie A. An explicit signIn takes the stale same-session fast path. A fresh instance correctly shows B/mismatch and preserves A. Low: incorrect wallet context/owner UI, no server authentication bypass, funds or unauthorized profile mutation demonstrated. SOURCE-CLOSURE blocker for requested restart/context controls. Reconcile public/lifecycle account with the first fenced reply of each lifetime, including empty replies, without deriving automatic logout authority from cookie/prior-lifetime A. Preserve binds/accountEvents and nonce cleanup fences. Existing auth-audit8.test.mjs same-client-restart emits an extra accountsChanged([]) after restart, masking the missing initial update. This scoped behavior predates the patch. REVIEWER Linux Node v24.21.0/viem2.56.9/actual AuthClient, Worker and SQLite at public347268a7ecae700088547c2402db9a3eb07a6fd2; actual production React remount/browser/provider scheduling is unknown. Independently reproduced a second consequence of the same split identity: after another context installs cookie B while stopped, restart observes B internally but leaves public A; a subsequent genuine B-to-A event returns at accountChanged line584 (a===s.account), suppressing B's required conditional cleanup. This is merged here because reconciling the binding account fixes both symptoms. Exact source SHA256: e8b6b2433fb2b8bc64f4ec8e03a2c5049d02cba42ea15ff5556a923846d12829. The immutable prior Audit8 document was independently fetched: 32883 bytes, SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc.","line":290,"path":"source/src/world/auth.ts","reproduction":"Run the following stdin with node --input-type=module from source/ after fresh locked npm ci --ignore-scripts. REVIEWER exit1: expected B, actual A. Order: local A login; observe A/seat7 owner; stop; switch provider B; restart same instance; await its eth_accounts(B)/cookie restoration; explicit signIn. Actual A/owner before and after click; fresh-client control B/mismatch. With provider locked while stopped, restarted account likewise remains A instead of null. Actual B-case before/after SQLite session=(noncec66d01ab1e646725c8f7a0c7dcdcfbb8,address0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a,expires_at1791201600000,revoked_atNULL), challenge=(same nonce,used_at1790596800000,invalidated_atNULL). Created/live/revoked1/1/0 and challenges total/used/pending/invalidated1/1/0/0 remain unchanged. Setup challenge/verify1/1. Measured client prompt/connect/challenge/verify/logout/broadcast0/0/0/0/0/0; sessionGET3/homeGET2 after click; hint writes2 at restart,3 including click; index/budget/ownerOfRPC1/1/1. The locked variant has sessionGET2/homeGET1/hint writes2, otherwise same counts. Cleanup plan stop:none. Synthetic identities only; no member writes.\n\nimport assert from 'node:assert/strict';\nimport {privateKeyToAccount} from 'viem/accounts';\nimport {setup,provider,tab,ready,until,flush,rows,logouts,prompts,routeEvents,fakeImd,fakeChain,statusOf} from './tests/auth-r7-fixtures.mjs';\nconst A=privateKeyToAccount('0x'+'11'.repeat(32)),B=privateKeyToAccount('0x'+'22'.repeat(32));\nconst a=A.address.toLowerCase(),bb=B.address.toLowerCase(),results=[];\nfor(const next of [B,null]){\n const owners=[];owners[7]=a;\n const w=setup({chain:fakeChain({owners:{7:a}}),imd:fakeImd({seats:{7:'707'},owners,online:[7]})}),b=w.browser(),p=provider(A);\n let budget=0;w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\n assert.equal((await b.signIn(A)).verify.status,200);\n const q=tab(w,b,p);let hintWrites=0;const set=q.c.deps.hint.set;q.c.deps.hint.set=v=>{hintWrites++;set(v);};\n try{\n  await ready(q);assert.equal(statusOf(q.c.state,w.clock.now()),'owner');\n  const before=rows(w);q.stop();p.switchTo(next);q.restart();await until(()=>routeEvents(q,'/api/auth/session').filter(e=>e.finished).length===2&&!q.c.state.checking);await flush();\n  const restored={account:q.c.state.account,session:q.c.state.session?.address,status:statusOf(q.c.state,w.clock.now())};\n  const hintsAtRestart=hintWrites;\n  if(next){await q.signIn();await flush(20);}\n  const counts={setupChallenge:1,setupVerify:1,prompt:prompts([p]),connect:p.calls.filter(x=>x==='eth_requestAccounts').length,\n   session:routeEvents(q,'/api/auth/session').length,home:q.events.filter(e=>e.kind==='route'&&e.path.startsWith('/api/me/home')).length,\n   challenge:routeEvents(q,'/api/auth/challenge').length,verify:routeEvents(q,'/api/auth/verify').length,\n   logout:logouts(q).length,broadcast:q.channels.flatMap(c=>c.messages).length,hintWrites,hintsAtRestart,\n   index:w.chain.state.calls.filter(c=>!c.body).length,budget,rpc:w.chain.state.calls.filter(c=>c.body).length};\n  const fresh=tab(w,b,p);await until(()=>fresh.c.state.restored&&!fresh.c.state.checking);await flush();\n  const control={account:fresh.c.state.account,status:statusOf(fresh.c.state,w.clock.now())};fresh.stop();\n  assert.equal(control.account,next?bb:null);if(next)assert.equal(control.status,'mismatch');\n  assert.deepEqual(rows(w),before);assert.equal(logouts(q).length,0);\n  const result={provider:next?bb:null,before,after:rows(w),restored,afterClick:statusOf(q.c.state,w.clock.now()),counts,control,plans:q.c.lifecycleSnapshot.cleanupPlans};\n  results.push(result);console.log(JSON.stringify(result));\n }finally{q.stop();}\n}\nassert.equal(results[0].restored.account,bb,'restart must adopt the current binding account');\nassert.equal(results[0].restored.status,'mismatch');\nassert.equal(results[1].restored.account,null);\n\n\nAdditional merged cleanup reproduction (same environment, node --input-type=module stdin","severity":"low","snippet":"      }else if(a){this.observedAccount=a;if(!this.s.account){this.lifecycle.discovered(p,a);this.set({account:a});}}","title":"Restarted AuthClient retains a prior wallet account instead of its current eth_accounts reply"},{"citation":"resolved","description":"PARTIAL/REOPENED latest Audit8 #2 (https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md). wasFlow at line587 treats every retained owner as current account-change intent, including detached cleanup from a stopped lifetime. After cookie A is restored with no observed account in the restarted lifetime, first accountsChanged(C) acquires displayed-session cleanup authority from the old B owner. planCleanup then prioritizes displayed A and sends expectedAddress=A, revoking A instead of preserving it as mismatch. The Worker correctly enforces the assertion; the client should not issue it. This reproduces with public account=null before the event, independent of the separate stale-account finding. Low: unintended logout/old-flow cross-context authority, no signature bypass or fund loss demonstrated. SOURCE-CLOSURE blocker under the specified no-old-flow-cross-revocation requirement. Separate current lifetime/click switch authority from old retained nonce-cleanup responsibility, retaining B's conditional cleanup and binding/generation guards. Fresh REVIEWER Linux Nodev24.21.0, real locked viem2.56.9, actual AuthClient/Worker/node:sqlite at public347268a7ecae700088547c2402db9a3eb07a6fd2; real extension/browser/D1/production scheduling remains unknown. Exact source SHA256: e8b6b2433fb2b8bc64f4ec8e03a2c5049d02cba42ea15ff5556a923846d12829. The immutable prior Audit8 document was independently fetched: 32883 bytes, SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc.","line":589,"path":"source/src/world/auth.ts","reproduction":"From source/ of the fresh pin after npm ci --ignore-scripts, run the following stdin with node --input-type=module. REVIEWER exit1 at expected A.revoked_at=NULL, actual1790596800000; a no-event control passes. Order: client B verify commits and sets cookie B but its fetch result is held; lock; stop, holding nonce-B cleanup before Worker execution; direct local other-context A login sets the shared cookie; restart with eth_accounts=[]; canonical A/home complete with account=null; deliver first accountsChanged(C). Expected preserve A/mismatch, no new logout/hint, B cleanup remains nonce-specific. Actual new expectedAddress=A logout204 with cookie clear, signed-out broadcast, A row revoked. Event-case actual rows before/after: B=(nonce dd435a8374693913f7e1f63e0c9f1401,address0x1563915e194d8cfba1943570603f7606a3115508,expires_at1791201600000,revoked_atNULL unchanged); A=(nonce0c89a82516064a909f02854db4035d41,address0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a,expires_at1791201600000,revoked_atNULL ->1790596800000). Both challenges retain used_at1790596800000/invalidated_atNULL. Sessions created/live/revoked2/2/0 ->2/1/1; challenges total/used/pending/invalidated2/2/0/0 unchanged. Before releasing old gates: prompt/client challenge/client verify1/1/1 plus direct A challenge/verify1/1; sessionGET3/homeGET1; logout2 (old pending nonce+new completed address) versus control1; broadcasts1 versus0; hint writes2 versus1; index/budget/RPC1/1/0, event adds none. No M1 rows touched. All identities/signatures are offline synthetic.\n\nimport assert from 'node:assert/strict';\nimport {privateKeyToAccount} from 'viem/accounts';\nimport {setup,provider,tab,ready,defer,until,flush,rows,logouts,prompts,routeEvents} from './tests/auth-r7-fixtures.mjs';\nconst A=privateKeyToAccount('0x'+'11'.repeat(32)),B=privateKeyToAccount('0x'+'22'.repeat(32)),C=privateKeyToAccount('0x'+'33'.repeat(32));\nconst results=[];\nfor(const emit of [false,true]){\n const w=setup(),b=w.browser(),p=provider(B),v=defer(),d=defer();let committed=false,held=false,budget=0;\n w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\n const q=tab(w,b,p,{beforeSend:async(path,init)=>{\n   if(path==='/api/auth/logout'&&JSON.parse(init.body).expectedNonce){held=true;await d.promise;}\n  },intercept:async(path,r)=>{if(path==='/api/auth/verify'){committed=true;await v.promise;}return r;}});\n let hintWrites=0;const set=q.c.deps.hint.set;q.c.deps.hint.set=x=>{hintWrites++;set(x);};\n await ready(q);const flow=q.signIn();await until(()=>committed);\n p.switchTo(null);q.stop();await until(()=>held);\n assert.equal((await b.signIn(A)).verify.status,200);\n q.restart();await until(()=>q.c.state.session?.address===A.address.toLowerCase()&&!q.c.state.checking);\n assert.equal(q.c.state.account,null);\n const before=rows(w),chainBefore=w.chain.state.calls.length,hintsBefore=hintWrites;\n if(emit){p.switchTo(C);await until(()=>logouts(q).some(e=>e.addressAssertion&&e.finished));}\n await flush(30);\n const after=rows(w),a=after.sessions.find(s=>s.address===A.address.toLowerCase());\n assert.equal(a.revoked_at,emit?w.clock.now():null);\n assert.equal(logouts(q).filter(e=>e.addressAssertion).length,emit?1:0);\n assert.equal(w.chain.state.calls.length,chainBefore);\n const result={emit,before,after,client:{account:q.c.state.account,session:q.c.state.session?.address??null},\n  counts:{prompt:prompts([p]),challenge:routeEvents(q,'/api/auth/challenge').length,verify:routeEvents(q,'/api/auth/verify').length,\n   directChallenge:1,directVerify:1,logout:logouts(q).length,hintWrites,hintsBefore,\n   session:routeEvents(q,'/api/auth/session').length,home:q.events.filter(e=>e.kind==='route'&&e.path.startsWith('/api/me/home')).length,\n   budget,index:w.chain.state.calls.filter(c=>!c.body).length,rpc:w.chain.state.calls.filter(c=>c.body).length},\n  logout:logouts(q).map(({assertedNonce,...e})=>e),broadcast:q.channels.flatMap(c=>c.messages),plans:q.c.lifecycleSnapshot.cleanupPlans};\n console.","severity":"low","snippet":"    const other=!!a&&!!this.s.session&&this.s.session.address!==a&&(wasFlow||changed);","title":"A stopped lifetime's retained verify owner authorizes revocation on the new lifetime's first wallet observation"},{"citation":"resolved","description":"OPEN additional sink counterexample related to latest Audit8 #4 (https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md). createArtifactStore now rejects dangling/existing final links, but the supported replay CLI imports only its sanitizer and writes --minimize directly with writeFileSync(out,...). A preplanted dangling minimized-output symlink is followed and its outside target created. This bypasses final-entry validation, source/tmp containment, exclusive sibling temp/fsync and validated replacement. Info: local opt-in artifact hygiene, not a remote Worker/wallet exploit; default scheduler persistence remains disabled. SOURCE-CLOSURE blocker for complete artifact/runner containment. Use the validated artifact-store writer for CLI minimization, preserving separate replay input and rejecting nonregular/linked outputs. Assumes the same pre-existing link case as Audit8; no hostile concurrent ancestry-swap or SMB/NFS claim. Fresh REVIEWER real Linux file-symlink reproduction, Nodev24.21.0/locked viem2.56.9 at public347268a7ecae700088547c2402db9a3eb07a6fd2. All physical probe files remain beneath the actual checkout source/tmp and are removed; a nested synthetic source models the prohibited destination. Exact source SHA256: 6559bd435d66d275dbea845fd14ef1cd20ab75a30aa2fd9c185843442c2888db. The immutable prior Audit8 document was independently fetched: 32883 bytes, SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc.","line":13,"path":"source/scripts/replay-auth-trace.mjs","reproduction":"After fresh locked npm ci --ignore-scripts, run the following via node --input-type=module stdin from source/. REVIEWER outer exit1 at expected outside-target-absent assertion; replay child exit1 is the deliberately triggered HARNESS-CAUSAL invariant, not an environmental failure. Generate real runSeed(0) trace (46 actions/10 Worker calls); append duplicate completed worker action (47 actions) to reach failure/minimization; save original through real store. In synthetic source S, preplant S/tmp/store/core500-failure-minimized.json -> S/outside.json, target absent. Same createArtifactStore rejects it with 'not a regular file'. CLI --replay S/tmp/store/core500-failure-original.json --minimize S/tmp/store/core500-failure-minimized.json creates S/outside.json and leaves output symlink intact. Saved invariant HARNESS-CAUSAL; minimization.reproduced=true,attempts14,invalid5,originalEvents47. Measured saved bytes SHA256683ba5e0d06c1c86ee74dbe1dfcc357010d271a72282d922e058550f594c2b2d. Expected reject link without target write. This intentionally invalid trace establishes sink reachability, not an Auth regression. Filesystem mechanism has no database authority or prompt/challenge/verify/logout/hint/index/budget/RPC requests; replay internally uses fresh synthetic Worker/SQLite fixtures (10 Worker calls in original trace), no persistent M1/production rows, and those internal route counts do not establish this filesystem defect. No naturally occurring Auth failure or Windows-specific CLI exploit measured.\n\nimport assert from 'node:assert/strict';\nimport {mkdirSync,mkdtempSync,symlinkSync,readFileSync,existsSync,lstatSync,rmSync} from 'node:fs';\nimport {resolve,join} from 'node:path';\nimport {spawnSync} from 'node:child_process';\nimport {createHash} from 'node:crypto';\nimport {runSeed} from './tests/auth-scheduler-driver.mjs';\nimport {createArtifactStore} from './tests/auth-artifacts.mjs';\nmkdirSync('tmp',{recursive:true});const root=mkdtempSync(resolve('tmp/reviewer-replay-'));let escaped;\ntry{\n const trace=await runSeed(0,{retainTrace:true});assert.ok(trace.metrics.workerCalls>=2);\n trace.actions.push({type:'worker',id:trace.actions.find(a=>a.type==='worker').id});\n const store=createArtifactStore({sourceDir:root,requestedDir:'tmp/store'});\n store.write('core500-failure-original.json',{trace});\n const input=join(store.directory,'core500-failure-original.json'),out=join(store.directory,'core500-failure-minimized.json'),outside=join(root,'outside.json');\n symlinkSync(outside,out,'file');assert.equal(existsSync(outside),false);\n assert.throws(()=>store.write('core500-failure-minimized.json',{}),/not a regular file/);\n const cli=spawnSync(process.execPath,['scripts/replay-auth-trace.mjs','--replay',input,'--minimize',out],{encoding:'utf8'});\n assert.equal(cli.status,1);escaped=existsSync(outside);assert.equal(escaped,true);assert.ok(lstatSync(out).isSymbolicLink());\n const bytes=readFileSync(outside),saved=JSON.parse(bytes);\n console.log(JSON.stringify({exit:cli.status,stderr:cli.stderr.trim(),createdOutsideConfiguredStore:escaped,outputStillSymlink:true,\n  invariant:saved.invariant,workerCalls:trace.metrics.workerCalls,actions:trace.actions.length,minimized:saved.trace.minimization,\n  savedSha256:createHash('sha256').update(bytes).digest('hex')}));\n}finally{rmSync(root,{recursive:true,force:true});}\nassert.equal(escaped,false,'minimization must reject a dangling final link without creating its outside target');\n\n\nReproduction JavaScript SHA256 (UTF-8 including final newline): 42470cc0493f2a290d3744912a48c1e79e38f14ceb197f4805fdb33ac00c66d5.","severity":"info","snippet":"    const minimized=await (audit8?minimizeAudit8Failure:minimizeFailure)(error.schedulerTrace??trace,error.invariant);writeFileSync(out,JSON.stringify(sanitizeArtifact({invariant:error.invariant,trace:minimized}),null,2)+'\\n');}","title":"Replay CLI minimization bypasses the artifact store and follows a dangling output symlink"},{"citation":"resolved","description":"PARTIAL/OPEN latest Audit8 #5 (https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md). The bare-path route exception accepts a route prefix followed by a dot, so absolute filenames such as /api/auth/session.log survive nested persisted diagnostics although they are not routeIds. The general arbitrary-root path-redaction guarantee is incomplete. Info: opt-in local artifact hygiene only; these deliberately synthetic filenames are not evidence of actual machine-data disclosure or an Auth bypass. Restrict exemptions to exact/unambiguous complete route identifiers, preserving exact route IDs, network URLs, relative identifiers and replay structure. SOURCE-CLOSURE blocker for complete redaction closure, separate from filesystem containment. Fresh REVIEWER Linux Nodev24.21.0 measurement against public347268a7ecae700088547c2402db9a3eb07a6fd2; module SHA25669b9d2023fde69ec2b2b1719c721f0f99d5b0d7274aef5a548d75a96ec2fe2d7. Supplied suite613/613 and verifier13/13 still pass. Windows runtime and naturally emitted production diagnostics are unmeasured; conservative same-line masking itself is an accepted documented policy. Exact source SHA256: 69b9d2023fde69ec2b2b1719c721f0f99d5b0d7274aef5a548d75a96ec2fe2d7. The immutable prior Audit8 document was independently fetched: 32883 bytes, SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc.","line":15,"path":"source/tests/auth-artifacts.mjs","reproduction":"Run the following with node --input-type=module on stdin from source/ after fresh locked npm ci --ignore-scripts. REVIEWER exit1 at the expected masked-message assertion. createArtifactStore persists nested 'Cannot read /api/auth/session.log', 'Error at /api/auth/verify.backup', and 'Error at /api/me/home.private.json' verbatim. Expected all three absolute filenames masked; actual unchanged. Control /var/private/session.log masks to [local-path]; exact /api/auth/session, https://example.com/root/project.ts, tests/auth-artifacts.test.mjs, structured action start/tab a and nonce n1 survive. Persisted JSON SHA256372f303933cdb0b337824d911a3215eb44cb0364e86e532b1b99b844e2b75c24. Actual file is read back before deletion under a synthetic source/tmp root. No outside-root writes; no database rows; prompt/challenge/verify/logout/hint/index/budget/RPC all0.\n\nimport assert from 'node:assert/strict';\nimport {mkdirSync,readFileSync,rmSync,existsSync} from 'node:fs';\nimport {resolve,join} from 'node:path';\nimport {createHash} from 'node:crypto';\nimport {createArtifactStore} from './tests/auth-artifacts.mjs';\nconst root=resolve('tmp/reviewer-route-prefix');assert.equal(existsSync(root),false);mkdirSync(root,{recursive:true});\nlet saved;\ntry{\n const store=createArtifactStore({sourceDir:root,requestedDir:'tmp/artifacts'});\n const input={nested:{message:'Cannot read /api/auth/session.log',errors:['Error at /api/auth/verify.backup','Error at /api/me/home.private.json']},\n  control:'Cannot read /var/private/session.log',route:'/api/auth/session',relative:'tests/auth-artifacts.test.mjs',\n  url:'https://example.com/root/project.ts',trace:{actions:[{type:'start',tab:'a'}],nonce:'n1'}};\n store.write('core500-RESULT.json',input);\n const bytes=readFileSync(join(store.directory,'core500-RESULT.json'));saved=JSON.parse(bytes);\n assert.equal(saved.nested.message,input.nested.message);assert.deepEqual(saved.nested.errors,input.nested.errors);\n assert.equal(saved.control,'Cannot read [local-path]');assert.equal(saved.route,input.route);\n assert.equal(saved.url,input.url);assert.equal(saved.relative,input.relative);assert.deepEqual(saved.trace,input.trace);\n console.log(JSON.stringify({saved,sha256:createHash('sha256').update(bytes).digest('hex'),noOutsideWrite:true}));\n}finally{rmSync(root,{recursive:true,force:true});}\nassert.equal(saved.nested.message,'Cannot read [local-path]','route-prefix filenames must be masked');\n\n\nReproduction JavaScript SHA256 (UTF-8 including final newline): 628b43ae42b5fb454ee28051d2b288d9dba48aa4b21a920616fc1fd3dc97ad4d.","severity":"info","snippet":"    const route=[...routeIds].find(id=>chunk.startsWith(id,match.index)&&/^(?:$|[\\s.,;|)\"'<>])/.test(chunk.slice(match.index+id.length)));","title":"Route-prefix exemptions leave absolute filenames unmasked in persisted diagnostics"}],"hash":"73463325b9200243ae7bc76d4ee79e7ee7d478a7b98a33f740762078451ecf87","nodeId":"0fbb0aab-9c5e-4384-905e-660b128ab9c3","outcome":"completed","summary":"The candidate does **not fully close Audit8**. I independently reproduced and deduplicated **three Low and two Info findings**.\n\nSaved complete reproductions, database rows, counts, hashes, prior links, and limitations in [.imd-findings.json](/root/.identitymd/work/d76a2a79-7394-420a-99d2-df2ba6a23f45/0fbb0aab-9c5e-4384-905e-660b128ab9c3/.imd-findings.json). Product and configuration files remain unchanged.\n\n| Verdict | Result | Basis |\n|---|---|---|\n| SOURCE-CLOSURE | **BLOCKED** | Reproduced eligibility, wallet-context, cleanup, and artifact defects remain. |\n| RELEASE-READINESS | **BLOCKED** | Source blockers remain; production build/upload/persistence/readback are TEAM observations, not independent reviewer measurements. |\n\nReviewed exact public commit [`347268a7ecae700088547c2402db9a3eb07a6fd2`](https://github.com/tungweb3/imd-ember-world-review/tree/347268a7ecae700088547c2402db9a3eb07a6fd2).\n\n| ID | Severity | Pinned location | Reproduced failure |\n|---|---|---|---|\n| F1 | Low | [ownership.ts:355](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/server/ownership.ts#L355) | A sighting starts at age `86399999ms`; a `2ms` D1 delay produces age `86400001ms`, yet the response reports `eligible=1`. An immediate second request correctly returns zero without another proof. |\n| F2 | Low | [auth.ts:290](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L290) | Observe A → stop → provider changes to B → restart. `eth_accounts(B)` leaves displayed account A and owner mode. A related sequence suppresses genuine B→A cleanup. Duplicate specialist reports are merged here. |\n| F3 | Low | [auth.ts:589](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L589) | Hold old B verification/nonce cleanup → stop → another context installs cookie A → restart locked → first account event C. The old retained owner authorizes an address-A logout; A’s database row is revoked. |\n| F4 | Info | [replay-auth-trace.mjs:13](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/scripts/replay-auth-trace.mjs#L13) | Replay minimization follows a dangling final-output symlink and creates its target outside the modeled artifact root. The repaired artifact store rejects that same link. |\n| F5 | Info | [auth-artifacts.mjs:15](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/tests/auth-artifacts.mjs#L15) | Persisted nested diagnostics retain `/api/auth/session.log`, `/api/auth/verify.backup`, and `/api/me/home.private.json` because route-prefix exemptions accept filename suffixes. |\n\nEach retained finding has an independently executed reproduction whose expected-behavior assertion exits **1** against this candidate. F4’s deliberately invalid replay establishes filesystem-sink reachability; it is not an asserted Auth failure. F5 uses synthetic filenames and does not establish an actual private-machine disclosure.\n\nThe latest [immutable Audit8](https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md) maps as follows:\n\n| Audit8 mechanism | Closure assessment |\n|---|---|\n| Post-D1 ownership-proof expiry | **Fixed within tested bounds.** Exact proof-expiry controls pass; F1 is a separate adjacent eligibility defect. |\n| First locked-provider account event | **Partial/reopened.** Ordinary first-observation controls pass; F2 and F3 expose lifecycle failures. |\n| Slow overlapping fresh requests | **Fixed within tested bounds.** Both four-page cohorts at `7475/7500ms` share one cycle, four pages, one budget, one RPC, and one epoch. |\n| Dangling artifact final link | **Store repair passes; overall partial.** F4 reaches an unprotected CLI writer. |\n| General diagnostic-path redaction | **Parti","treeHash":null,"usage":{"cachedInputTokens":3914496,"inputTokens":206746,"model":"gpt-6-astra","outputTokens":20527,"runtime":"codex","turns":8,"wallClockMs":782621}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"6d0f746f68a99ddf","findings":[],"hash":"a1b56f1312678834f0bb4d885b317ae1e9348b0e9c7f99bc5c4693606c48ed17","nodeId":"c30c2392-27a4-4b96-b8ea-fa2845e3af3e","outcome":"failed","summary":"spawn ENAMETOOLONG","treeHash":null,"usage":{"cachedInputTokens":1540096,"inputTokens":160162,"model":"gpt-6-astra","outputTokens":7432,"runtime":"codex","turns":5,"wallClockMs":217108}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"553a378efc6a19cb","findings":[],"hash":"a44a63ed2949fb1e2310432bc11a7178d3398747e9b463cd70f78adac2b2d6f8","nodeId":"c30c2392-27a4-4b96-b8ea-fa2845e3af3e","outcome":"failed","summary":"{\"type\":\"error\",\"status\":400,\"error\":{\"type\":\"invalid_request_error\",\"message\":\"The 'gpt-6-astra' model is not supported when using Codex with a ChatGPT account.\"}}","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":3774}},{"artifacts":[],"attempt":3,"bundleHash":null,"device":"74db3e88f3c65478","findings":[{"citation":"resolved","description":"OPEN scoped lifecycle/asymmetry counterexample adjacent to latest Audit8 finding 2. start()/stop() reset observedAccount, but preserve s.account. In the non-discovery bind branch the real eth_accounts response sets observedAccount yet updates the displayed account only if !this.s.account. Start with cookie A, observed wallet A and an eligible seat, stop this client, switch the wallet to B while its listener is removed, and restart the same client. eth_accounts returns B, but s.account stays A. statusOf therefore returns owner instead of mismatch. A fresh AuthClient with the same cookie/provider correctly displays B and mismatch without revoking A. This contradicts the supported restart/context-fencing requirement and blocks an unqualified SOURCE-CLOSURE verdict. Low: proven incorrect client owner-mode authority and wallet context; the server cookie remains a valid A session, and no server authentication bypass, B ownership proof, persistent-profile mutation or real-wallet exploit is claimed. Apply the first current-lifetime provider observation to account even when the previous lifetime left a value, while preserving cookie A and avoiding automatic first-observation logout. Continue to fence late responses using binds/accountEvents. The existing same-client-restart test emits an extra accountsChanged([]) after restarting, clearing the stale state and concealing this case. Public pin 347268a7ecae700088547c2402db9a3eb07a6fd2; auth.ts SHA256 e8b6b2433fb2b8bc64f4ec8e03a2c5049d02cba42ea15ff5556a923846d12829. Prior immutable Audit8: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md (finding 2; original cookieA + locked[] + first B event is fixed). Fresh REVIEWER Node v24.19.0/Linux with real locked viem 2.56.9 and real Worker/SQLite. Supplied runner passes 613/613; no skipped files or private selectors. Browser/extension integration and production state not measured. Reproduction JavaScript SHA256 (UTF-8 including final newline): 5eb9127d2b370db2adc622a33ef854292e4e4830a3f99cbc2e2e4e26d00b1b2c.","line":290,"path":"source/src/world/auth.ts","reproduction":"Run from source/ of the exact public checkout with Node 24.19.0 and npm ci --ignore-scripts; feed this code to node --input-type=module. Observed exit 0 asserting defective behavior. The restart returns owner, the fresh-client control mismatch. Both retain the original A session, with no automatic logout. Measured restart A=0x70a50df2d3a3a74f90f0ce622bec671fa9b2ca0c, B=0xaB40AC50e7d593DD82606Bae94ae484C5BaD61b5; actual before/after sessions row {nonce:a0148d9fbec17d6bb94d02eff0c445cc,address:A,expires_at:1791201600000,revoked_at:null}, challenge {nonce:a0148d9fbec17d6bb94d02eff0c445cc,used_at:1790596800000,invalidated_at:null}. The script generates new synthetic identities each run. Home remains seat7/agent707/eligible1/block21000000/checkedAt1790596800000. Setup signs once with challenge/verify=1/1; the measured client flow adds personal_sign/challenge/verify/logout/hint=0/0/0/0/0, and total index/budget/ownerOf-RPC=1/1/1, unchanged across restart. The only recorded cleanup plan is stop:none. No member rows are created or changed.\n\nimport assert from 'node:assert/strict';\nimport {setup,newAccount,fakeImd,fakeChain,provider,tab,until,flush,rows,logouts,prompts,routeEvents,statusOf} from './tests/auth-r7-fixtures.mjs';\nfor(const mode of ['restart','fresh-client']){\n const A=newAccount(),B=newAccount(),a=A.address.toLowerCase(),owners=[];owners[7]=a;\n const w=setup({chain:fakeChain({owners:{7:a}}),imd:fakeImd({seats:{7:'707'},owners,online:[7]})}),b=w.browser(),p=provider(A);\n let budget=0;w.env.CHAIN_LIMITER={limit:async({key})=>{if(key==='chain:index')budget++;return {success:true};}};\n assert.equal((await b.signIn(A)).verify.status,200);\n let q=tab(w,b,p);await until(()=>q.c.state.restored&&q.c.state.account&&!q.c.state.checking);\n assert.equal(statusOf(q.c.state,w.clock.now()),'owner');const before=rows(w);\n q.stop();p.switchTo(B);if(mode==='restart')q.restart();else q=tab(w,b,p);\n await flush(40);\n const observed=await p.request({method:'eth_accounts'}),actual=statusOf(q.c.state,w.clock.now());\n assert.equal(observed[0],B.address);assert.equal(actual,mode==='restart'?'owner':'mismatch');\n assert.deepEqual(rows(w),before);\n console.log(JSON.stringify({mode,actual,expected:'mismatch',wallet:B.address,client:q.c.state.account,session:q.c.state.session,home:q.c.state.home,before,after:rows(w),counts:{personal_sign:prompts([p]),challenge:routeEvents(q,'/api/auth/challenge').length,verify:routeEvents(q,'/api/auth/verify').length,logout:logouts(q).length,hints:q.channels.flatMap(c=>c.messages).length,index:w.chain.state.calls.filter(c=>!c.body).length,budget,rpc:w.chain.state.calls.filter(c=>c.body).length},plans:q.c.lifecycleSnapshot.cleanupPlans}));\n q.stop();\n}\n","severity":"low","snippet":"      }else if(a){this.observedAccount=a;if(!this.s.account){this.lifecycle.discovered(p,a);this.set({account:a});}}","title":"Restarted AuthClient ignores the current provider account and retains owner mode for the previous account"},{"citation":"resolved","description":"PARTIAL / OPEN, latest Audit8 finding 5 (absolute diagnostic path redaction). The route exception accepts a route prefix followed by a dot, rather than requiring an actual complete route identifier. Consequently /api/auth/session.log, /api/auth/verify.backup and /api/me/home.private.json are treated as exempt protocol identifiers and remain verbatim in persisted nested diagnostics. These are absolute POSIX filenames, not any member of routeIds. Blocks claiming the requested arbitrary-root path-redaction closure; Info because this requires opt-in local artifact persistence and no actual private-machine disclosure or remote authority bypass is demonstrated. Restrict the exception to exact route values or unambiguous complete route tokens; retain masking for filename suffixes and retain actual route IDs, network URLs and replay fields. Public pin 347268a7ecae700088547c2402db9a3eb07a6fd2. Prior immutable report: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md#5-info-artifact-sanitizer-leaves-absolute-machine-paths-under-common-posix-roots-unmasked . Prior SHA256 8c9baaa6838e8b0137baa44282d1fcc2704834d68c92dafcd4426a882905bcfc verified independently. Fresh REVIEWER Node v24.19.0/Linux, exact locked viem 2.56.9. The supplied 613/613 suite and 13/13 verifier pass despite this counterexample. Module SHA256 69b9d2023fde69ec2b2b1719c721f0f99d5b0d7274aef5a548d75a96ec2fe2d7. Synthetic diagnostic inputs only; Windows runtime and naturally emitted production diagnostics not measured. Reproduction JavaScript SHA256 (UTF-8 including final newline): 5efd87aa1aea012999a964c2d4d0adc9c1f1bf3f52d5bf67a3d41dad76632a04.","line":15,"path":"source/tests/auth-artifacts.mjs","reproduction":"Run in the fresh pinned public checkout source/ with Node 24.19.0, after npm ci --ignore-scripts; feed this code to node --input-type=module. Observed exit 0 asserting the defect. The resulting JSON SHA256 is 372f303933cdb0b337824d911a3215eb44cb0364e86e532b1b99b844e2b75c24. Expected all three nested absolute filenames masked; actual all remain unchanged. /var/private/session.log is masked, and exact route/network URL/relative identifier/trace/nonce controls remain unchanged. DB rows: none; prompt/challenge/verify/logout/hint/index/budget/RPC counts: all 0. No output outside the synthetic source/tmp policy; fixture removed finally.\n\nimport assert from 'node:assert/strict';\nimport {mkdirSync,readFileSync,rmSync,existsSync} from 'node:fs';\nimport {resolve,join} from 'node:path';\nimport {createHash} from 'node:crypto';\nimport {createArtifactStore} from './tests/auth-artifacts.mjs';\nconst root=resolve('tmp/reviewer-route-prefix');assert.equal(existsSync(root),false);mkdirSync(root,{recursive:true});\ntry{\n const store=createArtifactStore({sourceDir:root,requestedDir:'tmp/artifacts'});\n const input={nested:{message:'Cannot read /api/auth/session.log',errors:['Error at /api/auth/verify.backup','Error at /api/me/home.private.json']},control:'Cannot read /var/private/session.log',route:'/api/auth/session',relative:'tests/auth-artifacts.test.mjs',url:'https://example.com/root/project.ts',trace:{actions:[{type:'start',tab:'a'}],nonce:'n1'}};\n store.write('core500-RESULT.json',input);const bytes=readFileSync(join(store.directory,'core500-RESULT.json')),saved=JSON.parse(bytes);\n assert.equal(saved.nested.message,input.nested.message);assert.deepEqual(saved.nested.errors,input.nested.errors);\n assert.equal(saved.control,'Cannot read [local-path]');assert.equal(saved.route,input.route);assert.equal(saved.url,input.url);assert.deepEqual(saved.trace,input.trace);\n console.log(JSON.stringify({saved,sha256:createHash('sha256').update(bytes).digest('hex'),noOutsideWrite:true}));\n}finally{rmSync(root,{recursive:true,force:true});}\n","severity":"info","snippet":"    const route=[...routeIds].find(id=>chunk.startsWith(id,match.index)&&/^(?:$|[\\s.,;|)\"'<>])/.test(chunk.slice(match.index+id.length)));","title":"Route-prefix exemption still persists absolute diagnostic file paths"}],"hash":"df0892ab0164d201183f36b5d09aa14500f1c13ea24970ead80248b5993efba1","nodeId":"c30c2392-27a4-4b96-b8ea-fa2845e3af3e","outcome":"completed","summary":"Recorded **two reproducible findings: one Low and one Info** in [.imd-findings.json](/home/seat21/.identitymd/work/d76a2a79-7394-420a-99d2-df2ba6a23f45/c30c2392-27a4-4b96-b8ea-fa2845e3af3e/.imd-findings.json). Both include complete executable reproductions and verified source snippets. Tracked source files remain unchanged.\n\n| Verdict | Result | Rationale |\n|---|---|---|\n| SOURCE-CLOSURE | **BLOCKED** | An additional lifecycle counterexample remains, and Audit8’s general path-redaction requirement is only partially closed. |\n| RELEASE-READINESS | **UNKNOWN** | Production upload, build, persistence and readback evidence remain TEAM observations. Full frontend compilation and authenticated production verification were unavailable. |\n\nReviewed public commit: `347268a7ecae700088547c2402db9a3eb07a6fd2`.\n\n| Finding | Severity | Pinned location | Concrete failure | Disposition |\n|---|---|---|---|---|\n| F1 — Restart retains the previous wallet account | Low | [source/src/world/auth.ts:290](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L290) | Cookie A and observed wallet A → stop client → wallet switches to B → restart the same client. `eth_accounts` returns B, but displayed account remains A and `statusOf` returns `owner`. | **Open**; additional lifecycle counterexample adjacent to Audit8 #2. |\n| F2 — Route exemption preserves absolute filenames | Info | [source/tests/auth-artifacts.mjs:15](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/tests/auth-artifacts.mjs#L15) | Persist nested diagnostics containing `/api/auth/session.log`, `/api/auth/verify.backup` or `/api/me/home.private.json`. All survive unchanged because a dot satisfies the route-prefix exemption. | **Partial/open** Audit8 #5; blocks complete artifact-redaction closure. |\n\nFor **F1**, the current-lifetime observation updates `observedAccount`, but the `!this.s.account` guard prevents updating the displayed account retained from the previous lifetime. A newly constructed client with the same cookie/provider state correctly displays B and `mismatch`, while preserving session A.\n\nThe reproduction measured:\n\n- Setup: one challenge and one verify.\n- Client flow: zero signature prompts, challenges, verifies, logouts or broadcast hints.\n- Total index/budget/ownership RPC counts: **1/1/1**, unchanged across restart.\n- Session/challenge counts before and after: **one live session; one used challenge; zero revoked sessions, pending challenges or invalidations**.\n- Cleanup plan: `stop:none`.\n- Home: seat `7`, agent `707`, eligible `1`, block `21000000`, `checkedAt=1790596800000`.\n\nOne measured session row was:\n\n```json\n{\"nonce\":\"a0148d9fbec17d6bb94d02eff0c445cc\",\"address\":\"0x70a50df2d3a3a74f90f0ce622bec671fa9b2ca0c\",\"expires_at\":1791201600000,\"revoked_at\":null}\n```\n\nIts challenge retained `used_at=1790596800000` and `invalidated_at=null`. The provider reported B, `0xaB40AC50e7d593DD82606Bae94ae484C5BaD61b5`, while the client continued displaying A. These are generated fixture identities.\n\nThis demonstrates incorrect client owner mode, not a server authentication bypass or persistent-profile mutation. The repair should apply the first current-lifetime provider observation while preserving cookie identity and existing late-response fences.\n\nFor **F2**, the actual saved JSON contained:\n\n```json\n{\"nested\":{\"message\":\"Cannot read /api/auth/session.log\",\"errors\":[\"Error at /api/auth/verify.backup\",\"Error at /api/me/home.private.json\"]}}\n```\n\nThe `/var/private/session.log` control was masked. Exact route identifiers, network URLs, relative identifiers, actions and nonce aliases remained intact. Persisted artifact SHA256:\n\n```text\n372f303933cdb0b337824d911a3215eb44cb0364e86e532b1b99b844e2b75c24\n```\n\nThere were no database rows or auth/index/budget/RPC operations. The fixture was removed afterward. These deliberately synthetic paths establish a sanitizer-contra","treeHash":null,"usage":{"cachedInputTokens":2607104,"inputTokens":174703,"model":"gpt-6-astra","outputTokens":16615,"runtime":"codex","turns":6,"wallClockMs":651145}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"381cad4126e6ec64","findings":[{"citation":"resolved","description":"OPEN new numerical/time-boundary counterexample adjacent to latest Audit8 finding 1. The added line 354 correctly validates the 30-second ownerOf proof against the live clock after enrichment, but line 355 still computes counts/eligible/size with req.now captured before all awaits. counts() at line 151 compares owner-specific last_online_at against that older time. An offline registered seat can therefore grant owner mode after its 24-hour eligibility window has ended even though the crypto proof is fresh. The same stale time is used for the no-eligible-seat lane decision at line 349. This does not reopen the exact sold-seat/30-second-proof reproduction, which is fixed, but leaves a separate stale eligibility authority boundary. Low: inaccurate household authority, no asset transfer or unauthorized profile mutation demonstrated. SOURCE-CLOSURE blocker for current household eligibility; preserve the existing inclusive 24-hour comparator and original checkedAt/index timestamps, but evaluate eligibility and the lane predicate using live time after waits. This is newly identified scoped pre-existing behavior; it is not claimed to have been introduced by Submission9.","line":355,"path":"source/server/ownership.ts","reproduction":"Fresh pinned public 347268a7ecae700088547c2402db9a3eb07a6fd2; Node v24.21.0/viem 2.56.9; real Worker routes and node:sqlite migrations, local chain/roster fixtures only. In source/, node --input-type=module: import assert from 'node:assert/strict'; import {setup,newAccount,fakeChain,fakeImd} from './tests/wallet-harness.mjs'; const A=newAccount(),a=A.address.toLowerCase(),owners=[]; owners[7]=a; const w=setup({chain:fakeChain({owners:{7:a}}),imd:fakeImd({seats:{7:'707'},owners,online:[]})}),b=w.browser(); await b.signIn(A); const t=w.clock.now(),seen=t-86400000+1; w.db.raw.prepare('INSERT INTO seat_presence(token_id,owner,last_online_at,updated_at) VALUES(7,?,?,?)').run(a,seen,seen); const prepare=w.db.prepare.bind(w.db); let once=true; w.db.prepare=sql=>{const wrap=s=>({...s,bind:(...args)=>wrap(s.bind(...args)),all:async()=>{if(once&&sql.startsWith('SELECT token_id,last_online_at')){once=false;w.clock.advance(2);}return s.all();}});return wrap(prepare(sql));}; const response=await b.get('/api/me/home'),home=await response.json(); assert.equal(home.eligible,0); Expected offline-24h/counts=false/eligible=0 at completion. Actual HTTP200, counts=true, eligible=1, size=s, checkedAt=1790596800000, block=21000000 at now=1790596800002; last_online_at=1790510400001 is 86400001ms old, while ownerOf proof is only 2ms old. Immediate second home at the same live clock returns eligible=0 with no additional index/budget/RPC. Counterexample measurement asserting defective behavior exited0. Controls: D1 delay0/1/2/5000ms gives sighting age86399999/86400000/86400001/86404999 and first eligible1/1/1/1; next eligible1/1/0/0. Totals each run: setup challenge/verify1/1; homeGET2; index/budget/ownerOfRPC1/1/1; prompts/logout/hints0/0/0; no eth_getCode. Actual 2ms-case seat_presence row=(token_id7,owner0x469ac0b3815a2695dd6a6660306aeb72121bf951,last_online_at1790510400001,updated_at1790510400001). Session=(same address,created_at1790596800000,expires_at1791201600000,revoked_atNULL,nonce648c8bcbb1f4214bd2e49afa866be60b). Challenge=(same nonce,issued_at1790596800000,accept_until1790597100000,used_at1790596800000,invalidated_atNULL). Sessions created/live/revoked1/1/0; challenges total/used/pending/invalidated1/1/0/0. Prior comparison: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md#1-low-home-returns-owner-authority-after-the-proof-expires-during-post-proof-d1-work . Production D1 timing, real wallet, multi-isolate behavior and full frontend build not checked. Fresh expected-behavior assertion was also executed unchanged against the candidate and exited1: AssertionError offline seat older than 24 hours must not count (1 !== 0). Source SHA256 db7c60509de363c925c938c44ba7e40721a1271f6e558e62e2022abbb2b3c317.","severity":"low","snippet":"    const seats=proof.ids.map(id=>this.status(id,world.agents.get(id),seen.get(id),req.now)),eligible=seats.filter(s=>s.counts).length;","title":"Post-await eligibility uses request-start time and counts sightings older than the 24-hour limit"},{"citation":"resolved","description":"OPEN scoped reverse-control failure related to latest Audit8 finding 2. start() clears observedAccount but stop/start preserves s.account. The non-discovery bind reply records the newly observed B internally yet updates visible/lifecycle account only when the old s.account is null. If account A was observed before stop, the wallet changes to B while listeners are detached, and the same client starts again, eth_accounts=[B] leaves account=A and statusOf=owner for cookie A. It should install B and show mismatch while preserving cookie A: a first observation in this lifetime must not acquire logout authority. The locked [] reply likewise leaves the old displayed account. No unauthorized server write or asset transfer was demonstrated. SOURCE-CLOSURE blocker for the requested restart/context reverse control; the original fresh-client CookieA + [] + first event B reproduction itself is fixed. Refresh account/lifecycle state from the first fenced observation on each start, including empty replies, without synthesizing an A-to-B event from the cookie or prior lifetime. This is newly identified scoped pre-existing behavior; it is not claimed to have been introduced by Submission9. The direct supported stop/start API is reproduced; reachability through a particular production React remount/device lifecycle remains unmeasured because the full frontend is withheld.","line":290,"path":"source/src/world/auth.ts","reproduction":"Fresh public 347268a7ecae700088547c2402db9a3eb07a6fd2, Node v24.21.0, locked viem 2.56.9. In source/ run node --input-type=module with: import assert from 'node:assert/strict'; import {setup,newAccount,provider,tab,ready,flush,fakeImd,fakeChain,statusOf} from './tests/auth-r7-fixtures.mjs'; const A=newAccount(),B=newAccount(),a=A.address.toLowerCase(),owners=[]; owners[7]=a; const w=setup({chain:fakeChain({owners:{7:a}}),imd:fakeImd({seats:{7:'707'},owners,online:[7]})}),b=w.browser(),p=provider(A); assert.equal((await b.signIn(A)).verify.status,200); const q=tab(w,b,p); await ready(q); await flush(); q.stop(); p.switchTo(B); q.restart(); await flush(30); assert.equal(q.c.state.account,B.address.toLowerCase()); Expected B/mismatch with session A alive; actual A/owner. Measurement asserting the defective A/owner result exited 0. Synthetic measured session row: address=0x631a8bc904c509fb493ab0f46b4024c3cc444916, nonce=c6d67e2b17303e059058feac89a29564, expires_at=1791201600000, revoked_at=null; matching challenge used_at=1790596800000, invalidated_at=null; actual provider B=0x8ff51aee5837b279a179aa2e837a490fc4a8a47a. Setup challenge/verify=1/1. Client prompts/challenge/verify/logout=0/0/0/0, session GETs=2, home GETs=1. Session created/live/revoked=1/1/0, challenges total/used/pending/invalidated=1/1/0/0. Substituting p.switchTo(null) also retains account A instead of null. Existing same-client-restart test emits an extra accountsChanged([]) after restart, masking the missing initial-reply update. Pinned prior: https://github.com/Identity-md/research/blob/d7f6e26bf449d9c5ea3a1ecb6557ca3adbd23632/jobs/7716c3f5-5d6c-4953-a643-141da678d051/files/AUDIT.md#2-low-first-accountschanged-observation-from-a-locked-wallet-revokes-the-cookie-restored-session . Offline real client/Worker/SQLite fixture only; no live wallet/browser or production concurrency assertion. A separate expected-behavior assertion statusOf(...)=mismatch was executed and exited1 with actual owner. Instrumented totals: index/budget/ownerOfRPC=1/1/1 across initial start and restart, hint writes=2 total (1 at each canonical restore), 0 prompt/challenge/verify/logout from the client, no eth_getCode. Source SHA256 e8b6b2433fb2b8bc64f4ec8e03a2c5049d02cba42ea15ff5556a923846d12829.","severity":"low","snippet":"      }else if(a){this.observedAccount=a;if(!this.s.account){this.lifecycle.discovered(p,a);this.set({account:a});}}","title":"Restarted AuthClient ignores its first wallet account reply and retains stale owner context"}],"hash":"e3896314076881081b43f71b8067b48418f6ff382dc9c86302db922a9c69ddf4","nodeId":"012ad1a3-eebe-447a-888a-18b0aa006cc8","outcome":"completed","summary":"Recorded **two Low findings** in [.imd-findings.json](/home/imd-worker-1650/.identitymd/work/d76a2a79-7394-420a-99d2-df2ba6a23f45/012ad1a3-eebe-447a-888a-18b0aa006cc8/.imd-findings.json). Source and configuration files remain unchanged.\n\n| Verdict | Result | Reason |\n|---|---|---|\n| SOURCE-CLOSURE | **BLOCKED** | Two reproducible ownership/context boundary defects remain. |\n| RELEASE-READINESS | **BLOCKED** | Source blockers remain; production correspondence is TEAM evidence, independently unverified here. |\n\nReviewed exact public commit [`347268a7ecae700088547c2402db9a3eb07a6fd2`](https://github.com/tungweb3/imd-ember-world-review/tree/347268a7ecae700088547c2402db9a3eb07a6fd2). Both findings are newly identified, pre-existing behavior—not claimed regressions introduced by Submission9.\n\n| Finding | Pinned location | Concrete reproduction and result |\n|---|---|---|\n| **Low — OPEN: eligibility uses request-start time after awaited enrichment** | [`source/server/ownership.ts:355`](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/server/ownership.ts#L355) | An offline seat’s owner-specific sighting starts at age `86,399,999 ms`. D1 enrichment advances time by `2 ms`. The response returns `eligible=1`, although the sighting is now `86,400,001 ms` old. The cryptographic proof is only `2 ms` old, so the new proof-expiry guard does not reject it. An immediate second request returns `eligible=0` without another RPC. |\n| **Low — OPEN: restart retains an obsolete wallet account** | [`source/src/world/auth.ts:290`](https://github.com/tungweb3/imd-ember-world-review/blob/347268a7ecae700088547c2402db9a3eb07a6fd2/source/src/world/auth.ts#L290) | Observe wallet A with cookie A → stop client → switch wallet to B while listeners are detached → restart the same client. `eth_accounts` returns B, but the client retains `account=A` and reports `owner`. Expected: account B, `mismatch`, and cookie A preserved. The supplied restart test sends an additional account event that masks this missing update. |\n\nBoth expected-behavior assertions were executed against the candidate and **exited 1**. Neither reproduction demonstrated asset transfer or unauthorized profile mutation.\n\nFor the first finding, recompute eligibility and the lane predicate using current time after waits, preserving the original proof/index timestamps. For the second, apply the first fenced provider observation of each lifetime—including an empty reply—without treating the cookie or previous lifetime as authority to log out A.\n\nThe five latest Audit8 mechanisms have these bounded dispositions:\n\n| Audit8 finding | Disposition | Fresh reviewer evidence |\n|---|---|---|\n| Low 1: post-D1 proof expiry | **Exact counterexample fixed; adjacent eligibility defect remains** | Proof ages `29999/30000/30001 ms` produce `200/503/503`. Recovery and failed/refused-lane controls pass. |\n| Low 2: first locked-provider event | **Exact counterexample fixed; restart coverage partial** | First observation preserves A; genuine observed switches revoke correctly. The restart counterexample above remains open. |\n| Low 3: slow fresh overlap | **Fixed within tested scope** | Twenty early joined requests, four pages at both `7475` and `7500 ms`: one cycle, four pages, one budget, one RPC, one epoch. |\n| Info 4: dangling final link | **Fixed within documented local-root assumptions** | Real Linux links, substitutions, nonregular targets and hardlink replacement controls pass. |\n| Info 5: diagnostic paths | **Fixed within tested forms; conservative masking policy retained** | Persisted nested path cases and actual saved-trace replay pass. |\n\n**Fresh REVIEWER measurements:** Node `v24.21.0`, locked viem `2.56.9`, fresh public checkout.\n\n| Check | Result |\n|---|---|\n| `npm ci --ignore-scripts` | Passed after relocating npm’s read-only default cache to `/tmp` |\n| `node scripts/review-tests.mjs --check` | Passed; all 23 files present |\n| `npm run test:review` | **","treeHash":null,"usage":{"cachedInputTokens":3489152,"inputTokens":189066,"model":"gpt-6-astra","outputTokens":16478,"runtime":"codex","turns":8,"wallClockMs":691278}}],"verification":[]}