{"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":"c47bc2a0-44e0-413f-a5f1-75b6922c575a","kind":"audit","nodes":[{"acceptedSubmissionHash":"ec63b9cd82bd0d600f602e9a7915e4675048d4343dceefee277bb21f9732afe9","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":"b8853d8ede35b8741f272d7ca94d3ddb25ce6fcc8cbb80a7a10513b97cc8ceb9","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":"8ddc35ffcf9ca9167f1ac2a3584dadaff09e3dcad1325ee91c0f4f0ec6ae1557","dependsOn":["audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"984e4e2a8dbc44bf7be9b16e8b0f55bc2fb4e244a4f55ec3c1eff2e6ed40c8fd","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":"01eb586cba66e6265bdadd34fbee36a056251707bb65632f3bb0fda0cbd1199b","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":"Audit this expanded Ember World shared GOLD candidate at public commit d0a6f42c1e455f16472ea0c51c310f7f2710a9fd (selected projection of local source 57ce2d598eedf2ded2a2080582efcd29b4214adb). Review TypeScript, SQL and Python as well as the retained Solidity boundaries. Start with README.md, SCOPE.md, REQUIREMENTS.md, LOCAL_TESTS.md, the manifests and context/publication-projection.json. This is a selected source export, not a complete World artwork/web build. Check the implementation and reproducible tests; prior test results are evidence, not a substitute for your independent review.\n\nHighest-priority checks:\n1. EMBEO purchases of GOLD are one-way point topups, never an AMM or trading pair. GOLD is wallet-bound, cannot transfer to another wallet, cannot redeem/sell for EMBEO, and has no cash-out. Topup income goes to the existing confirmed treasury; staking principal must never fund purchases, service fees or administrator operations. Check immutable original-price/version orders, exact integer arithmetic and 18-decimal UI handling, canonical/finalized payment evidence, wrong payer/token/chain/amount, replay, concurrent confirmation and bounded partial-credit repair. Automatic reconciliation and admin repair together must credit only the original remaining entitlement once. Changed prices affect new orders, not a valid old order's locked price.\n2. One persistent GOLD book/available/held account must serve legacy payments, topups, staking, service jobs and R4 Mint. Available = book minus held; no legacy route or concurrent consumer may spend held funds. Check transaction/CAS boundaries, duplicate source/order/receipt keys, reservation ownership and chain/contract/chain-instance scope. A timeout, UI close, process restart or restored SQLite file must not by itself authorize release of a pending Mint hold. Settle/consume or cancel using proven chain outcomes. Test cross-consumer double spending, retries, rollback and partial writes.\n3. World authentication must authorize the current wallet and live session, including EOA/SIWE continuity, same-origin request checks and revalidation after asynchronous work and within SQL mutations. Check logout, wallet switch, expired/stale sessions, concurrent requests and expected-address mismatch. The three confirmed owner wallets do not grant ordinary players owner rights. Admin repair must be bounded by an evidenced entitlement; there must be no arbitrary balance setter or unlimited grant. Verify immutable actor/request/reason and atomic before/after evidence, duplicate repair prevention, and price/trial/pause control authorization.\n4. Paid service debit creates one durable delivery job. Verify attempt/lease fencing, saved result retrieval without a second charge, retry and recovery. Unknown provider timeout is not proven terminal nondelivery. Refund the original GOLD at most once only on the supported terminal-failure path. Late completion must not overwrite a refunded state, expose a result improperly, or trigger another debit/refund. Exercise debit/job creation atomicity, lease expiry, stale workers, refund/finish races and repeated callbacks.\n5. Historical staking correction must not mistake purchased GOLD, refunds or other non-staking sources for missed staking rewards. Preserve old finalized evidence; include the new sources and holds in reconciliation/restore invariants and atomic guards. Feature pause stops new admissions while valid pending payments/reconciliation, saved results, lawful refunds and principal withdrawals remain serviceable. Do not infer pending transaction failure from elapsed time alone.\n6. Staking remains positive multiples of 100000 EMBEO, with 1 GOLD per unit for each completed UTC calendar day after next-day finalized confirmation. Principal is withdrawable at any time; only the withdrawn units lose that day's yield. Preserve valid other-unit/other-day rewards. No rolling 24-hour or per-second replacement, burn or revenue sharing is intended. Existing staking/payment Solidity and R4 Mint Solidity are unchanged; verify their exact retained boundaries, adapters and projections rather than limiting this audit to Solidity.\n7. Free and paid R4 Mint open simultaneously, first successfully completed wins from one shared supply of 2222. One free Mint per eligible Active Agent wallet, with neither EMBEO nor GOLD fee, no three-day window and no reserved free pool. Paid Mint still needs both its EMBEO fee and GOLD; buying GOLD does not offset the EMBEO Mint fee. Check that the copied service awaits the shared persistent reservation/settlement ledger and cannot bypass it through a legacy/test path.\n8. AIMarket trial controls use authenticated World research sessions only. Check UTC half-open start/end boundaries, manual stop, claim/introspection authenticity and fresh rechecks in World and its Python consumer. Trial claims must not unlock direct external-site visitors or elevate a player to owner, and must not require a second research-login signature. Review privacy and consistency at this boundary.\n\nReproduction and limits: LOCAL_TESTS.md distinguishes full-workspace tests from portable reproduction. The full workspace recorded 2804 passes, zero failures and five pre-existing Windows symlink skips; the selected financial suite, contracts and Python tests are reported separately. Local EDR, synthetic sessions, fixture providers/prices and local exact-production-SQL workerd D1 are fixtures, not proof of mainnet runtime/finality, real-wallet behavior, actual external AIMarket/fortune delivery, target VPS installation or production Workers CPU/D1 capacity. SQLite restore does not restore the memory-only EDR chain. The production Worker economy configuration still returns null. Fixture prices are not launch prices. Game development/reward issuance and production NFT/collection rights are deferred and are not delivered by this packet.\n\nPlease report concrete exploit/failure paths, affected files/lines, severity, assumptions and actionable regression tests. Include negative controls and assess the cross-language/state-machine boundaries. Distinguish demonstrated implementation vulnerabilities from unresolved deployment-specific release gates; neither local fixture success nor absence of findings authorizes deployment. No paid audit submission, wallet signature, payment, deployment or production migration is authorized by preparation of this packet. Return the report on the job's page.","parentJobId":null,"planHash":"32e697a2e9b20a18ea385d08855678f4a4ffe70bec6785003b06d542a9f3f2bd","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"c47bc2a0-44e0-413f-a5f1-75b6922c575a","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":"52017","feedbackHash":"2effec62b5609a1f13749dab7382c256dd629d091e7ad9bdfa95bbdcc8bf1f43","nodeKey":"audit_economics","submissionHash":"ec63b9cd82bd0d600f602e9a7915e4675048d4343dceefee277bb21f9732afe9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52609","feedbackHash":"e5c03f6db2b7fc882ba861db84dd3a4c8ba30e4ba68ddd0c96f5a401c53c2a03","nodeKey":"audit_flow","submissionHash":"b8853d8ede35b8741f272d7ca94d3ddb25ce6fcc8cbb80a7a10513b97cc8ceb9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51154","feedbackHash":"8d6be4c9df4799fb2e2e9fb57af17ddc78f0aa7900a56e676d46c1c8a0eb8f95","nodeKey":"audit_judge","submissionHash":"8ddc35ffcf9ca9167f1ac2a3584dadaff09e3dcad1325ee91c0f4f0ec6ae1557","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51024","feedbackHash":"b6c60a8a365a03547d9c45277bce6ac91e87850930ce35444ebb563b167f935a","nodeKey":"audit_math","submissionHash":"984e4e2a8dbc44bf7be9b16e8b0f55bc2fb4e244a4f55ec3c1eff2e6ed40c8fd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51881","feedbackHash":"dea4f4216f4a05a397b2f01a3f91875bb6ce475cf95f05647b943b3ee45f3664","nodeKey":"audit_permissions","submissionHash":"01eb586cba66e6265bdadd34fbee36a056251707bb65632f3bb0fda0cbd1199b","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"b8e3e32f175730711d75e557d420fb3dcb7ae3d870473d4ce42a23b6db3afe10","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1a7ecd03bd365366","findings":[{"citation":"resolved","description":"finishGoldJob rejects a successful delivery whose serialized result exceeds 16000 characters before it touches the row. The job stays 'working' until its 60 s lease lapses, claimGoldJob (line 76: state='working' AND lease_ms<=now) hands it to the next worker, the adapter re-queries the provider under the same idempotency key, receives the same result and fails again. The job can never reach 'delivered'; because it never reaches 'failed', neither refundGold (line 70) nor the admin 'refund-service' command (line 139 requires state IN ('failed','refunded')) can return the GOLD. The player is debited, has no result, and no bounded operator path exists. A per-item cap is reasonable, but a result that cannot be stored must become a terminal outcome (stored by reference, truncated with a marker, or recorded as a provider failure so the once-only refund applies). Verified with the executable check below against the migrations and the real functions (all assertions pass on this tree); negative control in the same test: a bounded result for a second order reaches 'delivered'.","line":80,"path":"world-source/server/world-gold.ts","reproduction":"Fixture as in world-source/tests/world-gold.test.mjs: `await f.topup(); const id=await f.service(); /* book 7 */ let a=await claimGoldJob(f.db,id,NOW); await assert.rejects(finishGoldJob(f.db,{orderId:id,attempt:a,nowMs:NOW+1,outcome:{result:'x'.repeat(16001)}}),/RESULT_TOO_LARGE/); assert.equal(job.state,'working'); await assert.rejects(refundGold(f.db,id,NOW+2),/REFUND_NOT_ENTITLED/); await assert.rejects(f.admin({action:'refund-service',orderId:id})); assert.equal(await claimGoldJob(f.db,id,NOW+3),null); a=await claimGoldJob(f.db,id,NOW+60001); assert.ok(a); /* loop repeats */ assert.equal((await f.snapshot()).book,'7');`. Expected: the job becomes terminal and the 3 GOLD is refundable exactly once (or the result is retrievable). Actual: state cycles working->working indefinitely with the debit stranded.","severity":"low","snippet":" at(args.nowMs);const json=JSON.stringify('result' in args.outcome?args.outcome.result:null);if(json.length>16000)fail('RESULT_TOO_LARGE');","title":"An oversized provider result makes a paid service job permanently non-terminal: never delivered, never failed, never refundable, re-claimed after every lease"},{"citation":"resolved","description":"SCOPE/REQUIREMENTS require that the copied R4 Mint service await the injected shared persistent ledger and cannot bypass it through a legacy/test path. Injection is instance assignment after construction (world-source/scripts/world-gold-local-runtime.mjs:23 `mint.gold={view,reserve,consume,release}`), but `resetChain()` unconditionally re-creates `this.gold=new LocalGoldLedger()` and clears `this.orders`, and `admin({action:'reset'})` (chain.mjs:166) calls it. The world-gold preview deliberately refuses that action at its HTTP layer (world-gold-preview.mjs:30 `LOCAL_ACTION_FORBIDDEN`), so a browser cannot trigger it there; the standalone mint server (mint/service/server.mjs:44 `/api/admin`) and any in-process caller can. The defect is that the protection lives in one caller instead of the service: after one reset (verified on the real EDR runtime) `mint.gold instanceof LocalGoldLedger`, paid-mint reservations are taken from the fixture balances (A=10,B=10) with no row in world_gold_holds, while `mint.beforeApproval` (feature pause) still fires, so the pause control survives but the money path does not. Every hold that was 'held' before the reset is orphaned: `mint.orders` is empty, `runId` (chain_instance) changed, so neither `consume` nor `release` can ever call settleMintGold with matching scope and the wallet's `available` stays reduced forever. Expected: a service that received a shared ledger must either keep it across reset or refuse reset; actual: it swaps to a throwaway ledger and strands existing holds. Low because the only exposed HTTP path blocks it and the fixture is local-only; reported because the requirement names exactly this boundary.","line":33,"path":"mint/service/chain.mjs","reproduction":"Verified script (Node, cwd world-source, after `npm ci` in contracts): `const r=await createGoldLocalRuntime(); const A=r.mint.addresses[1],a=A.toLowerCase(),injected=r.mint.gold; const order=await goldQuote({db:r.db,config:r.config,nowMs:Date.now()},{wallet:a,requestId,product:'gold-pack'}); await r.pay(order,A); /* book 10 */ const id='0x'+'309'.padStart(64,'0'),deadline=(await r.mint.time())+300; await r.mint.gold.reserve(id,'A',1n,{auth:{wallet:A,deadline,orderId:id},packet:{}}); /* world_gold_holds state 'held' */ await assert.rejects(r.mint.gold.reserve(id2,'C',1n,{auth:{wallet:r.mint.addresses[3],deadline,orderId:id2},packet:{}})); /* negative control: shared ledger refuses wallet C */ const runBefore=r.mint.runId; await r.mint.admin({action:'reset'}); assert.ok(r.mint.gold instanceof LocalGoldLedger); r.mint.gold.reserve(id3,'A',1n); /* succeeds, COUNT(*) FROM world_gold_holds still 1 */ assert.equal(r.mint.orders.size,0); assert.notEqual(r.mint.runId,runBefore); await assert.rejects(injected.release(id),/MINT_ORDER_MISSING/); await reconcileWorldGold(r.db,Date.now()); snapshot(a) => book 10, reserved 1, available 9 permanently.` Expected: reset refused or the injected adapter retained so the second reserve is still bounded by the shared `available`, and the first hold remains settleable; actual: fixture ledger in use, shared ledger untouched, first hold stranded.","severity":"low","snippet":"  this.gold=new LocalGoldLedger();this.orders=new Map();this.records=[];this.receipts=[];this.packets=new Map();this.submissions=new Set();","title":"Copied Mint service's resetChain() silently replaces the injected shared persistent GOLD ledger with the in-memory fixture ledger; only the preview's HTTP route guards against it, and holds taken befo"},{"citation":"resolved","description":"World computes `active = !stopped && now>=startsMs && now<endsMs` (world-source/server/world-gold.ts:112) at the moment it answers `/api/aimarket-auth/session`. The consumer re-evaluates with its own clock after network latency and rejects the whole identity payload when `active` is true but `endsMs <= _now_ms()`. That combination is a legitimate state in the last moments of every campaign and whenever the two clocks differ by more than the request latency; it is not a malformed response. `session_eligible` (lines 362-366) catches only BridgeDenied, so the BridgeUnavailable escapes to the caller: a portal reader gets an error instead of the balance fallback, i.e. a wallet that holds enough EMBEO is refused for that request although REQUIREMENTS only say an ended trial must not grant the trial. In complete_login (line 309) the same raise turns a valid login at the campaign boundary into `invalid_world_response`. `_world_trial` already applies the right rule (`_now_ms() < endsMs` -> False); an elapsed end should be 'inactive', not 'invalid'. Verified: active+future passes, inactive+past passes, only active+elapsed raises.","line":249,"path":"aimarket/web/world_auth.py","reproduction":"python3 -I in aimarket/ with AIMARKET_BRIDGE_SECRET set to any 40-char value: `from web import world_auth as w; now=int(time.time()*1000); base=dict(issuer=w.ISSUER,audience='aimarket',scope='research:read',address='0x'+'a'*40,expires_at=now+600000,bridge_session='A'*43); w._identity(dict(base,world_trial=dict(active=True,endsMs=now-1,version=1)))` -> raises BridgeUnavailable('invalid_world_trial'). Expected: identity returned with the trial treated as inactive so session_eligible proceeds to the holdings check. Actual: exception, and session_eligible has no handler for BridgeUnavailable. Regression test: patch _world_post to return that payload for a stored 'world'-profile session with a holdings stub returning True and assert session_eligible(token, require_profile='world') == (address, True).","severity":"low","snippet":"            or (trial[\"active\"] and (trial[\"endsMs\"] is None or trial[\"endsMs\"] <= _now_ms()))):\n        raise BridgeUnavailable(\"invalid_world_trial\")","title":"AIMarket consumer turns a trial that ends between World's introspection and the consumer's check into a hard BridgeUnavailable instead of an inactive trial; session_eligible propagates it and denies e"},{"citation":"resolved","description":"Correctly, a timeout, UI close, restart or restored SQLite file does not release a hold. But the only writers of settleMintGold are this adapter's consume/release, which require the order in the in-memory `mint.orders` map; `reconcileOrders` (mint/service/chain.mjs:131) iterates only that map. No admin command (goldAdminCommand actions: product/campaign/control/repair-topup/refund-service) and no reconciliation query over `world_gold_mints WHERE state='held'` can settle a hold from stored scope (chain_id, contract, chain_instance, id) plus chain reads (paidOrderTokenId / paidOrderRevoked / block.timestamp > deadline). For the local EDR fixture this is moot because the chain is memory-only, but a production service inheriting this adapter shape would permanently shrink `available = book - held` for the player. Open design gap and release gate, not a demonstrated exploit.","line":31,"path":"world-source/scripts/world-gold-local-runtime.mjs","reproduction":"`const r=await createGoldLocalRuntime({filename:'<tmp>.sqlite'})`; top up wallet A; `await r.mint.gold.reserve(id,'A',1n,{auth:{wallet:A,deadline,orderId:id},packet:{}})` -> world_gold_holds 'held'. `await r.reopen()` then `r.mint.orders.size===0`; `await r.mint.gold.release(id)` -> MINT_ORDER_MISSING; `reconcileWorldGold` leaves the hold; `worldGoldSnapshot(...).available` stays reduced by the hold indefinitely. Expected: a bounded, evidence-gated operator/reconciliation path able to call settleMintGold for stored scope; actual: none exists.","severity":"info","snippet":"  async release(id){const o=mint.orders.get(id);if(!o)throw Error('MINT_ORDER_MISSING');const used=await mint.contract.paidOrderTokenId(id),revoked=await mint.contract.paidOrderRevoked(id),chainTime=await mint.time();if(used||(!revoked&&chainTime<=o.auth.deadline))throw Error('MINT_AUTH_EXECUTABLE');await settleMintGold(db,{...scoped(o),state:'released',evidence:{revoked,chainTime,blockHash:(await mint.provider.getBlock('latest')).hash}},Date.now());}","title":"Pending Mint holds have no evidence-based settlement path outside the in-process order map: after a restart, restore or reset, held GOLD stays reserved even when the chain proves revocation or expiry"},{"citation":"resolved","description":"(1) The three admin wallets (aimarket-admin.ts:8-12) include the production treasury 0x1c65...8ec4 (economy-config.ts:7): the payment receiver is also a price/pause/campaign administrator. (2) The 'product' action lets an admin publish a topup version with any `embeo` >= 1 wei and `gold` up to 2^63-1 (lines 129-131); through the self-serve quote route that is economically an unbounded GOLD grant to whoever buys at that version. It is versioned, immutable, audited, and never alters a paid order's locked price, so it is a documented power rather than the forbidden arbitrary balance setter. (3) settleMintGold (line 101) and finishGoldJob (line 79) trust their server caller for chain evidence and for the 'terminal failure' classification; the only callers in this packet are the local fixture adapter. The Worker (worker/app.ts:155) runs reconcileWorldGold inside syncEconomy, which returns early unless the economy config and ECONOMY_CHAIN_LIMITER exist, and it has no claim/finish adapter and no mint verifier: in production every paid service job stays 'queued' and no Mint hold can be settled until those adapters exist. (4) reserveMintGold performs no session check; wallet identity for Mint reservations is wholly delegated to the mint service, whose packet authentication is a loopback session token. (5) Every check() inserts a permanent row into world_gold_write_checks (2 per quote, 2+ per admin command, with before_json snapshots) with no pruning. Checked and found sound: same-origin and Sec-Fetch-Site gating, expected_address matching, double readSession plus SQL-level session EXISTS inside quote/admin batches, EOA/ECDSA requirement for all writes, lowercase session addresses (auth.ts:508) matching the `address=?` comparison, admin idempotency with payload comparison, version CAS on controls/products/campaigns, hold/available triggers across legacy GOLD debits, once-only topup/refund references, half-open UTC campaign bounds in both World and Python, trial never unlocking the external profile, and the recovery planner's balance=journal+entries invariant.","line":117,"path":"world-source/server/world-gold.ts","reproduction":"Not an exploit path. Verification: `goldAdminCommand(db,{wallet:OWNER,...,command:{action:'product',product:'gold-pack',kind:'topup',title:'t',gold:'9223372036854775807',embeo:'1',enabled:true,version:2}})` succeeds; `grep -rn claimGoldJob world-source/worker` returns nothing; `grep -rn settleMintGold world-source/worker world-source/server` returns only the definition.","severity":"info","snippet":" if(!AIMARKET_ACTIVITY_ADMIN_WALLETS.includes(wallet))fail('ADMIN_REQUIRED');","title":"Trust assumptions and deployment gates that bound the permission model (documented powers, not bypasses)"}],"hash":"01eb586cba66e6265bdadd34fbee36a056251707bb65632f3bb0fda0cbd1199b","nodeId":"4e8163ac-dcf8-4c07-a872-4414a2fda806","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds five findings. No tracked file was changed; the only additions outside the repo were dependency installs and a scratch test in `/tmp`.\n\n**Outcome.** The permission model in the assigned area holds up: no arbitrary balance setter, admin repair bounded by the paid entitlement with shared once-only references, double session reads plus SQL-level session checks inside the quote and admin batches, EOA/ECDSA required for all writes, same-origin and Sec-Fetch-Site gating, version CAS on prices, controls and campaigns, hold/available triggers that also bind the legacy GOLD debit, half-open UTC trial bounds in both World and the Python consumer, and the trial never unlocking the external profile. I found no critical or high defects, so no Foundry proof file was required. The retained Solidity was checked only at its boundaries (mintPaid deadline and revoke semantics match the release adapter).\n\n**Findings, most severe first.**\n- **Low, `world-gold.ts:80`.** A provider result over 16000 characters leaves a paid service job cycling working to working forever. It can never be delivered, failed or refunded, and the admin refund command cannot reach it. Reproduced with an executable check against the real migrations.\n- **Low, `mint/service/chain.mjs:33`.** `resetChain()` replaces the injected shared GOLD ledger with the in-memory fixture ledger and clears the order map. Reproduced on the real EDR runtime: after one reset, paid-mint reservations come from fixture balances with no shared hold, and a hold taken before the reset is stranded for good. The preview route already refuses `reset`, which is why this is low rather than medium.\n- **Low, `aimarket/web/world_auth.py:249`.** An active trial whose end has just elapsed by the consumer's clock is treated as a malformed response. The exception escapes `session_eligible`, so a holder with enough EMBEO is denied for that request instead of falling back to the balance check. Reproduced in Python with negative controls.\n- **Info.** Pending Mint holds have no evidence-based settlement path outside the in-process order map, so any restart or restore leaves held GOLD reserved even when the chain later proves expiry or revocation.\n- **Info.** Documented trust assumptions and release gates: the treasury is also an admin wallet, admin product pricing is economically unbounded, the Worker has no job claim/finish adapter or mint verifier, Mint reservation identity is delegated entirely to the mint service, and the write-check table grows without pruning.\n\n**Limits.** Tests ran under Node 22 with the project's Node 24 gate overridden only inside my scratch file. Local fixtures, synthetic sessions and the memory-only EDR chain prove nothing about mainnet finality, real wallets, external providers or production Worker capacity, and nothing here authorizes deployment.","treeHash":null,"usage":{"cachedInputTokens":3561485,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":54037,"runtime":"claude","turns":40,"wallClockMs":818106}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"27168b1ff68f84d2","findings":[{"citation":"resolved","description":"World computes world_trial.active with its own clock when it answers /api/aimarket-auth/session (server/aimarket-auth.ts:151 -> goldCampaign, half-open now<endsMs). The Python consumer re-evaluates the same bound with its own clock after network latency, and when active is true but endsMs <= _now_ms() it classifies the whole identity payload as malformed and raises BridgeUnavailable('invalid_world_trial') from _identity(). session_eligible() (lines 362-366) catches only BridgeDenied, so the exception escapes before line 370 where _world_trial(...) would already return False and the holder balance (_balance) would be consulted. complete_login() (line 303/309) fails the same way for a portal login. The result: in the last moments of every campaign (latency only, no skew needed) and for the whole duration of any clock skew where AIMarket runs ahead of World, every World-profile portal read and every new portal login errors out, including for wallets that hold enough EMBEO and would be eligible without the trial. Requirement 8 asks for consistent half-open UTC handling and fresh rechecks on both sides; here the recheck fails closed for the entire request instead of only for the trial claim. Fix: in _identity() keep the structural checks (types, version>=1, key set) as hard failures, but when active is True and endsMs <= _now_ms() normalise the trial to inactive (or simply let _world_trial's existing now<endsMs rule decide) instead of raising. Regression test: add to aimarket/test_world_trial.py a case with world_trial={active:True,endsMs:NOW-20,version:1}, _balance patched True, asserting session_eligible('S'*43,require_profile='world')==(ADDRESS,True) and _world_trial(...) is False; and the same payload through complete_login returning a LoginResult. Merged from four specialist reports (audit_flow, audit_economics, audit_math, audit_permissions) of the same defect.","line":249,"path":"aimarket/web/world_auth.py","reproduction":"Reproduced with python3 -I against aimarket/ (AIMARKET_BRIDGE_SECRET set to a 40-char value, AIMARKET_WORLD_STATE_DB in a temp dir, _now_ms patched to NOW=2_000_000_000_000). Insert a world_sessions row (profile 'world', address 0xaaaa..aa, bridge_session 'B'*43, token 'S'*43). Patch _world_post to return {issuer,audience:'aimarket',scope:'research:read',address,expires_at:NOW+10000,bridge_session:'B'*43,world_trial:{active:True,endsMs:NOW-20,version:1}} and _balance to return True. Call session_eligible('S'*43,require_profile='world'). Expected: ('0xaaaa..aa', True) via the balance path with the trial ignored. Actual: raises BridgeUnavailable: invalid_world_trial. Same with endsMs=NOW. Negative controls run in the same script: endsMs=NOW+1000 -> ('0xaaaa..aa', True); active=False with endsMs=NOW-20 -> ('0xaaaa..aa', True).","severity":"medium","snippet":"            or (trial[\"active\"] and (trial[\"endsMs\"] is None or trial[\"endsMs\"] <= _now_ms()))):","title":"AIMarket consumer raises BridgeUnavailable instead of treating an ended trial as inactive, denying EMBEO holders at the campaign end boundary"},{"citation":"resolved","description":"snapshots() (lines 40-44) deliberately tolerates a pre-0014 database: when none of the world_gold_* tables exist it skips them and continues, which is the state of the deployed World D1 today and of any controlled pre-0013/0014 export. But build() later calls at(old,'world_gold_entries') unconditionally on this line, and at() (line 45) does s.find(...)!.rows, so with the snapshot absent the planner throws \"TypeError: Cannot read properties of undefined (reading 'rows')\" instead of returning a plan or a reviewed RECOVERY_* refusal. preflightEconomyRecovery (line 264) catches only RecoveryCapacityError, so the read-only diagnostic crashes identically. The staking-correction workflow (requirement 5) is therefore unusable on exactly the schema it must currently run against, and an operator cannot distinguish a schema gap from a planner bug. Every existing recovery test opens the database with all migrations, so this path is untested. Fix: treat a missing world_gold_entries snapshot as an empty row set (or return fail('RECOVERY_SCHEMA_MISSING') explicitly, matching the intent of snapshots()), and add a test that runs prepareEconomyRecovery and preflightEconomyRecovery on openD1(migrationFiles().filter(f=>!f.startsWith('0014'))).","line":204,"path":"world-source/server/economy-recovery.ts","reproduction":"Reproduced with test/scratch/repro.mjs case pre0014_recovery: two in-memory D1 adapters opened with migrationFiles().filter(f=>!f.startsWith('0014')); original DB gets economy_chain_state(config.id,last_block=102,last_hash=hash(102),through_ms=NOW+2d); shadow DB gets the same row for a successor config (deploymentBlock '99'); no events. prepareEconomyRecovery(db,shadow,from,to,{nowMs:through+1}) -> expected a plan with zero adjustments or a RECOVERY_* coded refusal; actual: TypeError: Cannot read properties of undefined (reading 'rows'). preflightEconomyRecovery with the same inputs -> expected {status:'ready'|'blocked'}; actual: the same TypeError. Negative control: the packaged test 'historical staking recovery preserves purchased GOLD and holds' passes with all migrations applied.","severity":"medium","snippet":" const totals=new Map<string,bigint>();for(const j of [...journal,...at(old,'world_gold_entries')])totals.set(j.wallet!,(totals.get(j.wallet!)??0n)+BigInt(j.delta!));","title":"Historical recovery planner and preflight crash with an unhandled TypeError on any database that has not applied migration 0014"},{"citation":"resolved","description":"world_gold_controls is consulted only when a quote is created (world-gold.ts goldQuote: goldFeature() plus the in-batch check on line 44). A quote stays payable for 15 minutes (expires_ms = now+900000). payEconomyOrderGold, the only path that debits GOLD for a World service order, and createEconomyGoldChallenge never read world_gold_controls, and reconcileWorldGold (cron, or POST /api/gold/orders/:id/reconcile by the player) then creates the queued delivery job. So after an owner pauses 'service', a quote obtained earlier still yields a new challenge, a book debit, a paid order and a queued job. REQUIREMENTS.md says a pause stops new admissions while already finalized work stays serviceable; an unsigned, undebited quote is not yet an admission and, unlike an on-chain EMBEO topup, can be refused server-side at no loss to the player. Fix: when the order has a world_gold_links row of kind 'service', add AND EXISTS(SELECT 1 FROM world_gold_controls WHERE feature='service' AND enabled=1) to this journal INSERT predicate (and refuse challenge creation while paused); leave reconcile, delivery and refunds of already-paid orders unaffected. Regression test: quote, pause, pay -> expect FEATURE_PAUSED, no journal row, order still pending; re-enable -> the same order pays exactly once.","line":277,"path":"world-source/server/economy-ledger.ts","reproduction":"Reproduced with test/scratch/repro.mjs case pause_service_bypass using the world-gold.test.mjs fixture shape: topup (book 10); id=goldQuote('research'); goldAdminCommand control service enabled:false version:0; negative control goldQuote('research') now rejects FEATURE_PAUSED; then createEconomyGoldChallenge + payEconomyOrderGold with a valid EOA signature for id, then reconcileWorldGold(db,NOW). Expected: payment rejected, book stays 10, no world_gold_jobs row. Actual: no error, book 7, world_gold_jobs has one row state 'queued' for id while world_gold_controls shows service enabled=0 version=1.","severity":"low","snippet":"      bind(db,`INSERT INTO economy_gold_journal(id,wallet,delta,kind,reference,created_ms) SELECT ?,?,-CAST(? AS INTEGER),'service-payment',?,? WHERE EXISTS(SELECT 1 FROM economy_orders WHERE id=? AND wallet=? AND status='pending' AND expires_ms>?) AND EXISTS(SELECT 1 FROM economy_gold_challenges WHERE order_id=? AND request_id=? AND nonce=? AND consumed_ms IS NULL AND expires_ms>?) AND ${auth.sql} AND NOT EXISTS(SELECT 1 FROM economy_chain_state WHERE config_id=? AND halted_reason IS NOT NULL)`,reference,wallet,existing.amount,reference,now,id,wallet,now,id,args.requestId,challenge.nonce,now,...auth.values,config.id),","title":"Pausing the service feature does not stop GOLD debits and job admissions for quotes issued before the pause"},{"citation":"resolved","description":"finishGoldJob rejects a successful delivery whose serialized result exceeds 16000 characters before touching the row. The job stays 'working' until its 60 s lease lapses, claimGoldJob (line 76) hands it to the next worker, the adapter (which must reuse orderId as idempotency key) fetches the same result and fails again. The job can never reach 'delivered'; because it never reaches 'failed', neither refundGold (line 70, REFUND_NOT_ENTITLED) nor the admin refund-service command (line 139 requires state IN ('failed','refunded')) can return the GOLD. The player is debited, has no result, and there is no bounded operator path. A size cap is reasonable, but a result that cannot be stored must become a terminal outcome (store by reference, truncate with a marker, or record a provider failure so the once-only refund applies). Only fixture adapters exist today, so this is a boundary defect a real provider adapter would hit.","line":80,"path":"world-source/server/world-gold.ts","reproduction":"Reproduced with test/scratch/repro.mjs case oversized_result: topup (book 10), paid research order (book 7), a=claimGoldJob(id,NOW); finishGoldJob({orderId:id,attempt:a,nowMs:NOW+1,outcome:{result:'x'.repeat(16001)}}) rejects RESULT_TOO_LARGE and the row stays state 'working'; refundGold(id,NOW+2) rejects REFUND_NOT_ENTITLED; admin refund-service rejects; claimGoldJob(id,NOW+3) returns null; claimGoldJob(id,NOW+60001) returns a new attempt and the same finish fails again, state 'working', book 7. Expected: a terminal state with the 3 GOLD refundable exactly once or the result retrievable. Negative control in the same case: finishGoldJob with outcome {failure:'provider-confirmed'} on the live attempt moves the job to 'refunded' and book back to 10.","severity":"low","snippet":" at(args.nowMs);const json=JSON.stringify('result' in args.outcome?args.outcome.result:null);if(json.length>16000)fail('RESULT_TOO_LARGE');","title":"An oversized provider result leaves a paid service job permanently non-terminal: never delivered, never failed, never refundable"},{"citation":"resolved","description":"reconcileWorldGold selects up to 128 pending paid topups ordered by created_ms and executes every credit() INSERT in one db.batch (one SQLite transaction). If any single INSERT fails, all other wallets' credits roll back, and because the failing order stays pending and oldest it is selected on every later run, so the scheduled path (worker/app.ts:155) never credits anyone again. A failing credit is reachable through the published limit: goldAdminCommand accepts product gold up to ECONOMY_MAX_INTEGER (line 129, 9223372036854775807). After one such credit the wallet balance is 2^63-1; the next topup for that wallet makes trigger world_gold_entry_apply compute balance+delta, which SQLite promotes to REAL, and economy_gold_accounts CHECK(typeof(balance)='integer') aborts the transaction. Products, links and orders are immutable, so the admin cannot cancel the poisoned order, and repair-topup fails with the same CHECK. Per-order reconcile still works, so no GOLD is lost, but automatic reconciliation and the admin repair path for everyone else stay broken. This needs an admin pricing mistake (a documented power, see the trust-assumption note), so severity is low. Fix: cap product gold far below int64 (e.g. 10^15) in goldAdminCommand and the products CHECK, and commit reconcile credits per order (or catch and skip a failing order and log it) so one order cannot block the others.","line":63,"path":"world-source/server/world-gold.ts","reproduction":"Reproduced with test/scratch/repro.mjs case int64_poison: admin products 'pack' gold=10 and 'huge' gold='9223372036854775807' embeo='1' (both accepted). Wallet B: quote 'huge', pay, reconcileWorldGold -> B.book='9223372036854775807'. Wallet B: second 'huge' quote, pay. Wallet A: quote 'pack', pay. reconcileWorldGold(db,NOW+10) -> expected A credited 10 with B's order left pending; actual: throws \"CHECK constraint failed: typeof(balance)='integer' AND balance>=0\" and A.book stays 0. reconcileWorldGold(db,NOW+11,aOrderId) -> A.book=10 (negative control). A second global reconcile throws again; goldAdminCommand repair-topup for B's second order throws the same CHECK error.","severity":"low","snippet":" if(statements.length)await db.batch(statements);","title":"One int64-overflowing topup credit aborts the whole global reconciliation batch forever; the admin product cap of 2^63-1 makes it constructible"},{"citation":"resolved","description":"Requirement 2: a timeout, UI close, restart or restored file must not by itself authorize release of a pending Mint hold; settle or cancel on proven chain outcomes. In the shared persistent ledger that rule exists only in the fixture adapter (scripts/world-gold-local-runtime.mjs:31 refuses release while used||(!revoked&&chainTime<=deadline)). settleMintGold stores deadline in world_gold_mints but never reads it; the only evidence check is Object.keys(o.evidence).length>0, and trigger world_gold_mint_update (migrations/0014:77-80) only requires evidence_json IS NOT NULL. Any server caller (a future production adapter, a maintenance script, a retry handler that treats an RPC timeout as 'released') can release a hold whose signed PaidAuthorization is still executable on chain. Once released the player's available GOLD rises and can be spent elsewhere; when the on-chain paid Mint later executes within order.deadline, the consume path fails MINT_STATE_CONFLICT and the Mint completes with its GOLD cost never debited (cross-consumer double spend). No HTTP route reaches settleMintGold and the shipped adapter is correct, so this is a missing defence at the boundary the production adapter is said to await. Fix: for state 'released' require nowMs>row.deadline or an evidence shape the ledger can verify (e.g. evidence.revoked===true with a block hash), and mirror the deadline rule in the world_gold_mint_update trigger; keep 'consumed' unchanged.","line":105,"path":"world-source/server/world-gold.ts","reproduction":"Reproduced with test/scratch/repro.mjs case early_release: fresh DB after all migrations, topup product gold=10, wallet A pays and reconciles (book 10). reserveMintGold(db,{id,wallet:A,auth:{x:1},gold:'8',chainId:1,contract,chainInstance:'run-1',deadline:NOW+300000},NOW) -> snapshot [book,reserved,available]=['10','8','2']. settleMintGold(db,{id,chainId:1,contract,chainInstance:'run-1',state:'released',evidence:{timeout:true}},NOW+1). Expected: rejected (NOW+1 < deadline and nothing proves revocation). Actual: succeeds; snapshot ['10','0','10']. A research service is then paid from the freed GOLD (book 7). settleMintGold(...state:'consumed',evidence:{tx}) for the on-chain execution of the same authorization throws MINT_STATE_CONFLICT with book still 7: the 8 GOLD is never debited. Negative control: the packaged test 'different private chain instance and empty outcome cannot release' passes, and the fixture adapter release() throws MINT_AUTH_EXECUTABLE in the same state.","severity":"low","snippet":" if(!o.evidence||!Object.keys(o.evidence).length)fail('MINT_EVIDENCE_REQUIRED');","title":"settleMintGold releases a held Mint reservation before its stored deadline on any non-empty evidence object; the ledger never checks the proven chain outcome itself"},{"citation":"resolved","description":"Holds are scoped to (chain_id, contract, chain_instance). The local EDR instance id (mint.runId) is regenerated on every process start or reset, and mint.orders, which the adapter's consume()/release() require, is memory-only; reconcileOrders (mint/service/chain.mjs:131) iterates only that map. After a restart, a SQLite restore or a reset, every 'held' row of the previous instance is unreachable: settleMintGold rejects both 'consumed' and 'released' with MINT_SCOPE_MISMATCH, no admin action (product/campaign/control/repair-topup/refund-service) can touch world_gold_holds, and worldGoldSnapshot subtracts the amount from available forever. Not releasing on restart alone is correct; the gap is that the packet provides no bounded, evidenced path at all (for example an admin or reconciliation step over world_gold_mints WHERE state='held' that settles from stored scope plus chain reads of paidOrderTokenId / paidOrderRevoked / block.timestamp > deadline, recorded with before/after state). In production the Worker contains no Mint settlement component, so the same orphaned state arises whenever the external Mint service is redeployed with a new instance id. Liveness/operability gap, not theft: available never goes negative and no GOLD is created. Merged from audit_economics (REPRO-2) and audit_permissions (runtime reopen) reports of the same gap.","line":103,"path":"world-source/server/world-gold.ts","reproduction":"Reproduced with test/scratch/repro.mjs case orphan_hold: topup (book 10); reserveMintGold with chainInstance 'edr-run-1', gold 8, deadline NOW+300s -> 10/8/2. Simulated restart: settleMintGold with chainInstance 'edr-run-2' at NOW+1e9 (deadline long past) for state 'released' and 'consumed' -> both fail MINT_SCOPE_MISMATCH; admin repair-topup and refund-service on the mint id fail their predicate check (CHECK constraint failed: accepted=1); worldGoldSnapshot at NOW+1e9 still shows ['10','8','2']. Also confirmed on the real EDR runtime (test/scratch/reset.mjs): after mint.admin({action:'reset'}) mint.orders.size is 0 and runId changed, so the injected release(id) throws MINT_ORDER_MISSING and the hold stays 'held' with available reduced by 1.","severity":"low","snippet":" if(!row||row.chain_id!==o.chainId||row.contract!==economyAddress(o.contract)||row.chain_instance!==o.chainInstance)fail('MINT_SCOPE_MISMATCH');","title":"A pending Mint hold whose chain instance or in-memory order map is gone can never be consumed or released; no evidenced operator or reconciliation path exists"},{"citation":"resolved","description":"SCOPE.md and REQUIREMENTS.md require the copied R4 Mint service to await the injected shared persistent ledger and not bypass it through a legacy/test path. Injection is instance assignment after construction (world-source/scripts/world-gold-local-runtime.mjs:23 mint.gold={view,reserve,consume,release}), but resetChain() unconditionally re-creates this.gold=new LocalGoldLedger() and clears this.orders, and admin({action:'reset'}) (line 166) calls it. The world-gold preview blocks that action only at its HTTP layer (scripts/world-gold-preview.mjs:30 LOCAL_ACTION_FORBIDDEN); the standalone mint server (mint/service/server.mjs /api/admin) and any in-process caller can still trigger it. After one reset the paid-Mint path reserves from the fixture balances (A=10,B=10) with no world_gold_holds row, while mint.beforeApproval (feature pause) still fires, so the pause survives but the money path does not; every hold that was 'held' before the reset is orphaned because mint.orders is empty and runId (chain_instance) changed. Expected: a service that received a shared ledger keeps it across reset or refuses reset. Fix: in resetChain() preserve an injected ledger (only construct LocalGoldLedger when this.gold is unset or is itself a LocalGoldLedger) and refuse 'reset' while shared holds are outstanding, instead of relying on one caller's route filter.","line":33,"path":"mint/service/chain.mjs","reproduction":"Reproduced with test/scratch/reset.mjs on the real EDR runtime (cwd repo root, contracts and world-source dependencies installed): r=await createGoldLocalRuntime(); wallet A quotes and pays 'gold-pack' (shared book 10). r.mint.gold.reserve(id,'A',1n,{auth:{wallet:A,deadline,orderId:id},packet:{}}) -> one world_gold_holds row 'held'. Negative control: the same reserve for wallet C (no GOLD) rejects. await r.mint.admin({action:'reset'}) -> r.mint.gold instanceof LocalGoldLedger is true, r.mint.orders.size is 0, runId changed. r.mint.gold.reserve(id4,'A',1n) succeeds against the fixture ledger (fixture view A book 10 reserved 1) while world_gold_holds still has only the original held row; mint.state() reports wallets' gold from the fixture ledger (B book 10 although B never bought GOLD). The shared snapshot for A stays [10,1,9] permanently because the original hold can no longer be settled.","severity":"low","snippet":"  this.gold=new LocalGoldLedger();this.orders=new Map();this.records=[];this.receipts=[];this.packets=new Map();this.submissions=new Set();","title":"Copied Mint service resetChain() silently replaces the injected shared persistent GOLD ledger with the in-memory fixture ledger and strands existing holds"},{"citation":"resolved","description":"Requirement 4 says a paid service debit creates one durable delivery job. payEconomyOrderGold (economy-ledger.ts:276-280) commits the journal debit, paid status and consumed challenge in one batch that contains no world_gold_jobs insert; the job is derived later by reconcileWorldGold (world-gold.ts:61), which the browser calls after payment or the Worker cron calls here. In syncEconomy the call is sequential after await syncEconomyChain (line 154) with no separate try/catch, and only when economy.config() is non-null and ECONOMY_CHAIN_LIMITER is bound; the scheduled() handler swallows any throw of the whole syncEconomy promise. So any chain-sync failure (ECONOMY_RPC_RATE_LIMITED from the limiter wrapper on line 151, an RPC outage, a halted economy) skips GOLD job creation and the 'failed'->'refunded' pass for that run, although neither needs chain data. Until the player manually posts /api/gold/orders/:id/reconcile, a debited service order has no job and a provider-confirmed failure stays unrefunded, precisely during the RPC incidents when degradation is likely. No GOLD is lost (the paid order is immutable, reconcile is idempotent), so this is a liveness and specification-conformance defect. Fix: insert the world_gold_jobs row in the payEconomyOrderGold batch (INSERT..SELECT guarded by a world_gold_links row of kind 'service', when the shared tables exist), and run reconcileWorldGold independently of syncEconomyChain (own try/catch, regardless of chain sync outcome). Merged from audit_flow (cron gating) and audit_math/audit_economics (atomicity) reports.","line":155,"path":"world-source/worker/app.ts","reproduction":"Atomicity reproduced with test/scratch/repro.mjs case job_not_atomic: fresh DB, research product gold=3, wallet book 10; goldQuote('research'), createEconomyGoldChallenge + payEconomyOrderGold with a valid signature -> economy_orders.status='paid', yet SELECT COUNT(*) FROM world_gold_jobs WHERE order_id=? returns 0 until reconcileWorldGold runs (then 1, state 'queued'). Cron gating verified in code: worker/app.ts:146-156 awaits syncEconomyChain on line 154 before reconcileWorldGold on line 155 inside one async function whose rejection is caught only at scheduled() line 195; a throw from the rpc wrapper (line 151, ECONOMY_RPC_RATE_LIMITED) or from syncEconomyChain therefore prevents line 155 from executing. Expected: the job row exists when payEconomyOrderGold returns, and reconcile/refund run even when chain sync fails.","severity":"low","snippet":"    await reconcileWorldGold(env.DB,at);","title":"Paid service GOLD debit and delivery-job creation are not one transaction, and the scheduled job/refund pass is gated behind chain RPC sync succeeding"},{"citation":"resolved","description":"The CAS-in-SQL pattern records every accepted predicate as a row keyed by a fresh UUID. Only admin actions need their before_json row kept (the audit insert on line 125 joins it); quote and reserve check rows are never read again, yet nothing deletes them and the scheduled housekeeping in worker/app.ts does not touch the table. On D1 this is unbounded row growth driven by any authenticated player (two rows per /api/gold/quote, bounded only by the per-IP ECONOMY_LIMITER), and the recovery planner snapshots the whole table into every plan (TABLES includes WORLD_GOLD_TABLES), so growth also pushes recovery toward RECOVERY_REQUIRES_LARGER_REVIEWED_PLAN. Fix: prune rows with before_json IS NULL in the cron (or use a rollback-only predicate such as a trigger-raised scratch insert deleted in the same batch), and exclude non-audit rows from the recovery snapshot.","line":8,"path":"world-source/server/world-gold.ts","reproduction":"Reproduced with test/scratch/repro.mjs case write_checks: after setup with one admin product the table has 3 rows; 20 goldQuote calls by one wallet add 40 rows (one auth check plus one product/control check each), none removed afterwards (43 rows). Expected: bounded storage or rows scoped to the audit trail only. Actual: linear growth with player quotes.","severity":"info","snippet":"const check=(db:D1Database,predicate:string,...args:unknown[])=>st(db,`INSERT INTO world_gold_write_checks(id,accepted) SELECT ?,CASE WHEN ${predicate} THEN 1 ELSE 0 END`,crypto.randomUUID(),...args);","title":"world_gold_write_checks grows by one permanent row per quote predicate, reservation and admin command with no pruning"},{"citation":"resolved","description":"The minute cron only calls reconcileWorldGold, which turns paid GOLD service orders into world_gold_jobs rows in state 'queued'. claimGoldJob/finishGoldJob are invoked only by the local fixture runtime (scripts/world-gold-local-runtime.mjs:42). Because a refund requires state 'failed', which only finishGoldJob can set, a player debited for research/fortune in production would have no delivery, no refund, and no admin remedy (refund-service requires state IN ('failed','refunded')). This is consistent with SCOPE.md (real providers are a release gate; the Worker economy config returns null), so it is a documented deployment gap rather than an implementation vulnerability. Release checklist items: a durable worker that claims jobs with orderId as the provider idempotency key; an explicit classification where only proven terminal nondelivery maps to {failure}; and a check that the 'service' feature cannot be enabled while no consumer is bound.","line":155,"path":"world-source/worker/app.ts","reproduction":"grep -rn 'claimGoldJob\\|finishGoldJob' world-source/worker world-source/server returns only the definitions in server/world-gold.ts; no Worker code path calls them. State walk: with migration 0014 applied, a 'service' product enabled and a non-null economy config, a paid GOLD order reconciled by cron becomes world_gold_jobs state 'queued' and stays there; refundGold/refund-service then reject with REFUND_NOT_ENTITLED (requires state 'failed'), as the packaged test 'timeout does not refund' shows for the queued/working states.","severity":"info","snippet":"    await reconcileWorldGold(env.DB,at);","title":"Deployed Worker creates queued service jobs but contains no claim/finish consumer, so production paid service orders would have neither delivery nor a lawful refund path"},{"citation":"resolved","description":"Documented powers, not bypasses. (1) The three admin wallets (server/aimarket-admin.ts:8-12) include the production treasury (economy-config.ts), so the payment receiver is also the price/pause/campaign administrator. (2) The 'product' action lets an admin publish a topup version with any embeo >= 1 wei and gold up to 2^63-1 on this line; through the self-serve quote route that is economically an unbounded GOLD grant to whoever buys at that version. It is versioned, immutable, audited and never alters a paid order's locked price, so it is a documented power rather than the forbidden arbitrary balance setter, but it is also the enabler of the int64 batch-poisoning finding. (3) settleMintGold (line 101) and finishGoldJob (line 79) trust their server caller for chain evidence and for the terminal-failure classification; the only callers in this packet are the local fixture adapter. (4) reserveMintGold performs no session check; wallet identity for Mint reservations is delegated to the Mint service, whose packet authentication is a loopback session token. Checked and found sound in this review: same-origin and Sec-Fetch-Site gating and Origin requirement on writes (world-gold-api.ts:30,33), expected_address matching, double readSession plus SQL-level session EXISTS inside quote/admin batches (authCheck), EOA/ECDSA requirement for writes, admin idempotency with payload comparison, version CAS on controls/products/campaigns, hold/available triggers covering legacy GOLD debits, once-only topup/refund references, half-open UTC campaign bounds in World, trial never unlocking the external profile, and the recovery planner's balance=journal+entries invariant.","line":129,"path":"world-source/server/world-gold.ts","reproduction":"Not an exploit path. Verification: goldAdminCommand(db,{wallet:OWNER,...,command:{action:'product',product:'huge',kind:'topup',title:'h',gold:'9223372036854775807',embeo:'1',enabled:true,version:1}}) succeeds (test/scratch/repro.mjs case int64_poison); grep -rn settleMintGold world-source/worker world-source/server returns only the definition; AIMARKET_ACTIVITY_ADMIN_WALLETS in server/aimarket-admin.ts lists the treasury address from server/economy-config.ts.","severity":"info","snippet":"  const gold=economyUint(cmd.gold,ECONOMY_MAX_INTEGER),embeo=cmd.kind==='topup'?economyUint(cmd.embeo):null;","title":"Trust assumptions bounding the permission model: admin product grants are unbounded in gold, the treasury wallet is also an administrator, and settlement facts are trusted from server callers"}],"hash":"8ddc35ffcf9ca9167f1ac2a3584dadaff09e3dcad1325ee91c0f4f0ec6ae1557","nodeId":"c757fa5f-4ee6-4de4-a129-106d22ce23ab","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds 12 merged, reproduced findings. The working tree is clean apart from ignored scratch files.\n\n**What I did.** I installed the locked world-source and contracts dependencies, ran the packet's own world-gold, API, AIMarket-auth and Python suites (all green, matching the recorded evidence) and `tools/verify-source.mjs` (source bytes match). I then re-ran every specialist claim against the code with my own scripts under `test/scratch/`: a Node script over the real migrations and ledger functions, a Python script against the AIMarket consumer, and an EDR-backed script for the copied Mint service. Seventeen specialist findings reduced to 12 after merging duplicates. Nothing was dropped as non-reproducing; every kept finding has the exact triggering state and expected-versus-actual output recorded, with negative controls.\n\n**Findings kept, by severity.**\n- **Medium, Python trial boundary.** Four specialists reported the same defect. When World answers \"active\" and the reply is checked after `endsMs`, `_identity()` raises BridgeUnavailable instead of treating the trial as ended, so EMBEO holders get an error rather than the balance fallback. Reproduced for both portal reads and login.\n- **Medium, recovery planner crash.** On a database without migration 0014, which is the deployed schema today, both the planner and the read-only preflight throw a TypeError instead of a coded refusal.\n- **Low, service pause bypass.** A quote issued before the pause can still be debited and queued for 15 minutes.\n- **Low, oversized result.** A result over 16000 characters leaves the job cycling in \"working\" with no refund path. A failure outcome on the same attempt refunds correctly, which confirms the mechanism.\n- **Low, int64 batch poisoning.** One overflowing credit, constructible through the admin product cap, aborts every global reconciliation run; per-order reconcile still works.\n- **Low, early Mint release.** The ledger accepts any non-empty evidence to release a hold before its deadline, enabling a cross-consumer double spend if a future adapter is careless.\n- **Low, orphaned holds.** Two specialist reports merged: after an instance change there is no evidenced path to settle a hold, confirmed on both SQLite and the live EDR runtime.\n- **Low, Mint reset swaps ledger.** Confirmed on EDR: `admin reset` replaces the injected shared ledger with the fixture one and strands the existing hold.\n- **Low, job atomicity and cron gating.** Three reports merged: the job row only appears on reconcile, and the cron reconcile sits behind chain sync success.\n- **Info.** Unbounded write-check rows, no production job consumer, and the trust-assumption summary.\n\n**Limits.** No Solidity finding reached high or critical, so no Foundry proof was required. The cron-gating half of the atomicity finding is verified by code reading rather than an instantiated Worker. Fixture success and this review do not authorize deployment; the production consumer, Mint settlement adapter and null economy config remain release gates.","treeHash":null,"usage":{"cachedInputTokens":2515546,"inputTokens":674,"model":"claude-fable-5-1","outputTokens":32687,"runtime":"claude","turns":34,"wallClockMs":475809}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e6de8d4c6cf97551","findings":[{"citation":"resolved","description":"Requirement 2 says a timeout, UI close, process restart or restored SQLite file must not by itself authorize release of a pending Mint hold, and that holds are settled or cancelled using proven chain outcomes. In the shared persistent ledger that rule exists only in the fixture adapter (world-source/scripts/world-gold-local-runtime.mjs:31 refuses release while `used||(!revoked&&chainTime<=o.auth.deadline)`). settleMintGold stores `deadline` in world_gold_mints but never reads it, and the only evidence check is `Object.keys(o.evidence).length>0`; the SQL trigger world_gold_mint_update (migrations/0014:77-80) only requires evidence_json IS NOT NULL. Any server caller (a future production adapter, a maintenance script, a retry handler that treats an RPC timeout as 'released') can release a hold whose signed PaidAuthorization is still executable on chain. Once released, the player's available GOLD rises by the hold amount and can be spent on a service or a second Mint; when the original paid Mint later executes (block.timestamp <= order.deadline), the adapter's consume path calls settleMintGold(consumed) which fails with MINT_STATE_CONFLICT, so the Mint completes with its GOLD cost never debited (double spend of GOLD across consumers). Severity is low because no HTTP route reaches settleMintGold and the shipped fixture adapter is correct; it is a missing defence at the boundary the production adapter is explicitly said to await. Fix: in settleMintGold, for state 'released' require `nowMs>row.deadline` OR an evidence shape the ledger can check (e.g. evidence.revoked===true with a block hash), and add the same `deadline` condition to the world_gold_mint_update trigger; keep 'consumed' unchanged.","line":105,"path":"world-source/server/world-gold.ts","reproduction":"Fresh DB after all migrations. Admin creates topup product gold=10. Wallet A pays a topup (ingestEconomyEvents payment event matching the order) and reconcileWorldGold -> book 10. reserveMintGold(db,{id,wallet:A,auth:{x:1},gold:'8',chainId:1,contract,chainInstance:'run-1',deadline:NOW+300000},NOW) -> snapshot [book,reserved,available]=['10','8','2']. Call settleMintGold(db,{id,chainId:1,contract,chainInstance:'run-1',state:'released',evidence:{timeout:true}},NOW+1). Expected: rejected (NOW+1 < deadline NOW+300000 and nothing proves revocation). Actual: succeeds; snapshot becomes ['10','0','10']. Then settleMintGold(...,state:'consumed',evidence:{tx}) for the on-chain execution of the same authorization throws MINT_STATE_CONFLICT, so the 8 GOLD is never debited. Negative control: the fixture adapter release() in world-gold-local-runtime.mjs:31 throws MINT_AUTH_EXECUTABLE in the same state. Probe script run with node against tests/d1-sqlite.mjs reproduced exactly these outputs.","severity":"low","snippet":" if(!o.evidence||!Object.keys(o.evidence).length)fail('MINT_EVIDENCE_REQUIRED');","title":"settleMintGold releases a held Mint reservation before its stored deadline on any non-empty evidence object; the ledger itself never enforces \"proven chain outcome\""},{"citation":"resolved","description":"reconcileWorldGold selects pending paid topups ORDER BY created_ms LIMIT 128 and executes all credit() inserts in one db.batch (one SQLite transaction). If any single INSERT fails, every other wallet's credit in the batch is rolled back, and because the failing order stays pending and is the oldest, it is included in every later run: the scheduled path worker/app.ts:155 `await reconcileWorldGold(env.DB,at)` then never credits anyone again. A failing credit is constructible through the published numeric limit: goldAdminCommand accepts product gold up to ECONOMY_MAX_INTEGER (line 129, 9223372036854775807). After one such credit the wallet's balance is 2^63-1; the next topup for that wallet makes the trigger world_gold_entry_apply compute balance+delta, which SQLite promotes to REAL, and economy_gold_accounts CHECK(typeof(balance)='integer') aborts the whole transaction. Products, links and orders are immutable, so the admin cannot cancel or neutralise the poisoned order; repair-topup fails with the same CHECK error. Per-order reconcile (the player's 'Check original order' button) still works because it filters by order id, so funds are not lost, but automatic reconciliation and the admin repair path are disabled. The REQUIREMENTS note that integer limits are technical constraints; this shows the constraint is enforced in a place (apply trigger) that converts an admin pricing mistake into a shared availability failure. Fix: cap product gold far below int64 (e.g. 10^15) at goldAdminCommand and in the products CHECK, and make reconcileWorldGold commit per order (or catch and skip a failing order while logging it) so one order cannot block the others.","line":63,"path":"world-source/server/world-gold.ts","reproduction":"Fresh DB. Admin: product 'pack' gold=10 and product 'huge' gold='9223372036854775807' embeo='1' (both accepted). Wallet B: quote 'huge', pay, reconcileWorldGold -> B.book='9223372036854775807'. Wallet B: second 'huge' quote, pay. Wallet A: quote 'pack', pay. reconcileWorldGold(db,NOW+10) -> expected: A credited 10 (and B's order left pending); actual: throws 'CHECK constraint failed: typeof(balance)=\\'integer\\' AND balance>=0' and A.book stays 10 instead of 20. reconcileWorldGold(db,NOW+11,aOrderId) (per-order) -> A.book=20 (negative control). goldAdminCommand repair-topup for B's second order -> same CHECK error. Raw check: SELECT typeof(9223372036854775807+1) -> 'real'. All reproduced with the node:sqlite D1 adapter in tests/d1-sqlite.mjs.","severity":"low","snippet":" if(statements.length)await db.batch(statements);","title":"Global reconcileWorldGold commits up to 128 credits in one all-or-nothing batch; a single pending topup whose credit overflows int64 (allowed by the admin gold cap of 2^63-1) permanently aborts cron r"},{"citation":"resolved","description":"World's /api/aimarket-auth/session computes world_trial.active as nowMs<endsMs with the Worker clock (world-gold.ts goldCampaign). The Python consumer re-evaluates the same half-open bound with its own clock, but instead of treating the disagreement as 'trial inactive' it treats the entire identity payload as malformed and raises BridgeUnavailable('invalid_world_trial') from _identity. In session_eligible only BridgeDenied is caught (lines 364-366), so the exception propagates to the portal request and the holder's balance eligibility (_balance) is never consulted; in complete_login the same raise aborts a login whose holder would pass _balance(fresh=True). The window is the network latency between World's answer and the consumer's check plus any clock skew between the two hosts, at every trial end (and at a manual stop that lands in the same window). It is a correctness/consistency defect at the cross-language boundary, not a privilege escalation: the failure direction is denial. Fix: in _identity, when trial['active'] is True but endsMs<=_now_ms(), normalise to active=False (or let _world_trial return False) rather than raising; keep the raise for structurally invalid claims (non-int endsMs, version<1, wrong key set).","line":249,"path":"aimarket/web/world_auth.py","reproduction":"python3 with PYTHONPATH=aimarket, AIMARKET_BRIDGE_SECRET set, _now_ms patched to NOW, a world_sessions row with profile 'world'. Patch _world_post to return {issuer,audience,scope:'research:read',address,expires_at:NOW+10000,bridge_session,'world_trial':{'active':True,'endsMs':NOW,'version':1}} (World answered 'active' one millisecond earlier) and _balance to return True. Expected: session_eligible(token,require_profile='world') == (address, True) (trial over, holder eligible by balance). Actual: raises web.world_auth.BridgeUnavailable: invalid_world_trial. Same payload in complete_login(state,flow,code,profile='world') with _balance True: expected LoginResult, actual BridgeUnavailable. Negative control: the packaged test_world_trial.py cases with endsMs=NOW+1000 pass. Reproduced with a 2-test unittest file (both tests error with the traceback above).","severity":"low","snippet":"            or (trial[\"active\"] and (trial[\"endsMs\"] is None or trial[\"endsMs\"] <= _now_ms()))):","title":"AIMarket consumer rejects the whole World identity (BridgeUnavailable) when World reports an active trial whose endsMs has already passed by the consumer clock, denying research access to an otherwise"},{"citation":"resolved","description":"Requirement 4 states 'Paid service debit creates one durable delivery job'. The GOLD debit happens in payEconomyOrderGold (economy-ledger.ts:276-280: journal insert, order paid, challenge consumed) and that batch contains no world_gold_jobs insert; the job is created later by reconcileWorldGold (this line), which the browser calls after payment (WorldGoldPanel.tsx:59 client.reconcile) or the Worker cron calls from worker/app.ts:155 only after economy.config() is non-null (currently null per SCOPE.md). If the browser's follow-up request is lost, the player has been debited but no queued job exists until the next explicit 'Check original order' click. No GOLD is lost (the paid order is immutable, reconcile is idempotent, and economy_fulfillments records the entitlement via trigger economy_order_paid), so this is a liveness and specification-conformance note rather than a vulnerability. Fix: add the world_gold_jobs INSERT..SELECT (guarded by a world_gold_links row with kind='service') to the payEconomyOrderGold batch when the shared tables exist, or document the lazy creation and ensure the cron path is live before launch.","line":61,"path":"world-source/server/world-gold.ts","reproduction":"Fresh DB, admin product 'research' kind service gold=3, wallet with book 10. goldQuote('research') -> order; createEconomyGoldChallenge + payEconomyOrderGold with a valid signature -> order status 'paid', economy_gold_accounts.balance 7. SELECT COUNT(*) FROM world_gold_jobs WHERE order_id=? -> expected 1 (durable job created with the debit), actual 0 until reconcileWorldGold(db,now) or reconcileWorldGold(db,now,orderId) is called, after which it is 1 with state 'queued'.","severity":"info","snippet":"  else statements.push(st(db,`INSERT INTO world_gold_jobs(order_id,wallet,state,updated_ms) SELECT id,wallet,'queued',? FROM economy_orders WHERE id=? ON CONFLICT(order_id) DO NOTHING`,nowMs,o.id));","title":"Paid service debit and delivery-job creation are not one transaction; the job row appears only when reconcileWorldGold runs, and the scheduled path is inert while the release config returns null"}],"hash":"984e4e2a8dbc44bf7be9b16e8b0f55bc2fb4e244a4f55ec3c1eff2e6ed40c8fd","nodeId":"d1fa3b69-640a-4f0f-9ca4-9696fad33dcf","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds four substantiated findings, each reproduced against the tree with the packaged `node:sqlite` D1 adapter or the Python stdlib test style. No critical or high defects were found, so no Foundry proof was required.\n\n**What I audited.** Migration 0014 triggers and the shared book/held/available invariants, `world-gold.ts` quote/credit/refund/lease/mint-settlement paths, the legacy ledger and settlement day arithmetic, payment evidence decoding in `economy-rpc.ts`, recovery replay, the 18-decimal client helpers, the Python trial consumer, and the three retained Solidity contracts plus the R4 Mint contract's free/paid supply rules. The existing world-gold suites (36 tests) pass locally.\n\n**Findings written**\n\n- **Low, `world-gold.ts:105`.** `settleMintGold` releases a hold before its stored `deadline` on any non-empty evidence object. The ledger never reads `deadline`; only the fixture adapter enforces the chain-outcome rule. Reproduced: release at `NOW+1` with `{timeout:true}` succeeds, and the later real consumption fails with `MINT_STATE_CONFLICT`, so the paid Mint's GOLD is never debited.\n- **Low, `world-gold.ts:63`.** Global reconcile commits up to 128 credits in one transaction. An admin price of 2^63-1 GOLD (accepted at line 129) makes the next credit for that wallet overflow to REAL and fail the balance CHECK, which rolls back every other wallet's credit on every cron run forever. Per-order retry still works; admin repair does not.\n- **Low, `world_auth.py:249`.** When World reports an active trial whose `endsMs` is already past by the consumer's clock, the consumer raises `BridgeUnavailable` instead of treating the trial as over, so an eligible EMBEO holder is denied at the UTC end boundary in both `session_eligible` and `complete_login`.\n- **Info, `world-gold.ts:61`.** The GOLD service debit and the delivery job are not one transaction. The job appears only when reconcile runs, and the cron path is inert while the release config returns null. No funds are lost.\n\n**Negative controls and leads refuted.** Day-boundary math is consistent half-open in settlement and recovery. Hold, refund and topup triggers reject forged amounts and duplicate references. Lease fencing rejects stale workers at the exact boundary. The session-address case lead was refuted because `auth.ts:508` stores addresses lowercased. Solidity boundaries are unchanged and arithmetic there is checked.\n\n**Release gates, not findings.** Mainnet finality, real provider delivery, production D1 capacity and the missing production Mint adapter remain unverified by local fixtures. Nothing here authorizes deployment.","treeHash":null,"usage":{"cachedInputTokens":3408078,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":54346,"runtime":"claude","turns":53,"wallClockMs":793217}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8e25456c4ae69acf","findings":[{"citation":"resolved","description":"_identity() treats a World introspection reply with world_trial.active=true and endsMs<=Python's now as a malformed response and raises BridgeUnavailable('invalid_world_trial'). World computes `active` with its own clock at the instant it answers (server/aimarket-auth.ts introspect -> goldCampaign(db,at), half-open now<endsMs). Any reply produced in the last milliseconds of a campaign that is received after endsMs (network latency alone, no clock skew needed) hits this branch. session_eligible() (line 363) only catches BridgeDenied, so the exception escapes before line 370, where `_world_trial(...) or _balance(address)` would have correctly denied the expired trial and fallen back to the holder balance check. complete_login() for the portal callback fails the same way. The result is inconsistent with the requirement that the trial ends at the UTC half-open boundary while holder access is unaffected: at the boundary, holders who qualify on their own EMBEO balance get a 'World unavailable' error instead of access, and a new portal login fails. The fresh trial recheck is correct about denying the trial; the defect is that it fails closed for the whole request instead of only for the trial claim. Fix: when active is true but endsMs<=now, treat the trial as inactive (e.g. set trial active False, or let _world_trial's existing `_now_ms() < endsMs` check handle it) instead of raising, and keep raising only for structurally invalid trial objects.","line":249,"path":"aimarket/web/world_auth.py","reproduction":"Stdlib-only, run against aimarket/: set AIMARKET_BRIDGE_SECRET (40 chars) and a temp AIMARKET_WORLD_STATE_DB. Insert a world_sessions row with profile='world' for address 0xaaaa…aa. Patch _world_post to return {issuer,audience:'aimarket',scope:'research:read',address,expires_at:now+600000,world_trial:{active:True,endsMs:now-20,version:1}} (World answered 20 ms before end; the reply arrives after end). Patch _balance to return True (the wallet is a real holder). Call session_eligible(tok, require_profile='world'). Expected: (address, True), with the trial ignored and the holder balance used. Actual: raises BridgeUnavailable('invalid_world_trial'). Negative control: the same call with world_trial.active=False returns ('0xaaaa…', True). Regression test: add this case to aimarket/test_world_trial.py, asserting that session_eligible returns eligible via balance and that _world_trial is False.","severity":"medium","snippet":"            or (trial[\"active\"] and (trial[\"endsMs\"] is None or trial[\"endsMs\"] <= _now_ms()))):","title":"Python AIMarket consumer raises BridgeUnavailable instead of denying the trial when World's 'active' trial reply arrives at or after endsMs, locking out genuine EMBEO holders at the boundary"},{"citation":"resolved","description":"The world_gold_controls 'service' switch is only checked when a quote is created (world-gold.ts goldQuote: goldFeature + in-batch check). A quote stays payable for 15 minutes. payEconomyOrderGold, the only path that debits GOLD for a World service order, and createEconomyGoldChallenge never look at world_gold_controls. So after an owner pauses 'service', a quote obtained earlier can still get a new GOLD challenge and signature, a book debit and a paid status, and reconcileWorldGold (scheduled, and reachable by the user via POST /api/gold/orders/:id/reconcile) then creates a queued delivery job. The requirement says a feature pause stops new admissions while existing paid work stays serviceable. An unsigned, undebited quote is not yet a paid admission, and unlike an on-chain EMBEO topup it can be refused server-side at no loss to the player. This assumes that 'admission' means the debit; if the owner intends a quote to count as admitted, document that. Fix: when the order has a world_gold_links row of kind 'service', add `AND EXISTS(SELECT 1 FROM world_gold_controls WHERE feature='service' AND enabled=1)` to the journal INSERT predicate in payEconomyOrderGold (and refuse challenge creation). Keep reconcile, delivery and refunds of already-paid orders unaffected.","line":277,"path":"world-source/server/economy-ledger.ts","reproduction":"Using the tests/world-gold.test.mjs setup(): f.topup() (book 10), then id=await f.quote('research'), then f.admin({action:'control',feature:'service',enabled:false,version:0}). Next, createEconomyGoldChallenge(... orderId:id ...), then payEconomyOrderGold with the signed challenge, then reconcileWorldGold(db,NOW). Expected: FEATURE_PAUSED, book stays 10, no job. Actual (reproduced): no error, book=7, world_gold_jobs has one row with state 'queued', and controls show service enabled=0. Negative control: a new goldQuote('research') after the pause correctly rejects with FEATURE_PAUSED. Regression test: assert that the payment rejects while paused, that the pending order stays unpaid with no journal row, and that after re-enabling the same order can be paid once.","severity":"low","snippet":"      bind(db,`INSERT INTO economy_gold_journal(id,wallet,delta,kind,reference,created_ms) SELECT ?,?,-CAST(? AS INTEGER),'service-payment',?,? WHERE EXISTS(SELECT 1 FROM economy_orders WHERE id=? AND wallet=? AND status='pending' AND expires_ms>?) AND EXISTS(SELECT 1 FROM economy_gold_challenges WHERE order_id=? AND request_id=? AND nonce=? AND consumed_ms IS NULL AND expires_ms>?) AND ${auth.sql} AND NOT EXISTS(SELECT 1 FROM economy_chain_state WHERE config_id=? AND halted_reason IS NOT NULL)`,reference,wallet,existing.amount,reference,now,id,wallet,now,id,args.requestId,challenge.nonce,now,...auth.values,config.id),","title":"Pausing the 'service' feature does not stop new GOLD service debits/job admissions for quotes issued before the pause"},{"citation":"resolved","description":"A GOLD service debit (payEconomyOrderGold) commits the debit and paid status, but no world_gold_jobs row. The durable job is only derived later by reconcileWorldGold, which also refunds jobs in the 'failed' state. In the scheduled Worker, reconcileWorldGold runs only after `await syncEconomyChain(...)` on line 154, and only when economy.config() is non-null and ECONOMY_CHAIN_LIMITER is bound. Any throw from chain sync (e.g. ECONOMY_RPC_RATE_LIMITED from the limiter wrapper just above, an Alchemy outage, or a halted economy) skips GOLD job creation and refunds for that cron run. Neither operation needs chain data. Until the player manually calls POST /api/gold/orders/:id/reconcile, a debited service order has no job, and a provider-confirmed failure stays 'failed' and unrefunded. This weakens the 'paid service debit creates one durable delivery job' and 'lawful refunds remain serviceable' requirements during exactly the RPC incidents where a pause or degradation is likely. Fix: create the job in the same D1 batch as the GOLD debit (INSERT INTO world_gold_jobs ... WHERE EXISTS paid order with a service link), and/or run reconcileWorldGold independently of syncEconomyChain (separate try/catch, before or regardless of chain sync).","line":154,"path":"world-source/worker/app.ts","reproduction":"Build the Worker via createWorker(..., economy:{config:async()=>cfg, rpc:()=>async()=>{throw Error('ECONOMY_RPC_RATE_LIMITED')}}) with env.DB holding a paid World 'research' order (GOLD debited by payEconomyOrderGold) and no world_gold_jobs row, plus a separate job in state 'failed'. Invoke scheduled(). Expected: the paid order gets a 'queued' job and the failed job becomes 'refunded' with one service-refund entry. Actual: syncEconomyChain throws on line 154, reconcileWorldGold on line 155 never runs, the order has no job and the failed job stays unrefunded. Negative control: with a working rpc fixture the same scheduled() call creates the job and refunds. Regression test: assert job creation is atomic with the debit (job row exists immediately after payEconomyOrderGold returns) and that reconcile runs when chain sync fails.","severity":"low","snippet":"    await syncEconomyChain(env.DB,config,{rpc,nowMs:at});","title":"Automatic job creation and terminal-failure refunds for already-debited GOLD service orders depend on chain RPC sync succeeding first"}],"hash":"b8853d8ede35b8741f272d7ca94d3ddb25ce6fcc8cbb80a7a10513b97cc8ceb9","nodeId":"359f82cf-8ca0-4223-8c2c-eae02406f896","outcome":"completed","summary":"I found three defects: one medium and two low. None is a critical or high ledger-integrity break in the code I covered. All three are in `.imd-findings.json`, and the repository is otherwise unchanged.\n\n## Findings\n\n1. **Medium: the AIMarket trial check fails the whole request at the trial's end** (`aimarket/web/world_auth.py:249`)\n   - **Trigger:** World works out \"trial active\" with its own clock at the moment it answers. If that answer arrives just after the trial's end time, which network delay alone can cause, the Python consumer treats it as a broken reply and raises an error.\n   - **Impact:** `session_eligible` only catches the \"denied\" error, so it never reaches the fallback that checks the wallet's EMBEO balance. Real holders get \"World unavailable\" at the trial boundary instead of access. A new portal login fails the same way.\n   - **Reproduced** with a standard-library Python script. Negative control: the same request with the trial marked inactive returns the address as eligible.\n   - **Fix:** treat an expired \"active\" trial as inactive rather than as an error.\n\n2. **Low: pausing \"service\" doesn't stop debits for quotes issued before the pause** (`world-source/server/economy-ledger.ts:277`)\n   - **Trigger:** the pause switch is only checked when a quote is created, and a quote stays payable for 15 minutes.\n   - **Impact:** reproduced: after the pause, an earlier quote was paid, the balance went from 10 to 7, and a delivery job was queued. Negative control: a new quote after the pause is correctly refused.\n   - **Assumption:** this counts the GOLD debit, not the quote, as the \"admission\". If you mean the quote to be the admission, it's a documentation point rather than a defect.\n\n3. **Low: job creation and refunds for GOLD service orders wait on blockchain sync** (`world-source/worker/app.ts:154`)\n   - **Trigger:** a GOLD service debit doesn't create the delivery job in the same transaction. Jobs and refunds come later from a scheduled step, which runs only after the blockchain sync succeeds. If that sync fails (RPC rate limit or outage), the error is swallowed and the step is skipped.\n   - **Impact:** debited orders have no job, and confirmed failures stay unrefunded, until the player triggers it by hand.\n   - **Not run:** I confirmed this by reading the code, not with a test.\n\n## What held up\nI tried these failure paths and didn't break them:\n- **Holds:** available = book minus held is enforced by a database trigger on every balance update, which covers old and new consumers. Two concurrent mints can't reserve the same GOLD.\n- **Once-only credit:** topup and refund entries use unique references tied to the original order's locked amount, so automatic reconciliation and admin repair can't credit twice. Admin can't set a balance or grant an arbitrary amount.\n- **Payment matching:** order, payer, amount, token and treasury must all match.\n- **Delivery jobs:** lease and attempt checks stop stale workers, and a late completion can't overwrite a refunded job.\n- **Mint holds:** settling or releasing a hold is bound to the chain, contract and chain instance, and needs evidence. A restart loses the in-memory order, so the hold stays locked; nothing releases it.\n- **Admin:** limited to the three owner wallets, with immutable before/after records written in the same transaction.\n- **Recovery:** purchased GOLD and refunds are counted in the balance totals and not mistaken for missed staking rewards.\n- **Login and the Python consumer:** the AIMarket login and Python session flow recheck the live session.\n\n## Coverage and limits\n- **Tested:** I ran the existing `world-gold` suite (22 pass) and my reproductions in a copy under `/tmp`, so nothing was written to the repo.\n- **Read in depth:** migration 0014, the core GOLD module and its API, the old payment and recovery paths, the Mint service and its local wiring, the AIMarket login, and the Python consumer.\n- **Less deep:** the full World login/session code, the client UI, and t","treeHash":null,"usage":{"cachedInputTokens":4069781,"inputTokens":82,"model":"claude-opus-5-5","outputTokens":31756,"runtime":"claude","turns":41,"wallClockMs":411132}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9df7d5d52e83c572","findings":[{"citation":"resolved","description":"snapshots() deliberately skips the world_gold_* tables when none of them exist (a pre-0014 database, which is the state of the deployed World D1 today and of any 'controlled pre-0013 export' the planner says it supports). But build() later calls at(old,'world_gold_entries') unconditionally; at() does s.find(...)!.rows, so on such a database the planner throws 'TypeError: Cannot read properties of undefined (reading 'rows')' instead of a reviewed RECOVERY_* refusal or a plan. preflightEconomyRecovery only catches RecoveryCapacityError, so the read-only diagnostic crashes the same way. The staking-correction workflow (requirement 5: preserve finalized evidence, include new sources) is therefore unusable exactly on the schema it must run against until 0014 is deployed, and an operator cannot tell a schema gap from a planner bug. All existing recovery tests open the DB with every migration, so the pre-0014 path is untested. Fix: guard the new-source tables the same way snapshots() does (treat a missing world_gold_entries snapshot as an empty row set, or return fail('RECOVERY_SCHEMA_MISSING') explicitly), and add a test that runs the planner with migrationFiles().filter(f=>!f.startsWith('0014')).","line":204,"path":"world-source/server/economy-recovery.ts","reproduction":"Run test/scratch/recovery-pre0014.test.mjs (node --test). Setup: two in-memory D1 adapters opened with all migrations except 0014; old DB has economy_chain_state(from.id,last_block=102,last_hash=hash(102),through_ms=NOW+2d); shadow DB has the same row for the successor config (deploymentBlock 99); no events. Call prepareEconomyRecovery(db,shadow,from,to,{nowMs:through+1}). Expected: a plan with zero adjustments (or a RECOVERY_* coded refusal). Actual: TypeError: Cannot read properties of undefined (reading 'rows'); preflightEconomyRecovery throws the same TypeError instead of returning {status:'blocked'|'ready'}.","severity":"medium","snippet":" const totals=new Map<string,bigint>();for(const j of [...journal,...at(old,'world_gold_entries')])totals.set(j.wallet!,(totals.get(j.wallet!)??0n)+BigInt(j.delta!));","title":"Historical recovery planner crashes with an unhandled TypeError on any database that has not applied migration 0014 (the current production schema)"},{"citation":"resolved","description":"World computes world_trial.active at its own clock inside /api/aimarket-auth/session; AIMarket then validates the same payload at its clock after network latency. _identity() treats active=True with endsMs <= _now_ms() as an invalid response and raises BridgeUnavailable. Neither session_eligible() nor complete_login() catches BridgeUnavailable (only BridgeDenied is handled), so during the last milliseconds of a campaign, or with any clock skew where AIMarket runs ahead of World, every portal read and login completion raises instead of evaluating the wallet on its EMBEO balance. A player who holds EMBEO and would be eligible regardless of the trial is refused with a 5xx-class error, and the error is not a denial that the caller can distinguish from World being down. Requirement 8 asks for consistent half-open UTC boundary handling on both sides; here the boundary is consistent only if the two clocks and the round trip are zero. Fix: in _identity treat an already-ended active trial as inactive (active=False) rather than invalid, or catch BridgeUnavailable from the trial field and continue to the balance path; keep the structural checks (types, version>=1) as hard failures.","line":250,"path":"aimarket/web/world_auth.py","reproduction":"Run python3 test/scratch/trial_boundary_repro.py. It patches _now_ms to NOW, inserts a 'world' profile session, and patches _world_post to return a well-formed identity with world_trial={active:true,endsMs:NOW,version:1} and _balance to return True (the wallet holds EMBEO). Expected: session_eligible('S'*43,require_profile='world') == (ADDRESS, True) via the balance path, and complete_login succeeds. Actual: both raise BridgeUnavailable('invalid_world_trial'). Negative control: with endsMs=NOW+1000 both succeed; with active=False both return eligibility from the balance.","severity":"low","snippet":"        raise BridgeUnavailable(\"invalid_world_trial\")","title":"AIMarket consumer turns a trial end-boundary race into a BridgeUnavailable exception, denying EMBEO holders instead of falling back to the balance check"},{"citation":"resolved","description":"Holds are scoped to (chain_id, contract, chain_instance). The local EDR chain instance id (mint.runId) is regenerated on every process start, and the mint.orders map that release() needs is memory-only, so after a restart or a SQLite restore every 'held' row of the previous instance is permanently unreachable: settleMintGold rejects both 'consumed' and 'released' with MINT_SCOPE_MISMATCH, no admin command (repair-topup, refund-service, control, product, campaign) can touch world_gold_holds, and worldGoldSnapshot keeps subtracting the amount from available forever. SCOPE.md states that a restart must not by itself authorize release, which is correct, but the packet provides no bounded, evidenced path at all (for example an admin 'release-expired-hold' that requires the on-chain proof that order.deadline has passed and paidOrderTokenId is zero, recorded with before/after state). In production the Worker contains no Mint settlement component (worker/app.ts never calls reserveMintGold/settleMintGold), so the same orphaned-hold state would arise whenever the external Mint service that owns the chain instance is redeployed with a new instance id. This is a liveness/operability gap, not a theft path: available never goes negative and no GOLD is created.","line":103,"path":"world-source/server/world-gold.ts","reproduction":"Run test/scratch/gold-repro.test.mjs, case REPRO-2. Setup: wallet W tops up 10 GOLD; reserveMintGold with chainInstance 'edr-run-1', gold 8, deadline NOW+300s -> snapshot book/reserved/available = 10/8/2. Simulate restart: call settleMintGold with chainInstance 'edr-run-2' at NOW+1e9 (deadline long passed) for state 'released' and 'consumed'. Expected (by requirement 2): some evidenced path exists to release a provably unexecutable authorization. Actual: both calls fail MINT_SCOPE_MISMATCH, admin refund-service/repair-topup on the mint id are rejected, and the snapshot at NOW+1e9 still shows 10/8/2.","severity":"low","snippet":" if(!row||row.chain_id!==o.chainId||row.contract!==economyAddress(o.contract)||row.chain_instance!==o.chainInstance)fail('MINT_SCOPE_MISMATCH');","title":"A Mint hold whose chain instance is gone can never be consumed or released, locking the player's GOLD with no evidenced operator remedy"},{"citation":"resolved","description":"The CAS-in-SQL pattern records every accepted predicate as a row keyed by a fresh UUID. Only admin actions need their before_json row kept (the audit insert joins it); quote and reserve checks are never read again, yet nothing deletes them and the scheduled housekeeping in worker/app.ts does not touch the table. On D1 this is unbounded row growth driven by any authenticated player (one row per /api/gold/quote, bounded only by the per-IP ECONOMY_LIMITER), and the recovery planner snapshots the whole table into every plan (TABLES includes WORLD_GOLD_TABLES), so growth here also pushes recovery toward RECOVERY_REQUIRES_LARGER_REVIEWED_PLAN. Fix: use a temporary/rollback-only predicate (e.g. SELECT RAISE via a trigger on a scratch table that is deleted in the same batch), or prune non-admin rows (before_json IS NULL) in the cron, and exclude them from the recovery snapshot.","line":8,"path":"world-source/server/world-gold.ts","reproduction":"Run test/scratch/gold-repro.test.mjs, case REPRO-3: after setup the table has 3 rows (admin product/before rows); 20 quotes by one wallet add 40 rows (one auth check + one product/control check each), none removed afterwards. Expected: bounded storage, or rows scoped to the audit trail only. Actual: 43 rows and growing linearly with player quotes.","severity":"info","snippet":"const check=(db:D1Database,predicate:string,...args:unknown[])=>st(db,`INSERT INTO world_gold_write_checks(id,accepted) SELECT ?,CASE WHEN ${predicate} THEN 1 ELSE 0 END`,crypto.randomUUID(),...args);","title":"world_gold_write_checks accumulates one permanent row per quote, reservation and admin command with no pruning"},{"citation":"resolved","description":"The minute cron only calls reconcileWorldGold, which turns paid GOLD service orders into world_gold_jobs rows in state 'queued'. claimGoldJob/finishGoldJob are invoked only by the local fixture runtime (scripts/world-gold-local-runtime.mjs). Because a refund requires state 'failed', which only finishGoldJob can set, a player whose GOLD was debited for research/fortune in production has no delivery, no refund, and no admin remedy (refund-service checks state IN ('failed','refunded')). This is consistent with SCOPE.md's statement that real providers are a release gate and with the Worker config returning null, so it is a documented deployment gap rather than an implementation vulnerability; it is recorded here so the release checklist includes: (a) a durable worker that claims jobs with the orderId as provider idempotency key, (b) an explicit classification of provider outcomes where only proven terminal nondelivery maps to {failure}, and (c) a test that the 'service' product cannot be enabled while no consumer is bound.","line":155,"path":"world-source/worker/app.ts","reproduction":"State: migration 0014 applied, an admin product of kind 'service' enabled, Worker running with a non-null economy config. A player quotes the service, signs the GOLD payment (economy_gold_journal debit, economy_orders.status='paid'), cron runs reconcileWorldGold -> world_gold_jobs(order_id,state='queued'). Expected: delivery or a terminal failure that refunds once. Actual in the packet as shipped: no code path in worker/app.ts ever calls claimGoldJob or finishGoldJob; the row stays 'queued' indefinitely and refundGold/refund-service reject with REFUND_NOT_ENTITLED (requires state='failed').","severity":"info","snippet":"    await reconcileWorldGold(env.DB,at);","title":"Deployed Worker debits GOLD for service orders and creates jobs, but contains no job claim/finish consumer, so paid service orders would stay 'queued' with no delivery and no lawful refund path"}],"hash":"ec63b9cd82bd0d600f602e9a7915e4675048d4343dceefee277bb21f9732afe9","nodeId":"5caf3f9a-927c-4dda-b56a-205217a2366d","outcome":"completed","summary":"Negative controls confirmed and the tree is clean. The findings file is complete and validated, so I'll close with the report.\n\n## Audit report: Ember World shared GOLD candidate (commit d0a6f42)\n\nThe findings file `.imd-findings.json` holds five substantiated findings. I found no demonstrated path that creates GOLD, spends held GOLD, double-credits a topup, double-refunds a service, or lets an ordinary wallet act as owner. The defects found are a crash in the staking-correction planner, a boundary inconsistency in the Python consumer, and three operability gaps.\n\n**Findings, most severe first**\n\n- **Medium, `world-source/server/economy-recovery.ts:204`.** The recovery planner reads the `world_gold_entries` snapshot unconditionally, but the snapshot step skips that table on any database without migration 0014. On the current production schema, both `prepareEconomyRecovery` and the read-only `preflightEconomyRecovery` throw a raw TypeError instead of a coded refusal or plan. The historical staking correction cannot run on the schema it has to run against. Reproduced in `test/scratch/recovery-pre0014.test.mjs`.\n- **Low, `aimarket/web/world_auth.py:250`.** World evaluates trial activity on its clock, AIMarket re-validates on its own clock after the round trip. An active trial whose end time has passed by the time AIMarket checks raises `BridgeUnavailable`, which neither `session_eligible` nor `complete_login` handles. An EMBEO holder is refused with an error rather than evaluated on balance. Reproduced in `test/scratch/trial_boundary_repro.py`; negative controls with a later end or an inactive trial return the balance result.\n- **Low, `world-source/server/world-gold.ts:103`.** A hold scoped to a chain instance that no longer exists can never be consumed or released, and no admin command can touch holds. The GOLD stays reserved forever. SCOPE.md documents that a restart must not authorize release, but no evidenced remedy exists either. Reproduced in `test/scratch/gold-repro.test.mjs` case REPRO-2.\n- **Info, `world-source/server/world-gold.ts:8`.** Every quote and reservation writes a permanent `world_gold_write_checks` row that is never pruned and is snapshotted into every recovery plan. REPRO-3 shows linear growth per player quote.\n- **Info, `world-source/worker/app.ts:155`.** The deployed Worker creates queued service jobs but never claims or finishes them, so a paid service order in production would have no delivery and no refund path. This is a documented release gate, recorded so it stays on the checklist.\n\n**What I verified and found sound**\n\n- Topup credit, admin repair and automatic reconciliation share one reference key and the entry-authority trigger bounds the cumulative credit to the locked order amount. Concurrent credit, partial legacy credit and repricing after quote behave as required.\n- Available equals book minus held across the legacy journal, Mint holds, service debits and consumption. The balance guard trigger fires on the legacy debit path. Concurrent reservations cannot share the same available GOLD.\n- Service jobs use attempt tokens and lease fencing. Late completion after refund is rejected, refund requires a terminal failure state, and reopening a delivered result costs nothing.\n- Write routes require a live EOA session, same-origin POST, matching expected address, and re-check the session inside the SQL batch. Session addresses are stored lowercase, so the in-SQL authorization predicate matches.\n- The three admin wallets are the only ones accepted for admin commands. There is no balance setter. Repair is bounded by the paid order. Before and after state is captured atomically.\n- The retained Solidity is unchanged and small. The exact-transfer supply check is intentional for the fixed-supply EMBEO token. The Mint contract shares one 2222 supply, has no free reservation pool, and requires both fees for paid mint.\n\n**Trust assumptions, not findings**\n\nAdmins can create a topup product at any price, includin","treeHash":null,"usage":{"cachedInputTokens":3776665,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":53849,"runtime":"claude","turns":55,"wallClockMs":893056}}],"verification":[]}