# Audit report

> Review Submission32/ only (read Submission32/AUDIT_REQUEST.md first). World source 0c15374 and Celestao source 382a266, both ALREADY LIVE at the owner's choice: the first real paid Celestao order was stuck for two hours because both World delivery adapters called fetch with redirect:'error', which Cloudflare Workers reject before sending. The fix is redirect:'manual' (both callers already refuse any non-200). The Celestao change only adds "about 1-2 minutes" wait text before payment and while a paid report is being prepared. No locked wallet file changed; the wallet-lock test passes.
>
> 1. Any way redirect:'manual' lets a redirect or any non-200 response count as delivered, or as a confirmed nondelivery that triggers a refund.
> 2. Any other fetch option or API in the Worker-side payment and delivery code that Workers reject or handle differently from Node.
> 3. The wait text: any run state in which it tells a buyer something false about payment, or invites a second payment.
> 4. Anything else in patches/cumulative_69ad4b5..0c15374.diff and patches/celestao_dd679fe..382a266.patch.
>
> Known items are in Submission32/AUDIT_REQUEST.md section 4. Report severity, reproduction and fix. Not in scope: chart engines, report wording, the 鼠道修仙 game, 3D/rendering, media, Genesis.

| | |
|---|---|
| Repository | https://github.com/tungweb3/imd-ember-world-review.git |
| Commit | `265b553401c91a178471250f197465e6b5d37eb5` |
| Job | `88a680a7-7435-41f4-96a6-8f848cb5ff0f` |
| Judged | 2026-11-08 17:34 UTC |
| Findings | 3 low · 3 info |

Four agents audited the code as it is at `265b553`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Low: Celestao "working" text promises the page updates by itself, but one failed status poll stops polling and removes every control

`Submission32/source/integrations/celestao/web/world-wallet.js:70`

```
  }catch(e){p.textContent=errorText(e);}
```

Celestao 382a266 (deployed copy Submission32/celestao/world_wallet_web/world-wallet.js, identical modulo CRLF) adds the run-state text "Paid. Your report is being prepared, usually in about 1-2 minutes. This page updates by itself; do not create another order." in showRun(). The 5-second re-poll (line 69) is scheduled only inside the try block after a successful GET /api/world/runs/{id}. If one poll throws, control goes to the catch on line 70, which overwrites the status paragraph with errorText(e) and does nothing else: no timer is set, and the "Check original World order" link and the "Check again" button are appended only in the else branch (line 68), so they are not present after an error. Every poll goes through identity(), which calls world.introspect() per request (app.py line 113); one BridgeUnavailable is answered 503 WORLD_AUTH_UNAVAILABLE, so a single transient World outage is enough. The buyer who was just told the page updates by itself is left with "Temporarily unavailable. Try again shortly." and no control; polling resumes only after a tab switch/refocus (recheck()) or a reload. The previous text made no self-updating claim, so the gap is new to this diff. Not a payment-safety issue: nothing false is said about payment and no second payment is invited; the false statement is about the page's behaviour during exactly the window (a paid run in working) the change was made for. Merged from audit_flow. Fix: in the catch, for errors other than AUTH_REQUIRED/CONTEXT_CHANGED, append the World order link and the "Check again" button and reschedule showRun with the same 5 s timer (optionally with a bounded back-off); or move the timer scheduling into a finally that runs whenever the run state is unknown or in ['prepared','working'].

**Reproduction**

Mechanical reproduction (run against the file as shipped): extract showRun from world-wallet.js, stub document/node/button/say/errorText/setTimeout and an api() that returns {state:'working'} once and throws once. Observed: successful poll -> status text "Paid. Your report is being prepared, usu...", 2 controls appended, setTimeout(5000) scheduled; one failed poll -> status text "Temporarily unavailable. Try again shortly.", 0 controls, no timer scheduled. Live reproduction: open https://celestao.com/?world_run=<id>#/saved while the run is in state working, and in the console stub window.fetch to reject once for a URL containing /api/world/runs/ (or let GET /api/world/runs/<id> answer 503 once). Expected: the page keeps saying the report is being prepared and keeps polling, or at least keeps the World order link and the Check again button. Actual: the paragraph reads "Temporarily unavailable. Try again shortly." with no link or button and never changes again while the tab stays visible and focused, even after the report is finished seconds later.

### 2. Low: Workers fetch-option guard scans only top-level .ts files of server/ and worker/ and only the one option that already failed

`Submission32/source/tests/worker-fetch-redirect.test.mjs:10`

```
 for(const dir of ['server','worker'])for(const name of readdirSync(dir))if(name.endsWith('.ts')){
```

tests/worker-fetch-redirect.test.mjs (added in f33468a) is the only regression guard against a repeat of the incident class "a RequestInit value Node accepts and workerd throws a TypeError on before sending", which produced the silent two-hour stall (runGoldServiceJobs catches the throw without logging and retries each minute; no refund is possible). The test uses readdirSync(dir) without recursion, keeps only names ending in .ts, scans only server/ and worker/, and matches only /redirect\s*:\s*['"]error['"]/. Gaps, each confirmed: (1) a file in any subdirectory of server/ or worker/ is never read; (2) src/shared/ and the src/world files worker/app.ts bundles are never scanned; (3) .mjs/.js helpers are skipped; (4) the cache option is unchecked, although Cloudflare's Request documentation states only cache:'no-store' and 'no-cache' are supported and any other value throws TypeError "Unsupported cache mode: <mode>" (compatibility_date 2026-05-15 enables cache_option_enabled), and Node accepts all cache modes. AUDIT_REQUEST section 4 discloses that the search for other options is not a test; this finding adds that the test does not cover the whole Worker bundle even for the one option it checks. Currently no Worker-side file in the bundle uses any of these options (the only cache:/credentials:/redirect:'error' uses are browser code in src/world/worldGoldClient.ts:23 and src/world/squeekCheckoutClient.ts:32), so this is a coverage defect, not a live one. Merged from audit_math, audit_economics, audit_flow and audit_permissions. Fix: walk server/, worker/, src/shared/ (and the src/world modules worker/app.ts imports) recursively over .ts/.tsx/.mjs/.js, reject redirect:'error' and cache values other than 'no-store'/'no-cache' (and mode/credentials/referrer/integrity/keepalive/priority), or better route all Worker-side outbound calls through one helper and unit-test its init object; and add one miniflare/workerd test (@cloudflare/vitest-pool-workers or wrangler unstable_dev) that calls createCelestaoDelivery with the real fetch against a local stub and asserts the request is sent.

**Reproduction**

Verified on Node 24.21.0. Create a tree with worker/app.ts (any content), server/sub/inner.ts containing fetch(u,{method:'POST',redirect:'error'}), src/shared/relay.ts containing fetch(u,{redirect:'error'}), server/helper.mjs containing fetch(u,{redirect:'error'}), and server/top.ts containing fetch(u,{method:'POST',cache:'reload',redirect:'manual'}); copy tests/worker-fetch-redirect.test.mjs beside them and run node --test tests/worker-fetch-redirect.test.mjs. Expected: the test fails and names server/sub/inner.ts, src/shared/relay.ts, server/helper.mjs and server/top.ts. Actual: "pass 1, fail 0". Control: adding server/bad.ts with redirect:'error' at the top level makes the test fail with actual: ['server/bad.ts'], so the regex itself works and only the walk and the option set are too narrow. In production workerd each of those four files would throw a TypeError at the fetch call before anything is sent, which runGoldServiceJobs swallows as status 'retry' exactly as on 2026-11-08.

### 3. Low: A paid order Celestao refuses with 409 is retried every minute for ever, with no refund path, no operator surface on either side, and only a 20%-sampled log line

`Submission32/source/server/celestao-delivery.ts:20`

```
  if(response.status===409)console.error('celestao_delivery_refused',order.orderId);
```

Celestao's deliver endpoint (integrations/celestao/celestao_world/paid.py lines 127-139) answers 409 ORDER_BINDING_REQUIRED when vault.run raises Missing and the run is not in pruned_runs (vault.py 16-17: Missing is a ValueError subclass, so the re-raise at line 134 is caught by the except on line 139), or when product/inputHash do not match the row (line 137). All these paths run before vault.bind_order, so the run keeps order_id NULL. Consequences: (1) World: the adapter logs console.error('celestao_delivery_refused') and throws DELIVERY_UNAVAILABLE; runGoldServiceJobs records 'retry' and the job stays queued/working and is re-claimed every cron minute (world-gold-delivery.ts 27-37, world-gold.ts 139) with no bound; it never reaches 'failed', so neither reconcile's refundGold nor the operator command refund-service can run (world-gold.ts 264: guard REFUND_NOT_ENTITLED requires state IN ('failed','refunded')). (2) Celestao: vault.stuck() (vault.py 240) lists only rows WHERE order_id IS NOT NULL and vault.abandon(order_id) (vault.py 245) looks up by order_id, so the unbound run is invisible to the Celestao operator too and cannot be ended. (3) The only signal is this console.error line, emitted inside the scheduled handler, and wrangler.jsonc line 40 sets observability head_sampling_rate 0.2, so each minute's line is kept with probability 0.2 and a one-off refusal usually leaves nothing (this is why the 2026-11-08 stall needed a separate probe Worker). (4) The buyer's Celestao page, if the run row exists but mismatches, stays in 'prepared' and after 382a266 reads "This reading has not started yet. If you paid in World, it usually starts within 1-2 minutes" on every 5 s poll, indefinitely. The refusal-not-refund rule is the owner's stated design for a wrong or restored database and is correct; the defect is that the stated invariant (vault.py line 25: a buyer who paid gets the report or the money back) has no path to be restored by anyone, while the Worker's own comment says retrying will not help. Preconditions are operator-side (restore from a backup taken before the run was admitted, or an inconsistent row), not attacker-reachable, hence low. Merged from audit_economics (409 dead end) and audit_economics (log sampling). Fix: in createCelestaoDelivery return a distinct outcome for 409 (or persist a 'refused' marker and counter in world_gold_jobs.failure) and stop re-claiming after a bounded number of consecutive refusals, listing the job for operator review; add an admin action that can move such a job to 'failed' with evidence {kind:'operator-confirmed-nondelivery'} so refundGold can run; on the Celestao side make vault.stuck() or the deliver log include refused orders (orderId is known at the 409). Set head_sampling_rate to 1 for scheduled invocations or write the last delivery error into world_gold_jobs so it is queryable regardless of log sampling.

**Reproduction**

State: wallet W pays order O for run R (economy_orders.status='paid', world_gold_jobs row queued). Before the first delivery call, Celestao's vault loses row R without a pruned_runs tombstone (restore from a backup taken before R was admitted), or R's input_hash differs from celestao_checkouts.input_hash. Cron minute 1: deliver -> POST /api/internal/world-gold/deliver -> vault.run raises Missing, was_pruned False -> HTTPException(409). Confirmed with a stub transport answering 409 {"detail":"ORDER_BINDING_REQUIRED"}: the adapter prints celestao_delivery_refused <orderId> and throws DELIVERY_UNAVAILABLE (no 'delivered', no 'terminal-failure'), so runGoldServiceJobs records status 'retry'. Minutes 2..N: identical, for ever. Expected: within the 24 h bound the order is delivered or refunded, or at least surfaced to an operator who can refund. Actual: the job never leaves queued/working; goldAdminCommand refund-service for O fails REFUND_NOT_ENTITLED (guard at world-gold.ts 264); vault.stuck() returns nothing for O (order_id NULL) and vault.abandon(O) raises Missing; the console.error line is kept for about one invocation in five.

### 4. Info: No test feeds a 3xx or any non-200 response to the Celestao delivery adapter, although the fix's safety argument rests on the non-200 refusal

`Submission32/source/tests/celestao-checkout.test.mjs:72`

```
  return new Response(raw,{headers:{'content-type':'application/json','x-celestao-signature':mode==='unsigned'?'invalid':await celestaoSignature(SECRET,'response',DELIVERY,String(NOW/1000),raw)}});
```

f33468a changes redirect:'error' to redirect:'manual' and argues it is safe because both callers refuse any non-200. Under 'manual', Workers and Node hand the 3xx response itself to the caller, so the status check (celestao-delivery.ts 21, aimarket-delivery.ts 29) is now the only thing between a redirect and the signature/JSON path. The claim is correct today: I ran createCelestaoDelivery and createAIMarketDelivery against stub transports answering 301, 302 (with a JSON body claiming kind:'delivered'), 307, 308, 409, 500, 204 and a 200 text/html body, and every case throws (DELIVERY_UNAVAILABLE, or BAD_REQUEST from celestaoBody for the HTML case), which runGoldServiceJobs turns into 'retry'; none returns kind:'delivered' or kind:'terminal-failure'. I also confirmed with a local HTTP server that Node's fetch with redirect:'manual' returns the 3xx with its real status, as Workers does. But nothing in the suite checks this: tests/celestao-checkout.test.mjs lines 68-77 return only status-200 responses (modes ok, wrong-wallet, unsigned) and assert init.redirect==='manual' as a string; tests/aimarket-delivery.test.mjs line 17 has one 500 case and no 3xx. A later edit that relaxes response.status!==200 (to !response.ok plus following the Location header, or to any 2xx) would pass the suite. Merged from audit_flow and audit_permissions. Fix: add a 302-with-Location case and a 307 case to both test files next to the existing 'unsigned' and 500 cases, asserting assert.rejects(provider.deliver(order),/DELIVERY_UNAVAILABLE/) and that calls increased by one; and in the celestao test assert the World job is still working/queued afterwards.

**Reproduction**

grep -n "status:3\|status: 3\|30[1278]" tests/celestao-checkout.test.mjs tests/aimarket-delivery.test.mjs returns nothing (the only non-200 anywhere is the 500 at aimarket-delivery.test.mjs:17). Expected: at least one transport stub returning new Response(null,{status:302,headers:{location:'https://example.invalid/'}}) for each adapter. Actual: none. Executable check of the current code (passes, so this is a coverage gap not a defect): for each of status 301/302/307/308/409/500/204 and 200 text/html, both adapters throw and never return delivered or terminal-failure.

### 5. Info: World job lease equals the cron period, so a "pending" delivery is often retried only every other minute; the new 1-2 minute text assumes a one-minute retry

`Submission32/source/server/world-gold.ts:139`

```
 const result=await st(db,`UPDATE world_gold_jobs SET state='working',attempt=?,lease_ms=?,updated_ms=? WHERE order_id=? AND (state='queued' OR (state='working' AND lease_ms<=?)) RETURNING wallet`,token,nowMs+60000,nowMs,orderId,nowMs).run();
```

Both new texts (the confirm step and the working state in world-wallet.js line 39 and 68) say the report is usually ready about 1-2 minutes after payment, and AUDIT_REQUEST section 4 derives that from "the cron runs every minute and Celestao waits up to 8 seconds". That holds only when the first delivery call finishes the chart inside DELIVERY_WAIT_SECONDS=8 (paid.py 13). When Celestao answers kind:'pending' (a chart that needs more than 8 s, the four-system profile product, an engine busy or briefly away), runGoldServiceJobs leaves the World job in state 'working' with lease_ms = claim time + 60000 (this line), and the SELECT in world-gold-delivery.ts line 27 picks it up again only when lease_ms <= now, where now is read at line 22 at the start of the pass. The claim in tick N happens after sharedGoldSchemaPresent, worldGoldSchemaReady, reconcileWorldGold and the job SELECT (several D1 round trips) and claimGoldJob reads clock() again (line 138), so lease_ms is stamped later than tick N's own now by the prelude plus the SELECT. Tick N+1's now is read after the same prelude, so it is earlier than lease_ms unless cron jitter exceeds the SELECT round trip; the job is then skipped until tick N+2. A report not ready in 8 s is therefore typically delivered 2-3 minutes after payment, not 1-2. The text hedges with "usually", and the single measurement behind it (evidence/production_timeline.md) was a chart that finished inside the first 8-second call, so this is informational. It also matters because a shorter retry interval shortens the time an operator needs to notice a stuck order. The bundle lacks tests/d1-sqlite.mjs, so runGoldServiceJobs could not be executed here; the reproduction is arithmetic from the quoted lines. Merged from audit_math and audit_flow. Fix (any one): claim with the same now the SELECT used (pass now instead of clock() at world-gold-delivery.ts 30 and drop the fresh read), make the lease shorter than the cron period (e.g. nowMs+45000; finishGoldJob still requires lease_ms>finishedAt and the delivery call is bounded at 10 s), select with lease_ms<=now+grace of a few seconds, or soften the text to "a few minutes".

**Reproduction**

State: a paid fortune order whose Celestao computation takes 20 s. Tick N: cron fires 12:00:00.300; runGoldServiceJobs reads now=12:00:00.500 after the prelude; the SELECT returns the job; claimGoldJob reads fresh=12:00:00.650 and sets lease_ms=12:01:00.650; Celestao answers pending after 8 s; the job stays working. Tick N+1: cron fires 12:01:00.250; now=12:01:00.450 < lease_ms 12:01:00.650, the SELECT (lease_ms<=now) returns no row, nothing is retried. Tick N+2 at 12:02:00.400 claims and gets delivered. Delivery after payment at 11:59:30: about 2 min 40 s. Expected per text: about 1-2 minutes. Deterministic form: call runGoldServiceJobs(db,pendingProvider,clock) where clock returns T on the first read and T+150 on the claim read, then call it again with clock()=>T+60000: the second call returns [] because lease_ms=T+60150 > T+60000.

### 6. Info: A run whose quote lapsed (retired) still reads state "prepared": the page polls it every 5 s for ever and says "do not create another" while World says start again

`Submission32/source/integrations/celestao/celestao_world/paid.py:81`

```
        return {'wallet':ident.wallet,'context':ident.context,'runId':run_id,'state':run['state'],'recordId':run['result_id'],'failure':run['failure']}
```

GET /api/world/runs/{id} returns run['state'] from vault._run_view (vault.py 130-131), which does not expose the retired column. vault.retire_lapsed (vault.py 166-174) marks a run retired after World reports lapsed (the quote expired unpaid and 5 minutes passed, celestao-checkout.ts 11 and 51) but leaves state='prepared'. The World checkout page for that run shows ORDER_EXPIRED: "This unpaid quote expired. Wait 5 minutes, then return to Celestial Atlas to start again." (CelestaoCheckout.tsx 9) and its back link is always https://celestao.com/?world_run=<run>#/saved (line 31). On that page showRun() renders the 382a266 text for anything not complete/cleared/failed/working: "This reading has not started yet. If you paid in World, it usually starts within 1-2 minutes. Check its original order; do not create another." and, because the state is in ['prepared','working'], re-polls every 5 s indefinitely (world-wallet.js 68-69). No payment is possible on a retired run, and the only way forward is to run compute again, which vault.prepare does by skipping retired rows and opening a new run (vault.py 118). The payment clause is conditional, so nothing false is said about payment and no second payment is invited; the "do not create another" wording pre-dates this diff. The new sentence adds a suggestion that waiting may help where it cannot. Informational. Merged from audit_math. Fix: expose retired (or a state 'lapsed') from GET /api/world/runs/{id}, render a lapsed text that points to starting the reading again and states no payment was taken, and exclude it from the ['prepared','working'] polling list.

**Reproduction**

State: wallet W prepares run R; World quotes order O; O is never paid and expires_ms+300000 passes. W presses compute again on the same input: World /prepare answers lapsed:true, Celestao retire_lapsed(R) sets retired=1 (state stays prepared) and a new run R2 is created. Now open https://celestao.com/?world_run=R#/saved (the back link from the World page that shows ORDER_EXPIRED). Expected: a lapsed text such as "This quote lapsed; start the reading again (no payment was taken)" and no further polling. Actual: GET /api/world/runs/R returns state 'prepared' (vault._run_view has no retired field, and the retire UPDATE at vault.py 174 does not change state), the page shows "This reading has not started yet. If you paid in World, it usually starts within 1-2 minutes. Check its original order; do not create another." and schedules a new poll every 5 s while the tab is visible.

---

Judge's submission `aef6fd0f8727b5bb2ea32d104ecd7144c4b0a242b74e550a7647cc40f96a3af2`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
