# Audit report

> Review Submission33/ only (read Submission33/AUDIT_REQUEST.md first). It answers audit 88a680a7 of Submission32: findings 1, 2, 4 and 5 are fixed, 3 and 6 are disclosed and not changed. World source 7672dc5 changes only tests and the Celestao mirror (no Worker code, no locked wallet file; the live Worker is unchanged). Celestao source 2b5f1c2 is ALREADY LIVE at the owner's choice: after a failed status check the paid-run page now keeps the World order link, the Check again button and the 5-second poll (not after a lost wallet session; NOT_FOUND keeps the link but stops polling), and the wait text says about 1-3 minutes.
>
> 1. Finding 1: any error or state in which the page now polls when it should not (a lost or switched wallet, another wallet's run), stops when it should not, shows a link for the wrong run, or invites a second payment.
> 2. Finding 2: any Worker-side fetch option that Workers reject or treat differently from Node and tests/worker-fetch-redirect.test.mjs still misses.
> 3. Finding 4: any 3xx or other non-200 answer that the delivery adapters or the job runner could still count as delivered or as a confirmed nondelivery that refunds.
> 4. Anything else in patches/cumulative_0c15374..7672dc5.diff and patches/celestao_382a266..2b5f1c2.patch.
>
> Known items are in Submission33/AUDIT_REQUEST.md section 2. 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 | `01db27b8cbafa6128825891d038da4d88303e1fc` |
| Job | `dc1879f1-c752-42aa-93b4-c9b6b8daef31` |
| Judged | 2026-11-10 16:09 UTC |
| Findings | 3 low · 4 info |

Four agents audited the code as it is at `01db27b`, 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: Finding 1 fix is undone by the session re-check: during a World outage the paid-run view is never built on landing, and one focus/visibilitychange (or one transient session error) removes the view, th

`Submission33/source/integrations/celestao/web/world-wallet.js:90`

```
 async function recheck(){if(!catalog?.walletEnabled||rechecking)return;rechecking=true;const previous=identity,record=currentRecord,result=currentResult;invalidate();try{await session();if(identity&&identity.context===previous?.context&&record&&result&&reader?.(result)){currentRecord=record;currentResult=result;}refreshView();}finally{rechecking=false;}}
```

The 88a680a7 #1 change keeps the link, the Check again button and the 5 s poll inside showRun() after a failed GET /api/world/runs/<id>. But the run view exists only while identity is set: mount() builds the run host only when identity&&opaque(run) (line 81), and identity comes from GET /api/world/session. On Celestao that route goes through the same identity() dependency as the status check (celestao_world/app.py lines 108-118: sessions.read then world.introspect over the bridge), so in the outage the fix was written for ("World stays unreachable") the session check answers 503 WORLD_AUTH_UNAVAILABLE at the same time as the status check. session() (line 14/16) then throws AUTH_UNAVAILABLE, leaves identity null and sets authError. Two consequences. (a) A buyer who returns from World to /?world_run=<id>#/saved during the outage gets no run view at all: no "paid, do not create another order" text, no World order link, no Check again, no poll, only "Temporarily unavailable" and a Connect World wallet button; nothing retries until a focus/visibilitychange. (b) A page that is already polling through the outage loses the whole view the first time the window gets focus or becomes visible: recheck() (line 90) calls invalidate() first (epoch++, identity=null, #world-wallet-library removed), then session() fails, then refreshView() rebuilds the library without the run host; the pending 5 s timer fires with live()===false and does nothing. A single transient failure of the session request (network error, a non-JSON 502 page) has the same effect, and switching to the World app to look at the order and back is exactly the move a waiting buyer makes. The World order link does not need a Celestao session, so the one control that still works is the one removed. The submission's tests (tests/celestao-home-prices.test.mjs runPage) always answer /api/world/session with 200 and never fire the focus listener, so neither case is exercised. No money is at risk: World still refuses a second payment for the same run, but the page now offers a sign-in instead of the order status, which is the symptom #1 was meant to remove. Fix: in recheck(), do not invalidate before the session answer arrives; only invalidate and drop the view on a definite 401 or a changed context, and on AUTH_UNAVAILABLE/network failure keep the previous identity and view and retry session() on the same 5 s cadence while a world_run view is open. In mount(), when world_run is opaque and the session failure is not a 401, still build the run host with the "if you paid, do not pay again" text, the World order link and Check again, and poll session() then showRun(). Add tests whose session answer is 503 and that fire the focus listener. Reported by the permissions, math and flow specialists; merged here.

**Reproduction**

Copy tests/celestao-home-prices.test.mjs (its real-world-wallet.js fake page) and make load() also return windowListeners. Case A (landing during outage): search '?world_run='+RID, route('/saved'), both /api/world/session and /api/world/runs/RID answer {ok:false,status:503,json:{detail:'WORLD_AUTH_UNAVAILABLE'}}. Expected: a run host with the World order link https://imdember.com/fortune-checkout/?run=RID&lang=zh, Check again and one 5 s timer. Actual (node 22): only '/api/world/session' is requested, no run host (present:false, link null), 0 timers, library text '我的錢包收藏World 錢包收藏暫時無法讀取，請稍後再試。連接 World 錢包重新連接／切換錢包查看垃圾桶'. Case B (focus during outage): session 200 then 503; runs answers {state:'working'} then 503. After the failed poll the link and one live timer remain (the fix works). Then call every window 'focus' listener once. Expected: the run view, link, button and poll stay, or a session retry is scheduled. Actual: run host gone (present:false, link null, again:false), library shows the same 'temporarily unavailable' + Connect button text, the one pending timer belongs to the old epoch and firing it issues no request (new calls 0). Case C: session rejects once with TypeError('Failed to fetch') on the focus re-check while runs keep answering {state:'working'}: same result, run view gone.

### 2. Low: Finding 2 guard misses quoted keys, property assignments, identifier and shorthand values, and does not scan the src/world modules the Worker bundle imports

`Submission33/source/tests/worker-fetch-redirect.test.mjs:14`

```
const options=(text,key)=>[...text.matchAll(new RegExp(String.raw`\b${key}\s*:\s*(['"\`])([^'"\`]*)\1`,'g'))].map(m=>m[2]);
```

tests/worker-fetch-redirect.test.mjs is the only check that Worker-side code never passes a fetch option Workers reject (redirect:'error' throws a TypeError in workerd before anything is sent; cache values other than no-store/no-cache throw "Unsupported cache mode"; integrity throws). Two gaps. (1) options() matches only an unquoted key followed by a string literal (\bredirect\s*:\s*quote...), and the credentials/referrerPolicy/integrity check matches only \bkey\s*:. A quoted key puts the closing quote between the name and the colon, so {'redirect':'error'}, {"cache":"reload"}, {'integrity':'sha256-…'} pass; a property assignment init.redirect='error' / init.cache='force-cache' (no colon) passes; an identifier value {redirect:MODE} with const MODE='error', and a shorthand property {redirect} with const redirect='error', pass. All are static text an ordinary refactor produces (a JSON-style literal, an options object built step by step, a shared constant), all are valid TypeScript, and Node accepts all of them, so the unit suite stays green while every production delivery fails, which is exactly the failure 88a680a7 #2 was raised to prevent. The disclosure ("cannot see options built at run time") covers the constant case only partially and the quoted-key and assignment cases not at all. (2) ROOTS (line 11) are server/, worker/ and src/shared/, and the comment says src/world/ is browser code, but worker/app.ts imports ../src/world/cadence.ts (line 3) and ../src/world/collections.ts (line 29) (and server/ files import src/world/market.ts, model.ts, status.ts, siwe.ts per the import graph), so those modules ship in the Worker bundle and are never scanned. Side note: current workerd ignores credentials/referrerPolicy rather than rejecting them, so the test comment overstates that part; banning them is harmless. Fix: match keys with optional quotes and the assignment form, e.g. (?:^|[^\w$.])['"`]?${key}['"`]?\s*[:=]\s*(?:(['"`])([^'"`]*)\1|([A-Za-z_$][\w$]*)) and treat a non-literal value or a shorthand/computed key as an offender; build the scan set from the Worker's real import graph (follow relative imports from worker/index.ts, or read the esbuild metafile) and fail when a Worker-side import resolves outside the scanned set. Better still, route every Worker subrequest through one helper whose RequestInit type limits redirect to 'manual'|'follow' and cache to 'no-store'|'no-cache', and have the guard assert bare fetch( appears only in that helper. Reported by all four specialists; merged here.

**Reproduction**

Copy tests/worker-fetch-redirect.test.mjs into a scratch tree with stub server/{celestao-delivery,aimarket-delivery,aimarket-proxy}.ts each containing fetch(u,{method:'POST',redirect:'manual'}), worker/app.ts importing '../src/world/cadence.ts', and src/shared/s.ts. Baseline: node --test reports pass 3, fail 0. Add src/shared/deep.ts with: init.redirect='error';init.cache='force-cache';fetch(url,init); fetch(url,{"redirect":"error","cache":"reload"}); fetch(url,{'redirect':'error','credentials':'include','integrity':'sha256-abc'}); const MODE='error';fetch(url,{redirect:MODE}); const redirect='error';fetch(url,{redirect}); and src/world/cadence.ts with fetch(u,{redirect:'error',cache:'force-cache'}). Expected: the redirect and cache tests report these offenders. Actual (node 22.23): pass 3, fail 0. Control: src/shared/control.ts with fetch(u,{redirect:'error'}) is reported as 'src/shared/control.ts: redirect:error' and the suite fails 1, so the scan itself runs. Applying the test's own options() to the strings: fetch(u,{redirect:"error"}) -> ['error']; fetch(u,{"redirect":"error"}) -> []; fetch(u,{'cache':'force-cache'}) -> []; init.redirect="error" -> []; fetch(u,{redirect:MODE}) -> [].

### 3. Low: After a 429 RATE_LIMITED or a 503 WORLD_AUTH_UNAVAILABLE answer the page re-polls every 5 s with no back-off and ignores Retry-After, and each poll costs Celestao one World introspection

`Submission33/source/integrations/celestao/web/world-wallet.js:73`

```
  }catch(e){p.textContent=errorText(e);const code=e?.message;if(live()&&code!=='AUTH_REQUIRED'&&code!=='CONTEXT_CHANGED'){controls();if(code!=='NOT_FOUND')poll();}}
```

The new catch schedules poll() for every error code except AUTH_REQUIRED, CONTEXT_CHANGED and NOT_FOUND. Two answers in that set are explicit "stop asking" signals. Celestao's WalletLimits (celestao_world/limits.py line 21-29) answers 429 {"error":"RATE_LIMITED"} with Retry-After: 60 once a peer group has spent 180 wallet-API requests in a minute, and identity() (app.py line 117) answers 503 WORLD_AUTH_UNAVAILABLE whenever the per-request world.introspect call fails (bridge.py lines 35-45: any non-200 status, non-JSON body or httpx error). In both states the page keeps sending one GET /api/world/runs/<id> every 5 s for as long as the tab is visible (12 per minute per tab). Because GET /api/world/runs/<id> depends on checked -> identity, every poll is also one Celestao-to-World introspection, so a World outage or World-side rate limit is amplified rather than relieved, and other wallet actions in the same browser (open a saved report, the collection list, logout) share the same 180/minute bucket and get 429 too. api() discards the response status and headers, so the Retry-After value is not available to the page. The disclosure says "no back-off" and that each check is one same-origin GET to Celestao; it does not say that each check is also one World introspection, nor that Retry-After is ignored. Before this change a failed check stopped polling, so this is new behaviour of the live page. Fix: have api() attach status and Retry-After to the thrown Error; in the showRun catch skip poll() for 429 and for the 503 codes WORLD_AUTH_UNAVAILABLE/WALLET_NOT_CONFIGURED/STORAGE_UNAVAILABLE or delay them by max(Retry-After, backoff); keep a per-request attempt counter in the request object and double the delay on consecutive failures (5, 10, 20, 40 s, capped at 60 s); keep controls() so the buyer can still check by hand.

**Reproduction**

Fake page from tests/celestao-home-prices.test.mjs: search '?world_run='+RID, route('/saved'), session 200; /api/world/runs/RID answers fail(429,{error:'RATE_LIMITED'}), fail(429,{error:'RATE_LIMITED'}), fail(503,{detail:'WORLD_AUTH_UNAVAILABLE'}), then bound({state:'working'}). After each answer inspect the pending fake timers and fire them. Expected: after a 429 the next check waits at least 60 s (Retry-After) or polling stops with only the Check again button; after the auth-layer 503 the interval grows. Actual: the pending timer delays are [5000], [5000], [5000]; 4 status checks are sent in a row; the link and Check again are present throughout.

### 4. Info: When the cached session expiry passes between polls, api() throws AUTH_REQUIRED without invalidating: the run view shrinks to "Reconnect your World wallet" with no order link, while the identity box s

`Submission33/source/integrations/celestao/web/world-wallet.js:18`

```
 async function api(path,value){const context=identity,generation=epoch;if(!context||context.expiresAt<=Date.now())throw Error('AUTH_REQUIRED');
```

api() throws AUTH_REQUIRED before any request when context.expiresAt<=Date.now(), without calling invalidate(). The new catch at line 73 gives AUTH_REQUIRED neither controls() nor poll(), on the stated assumption that "the page asks the buyer to reconnect". On this path nothing asks: identity stays set, the identity box keeps the wallet address and the Disconnect button, the collection list stays, and the run view becomes the one line "請重新連接 World 錢包。" with no World order link, no Check again and no poll. The order link is a plain World URL and needs no Celestao session, so the one control that still works is the one removed. Only a focus or visibilitychange (recheck()) repairs it; a buyer who keeps the tab in front sees the contradiction for as long as they wait. The pre-flight expiry check is pre-existing; the new catch turns it into a dead end for the paid-run view. Fix: in api(), call invalidate() before throwing on local expiry so the identity box and the error agree and the next recheck rebuilds the view; and in the showRun catch call controls() for AUTH_REQUIRED too (only poll() needs a session).

**Reproduction**

Fake page from tests/celestao-home-prices.test.mjs: /api/world/session answers {...SESSION, expiresAt: Date.now()+40}; /api/world/runs/RID answers bound({state:'working'}). Load /saved?world_run=RID: the view shows the link and one 5 s timer. Wait 60 ms, fire the timer. Expected: either the identity box shows the Connect button, or the run view keeps the order link and Check again. Actual: run host text '請重新連接 World 錢包。', host has 1 child (link null, again false), 0 timers pending, '.world-wallet-address' still shows 0xaaaa…aaaa, 3 requests in total (no new status request was sent).

### 5. Info: A 401 or a 409 CONTEXT_CHANGED on the status check removes the whole wallet section and nothing remounts it: no reconnect prompt, no order link, until a focus/visibilitychange

`Submission33/source/integrations/celestao/web/world-wallet.js:21`

```
  if(response.status===401||response.status===409&&data.detail==='CONTEXT_CHANGED'){invalidate();throw Error('AUTH_REQUIRED');}
```

AUDIT_REQUEST section 1 #1 says that for AUTH_REQUIRED/CONTEXT_CHANGED "the page asks the buyer to reconnect". In the code a 401, or a 409 CONTEXT_CHANGED (another tab connected a different wallet), on GET /api/world/runs/<id> reaches line 21, which calls invalidate() (removes #world-wallet-library: the identity box with its Connect button, the run host, the order link and the collection list) and throws AUTH_REQUIRED. showRun's catch then finds live()===false and adds nothing, and nothing calls mount() again. The /saved page is left with no wallet section at all, no reconnect prompt and no word that the order is paid, until a window focus or visibilitychange runs recheck(); a buyer who stays on the page (common in an in-app webview) sees nothing. Polling stops, which is correct. The submission's test for this case ('a lost sign-in stops polling') only counts timers and calls. Fix: after invalidate() on the run page call refreshView()/mount() (or have the AUTH_REQUIRED branch of showRun remount), so the library is rebuilt with the identity box showing the reconnect text and the Connect button; keep the poll stopped and assert the prompt in the test.

**Reproduction**

Fake page from tests/celestao-home-prices.test.mjs: search '?world_run='+RID, route('/saved'), /api/world/session -> 200 SESSION, /api/world/runs/RID -> {ok:false,status:409,json:{detail:'CONTEXT_CHANGED'}} (and separately {ok:false,status:401,json:{detail:'AUTH_REQUIRED'}}). Expected: an identity box reading 'Reconnect your World wallet' with a Connect button, and the World order link. Actual for both: main.querySelector('#world-wallet-library') is null, no run host, no [data-world-identity] element on the page, 0 timers.

### 6. Info: Each failed poll replaces "Paid … do not create another order" with "Temporarily unavailable. Try again shortly."

`Submission33/source/integrations/celestao/web/world-wallet.js:73`

```
  }catch(e){p.textContent=errorText(e);const code=e?.message;if(live()&&code!=='AUTH_REQUIRED'&&code!=='CONTEXT_CHANGED'){controls();if(code!=='NOT_FOUND')poll();}}
```

The fix keeps the link, the Check again button and the poll after a failed check, but the catch still overwrites the status paragraph with errorText(e). For a 503, a network error or any unmapped code this is '暫時無法讀取，請稍後再試。' / 'Temporarily unavailable. Try again shortly.' A buyer whose run was showing '已付款，報告正在產生…不需另建新單' loses both the "paid" status and the "do not create another order" warning at the first failed poll, for as long as the outage lasts; "try again shortly" on a page reached right after paying reads close to "pay again", and a second compute creates a new run and a new 1,000-3,000 GOLD order rather than reusing this one. Fix: in the catch keep the last known run-state text (or a fixed "If you paid in World, your order is safe; do not create another order" line) and add the error as a separate secondary line.

**Reproduction**

Fake page from tests/celestao-home-prices.test.mjs: session 200, /api/world/runs/RID answers bound({state:'working'}) then {ok:false,status:503,json:{detail:'WORLD_AUTH_UNAVAILABLE'}}. After mount the status text is '已付款，報告正在產生，通常約 1–3 分鐘；此頁會自動更新，不需另建新單。'. Fire the 5 s timer. Expected: the paid/do-not-reorder wording stays with the error added. Actual: the status text is exactly '暫時無法讀取，請稍後再試。', with the order link, Check again and 1 timer pending.

### 7. Info: Finding 6 is wider than retired runs: any prepared run with no order whose checkout window has passed is reported as plain "prepared", so the page polls it every 5 s and says a payment may be in fligh

`Submission33/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 only state/recordId/failure. A run stays state='prepared' with order_id NULL after its expires_ms passes whenever the buyer never quoted (left the World checkout page) or let the quote lapse without re-submitting the same input on Celestao: retire_lapsed (vault.py line 166) runs only inside prepare() on a repeat request, and prune_unpaid (vault.py line 149-152) deletes such rows only after UNPAID_GRACE_MS (30 days per the privacy text). For such a run the Celestao page shows "這筆解讀還沒開始產生。如果已在 World 付款，通常約 1–3 分鐘內會開始" and polls every 5 s (prepared is in the poll list at world-wallet.js line 72) although nothing can change its state. No money is at risk: the run is unpaid by construction. Disclosed item 6 describes the retired subset only. Fix: have status() also return ordered (order_id is not None) and expiresMs (both already in _run_view's columns) and in showRun treat state==='prepared' && !run.ordered && run.expiresMs + grace <= Date.now() as lapsed: show a "this quote lapsed; start the reading again" text, keep the link, do not poll.

**Reproduction**

Server: status() (paid.py line 81) returns run['state'] without looking at expires_ms or order_id, so a vault row with state 'prepared', order_id NULL, expires_ms in the past answers {"state":"prepared","recordId":null,"failure":null}. Page: fake page from tests/celestao-home-prices.test.mjs, session 200, /api/world/runs/RID answers bound({state:'prepared',recordId:null,failure:null}) six times, firing the fake timer after each. Expected: a terminal "lapsed" view with no poll. Actual: 6 status checks sent, 1 timer still pending, text '這筆解讀還沒開始產生。如果已在 World 付款，通常約 1–3 分鐘內會開始；請查詢原始訂單，不需另建新單。'.

---

Judge's submission `690aebbda29b3f2da39d64efff3b923158b19a4270570f23ac8b4d31c4c75432`, 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.
