ImgGen state machine — golden flow & error states

Baseline drill 2026-09-01, updated post-ship 13.1.16 · red = error states · green PROBE = the #5262 fix (generate decision resolved by a targeted server read, live in prod) · remaining open seams: #5274 (originating view flip), #5244 (img fetch-failed state), #5245 (prompt-interpolation regen). Leave feedback below.

CLIENT — useImgGen hookSERVER — image-gen.ts pipelineDISPLAY — asset host & cookieprompt / _idstored versionempty + fromCacheprobe found docsconfirmed absent · or probe err/timeout (fail toward generate)no url yetarrivalsettled + no versioncommit okfail, nothing shownfail, image exists↻ regen (user)no viewerpnggive upgive up (post-Prodia!)vibeApi.imgGen (110s server wait)block.image (docWritten?)readcookie in timemint after paintidle / skipno prompt, no _idREADINGmedia query ∥ host getCACHE HITversion + sourceKey + model matchPROBE · targeted readempty+fromCache → ask server for THIS doc, 5s …PENDING · no urlversion stored, url unmintedGENERATING120s client timeoutDISPLAYED<img src=minted url>ERROR · no imageerror card, raw msgERROR · has imageoverlay badge, never blankanon skipnever billed; refires on authcredit gateFAILS OPEN on check erroraccess-fn authorizebefore billing · FAILS CLOSEDProdia dispatch3× retry 429/5xx onlystore → R23× transient, one uploadIdBILLPromptContexts + meterserver doc writeidempotent; never throws ⇒ docWritten:falseimg_gen_failed (1/turn)prodia_* | store_* | config | otherurl minted ON READmintFilesUrls, ?v=uploadIdasset-session cookiebridge fired at runtime.ready, NOT awaitedbytes fetchedBROKEN IMG until refreshmint after paint; no onError retry — url prese…
  1. 0 · FIXED & SHIPPED 13.1.16 (#5243 → PR #5262): the generate decision now probes
    The drill measured paint→image at ~2.5 min, ~2 min of it the (correct) #4826 settle gate holding generation on a cold replica. #5262 replaced the indefinite defer with a targeted authoritative read over the existing req-query-docs/req-get-doc wire: empty+unsettled ⇒ ask the server about THIS doc (5s deadline), found ⇒ display, confirmed absent ⇒ generate now, probe failure ⇒ generate now (owner-flipped posture: rather a rare duplicate than minutes of waiting). Verified live: dispatch at paint+9s (was +140s); stored images display instantly on cold views, zero re-bills.
  2. 0b · RESIDUAL (#5274): the originating view never flips after success
    Verification isolated the next seam: the view that REQUESTED the generation stays on 'Generating image…' 90s+ after the server generated, billed and wrote the doc — the post-commit re-query reads the local replica the probe bypassed for the decision, so the image only shows on the NEXT view (or refresh). Ought: resolve the post-generation display via the same probe (or the block.image payload) so the turn ends with the image on screen.
  3. 1 · An <img> load failure must be a STATE, not a coincidence (#5244)
    Today `url present` and `bytes fetchable` are collapsed: the asset cookie bridge is deliberately un-awaited (srv-sandbox.ts:702), and ImgGen.tsx renders <img> with no onError. If the mint lands after paint the card is broken until a manual refresh. Ought: displayed splits into displayed·loading / displayed·ok / displayed·fetch-failed with a bounded retry (re-set src after the session POST resolves). This is THE load-until-refresh bug.
  4. 2 · Unknown staleness must read as 'wait', never 'settled' (narrowed by #5262)
    The FacadeStaleness fields are optional and undefined reads as settled (use-img-gen.ts) — a legacy or injected facade could silently authorize generation off an unconfirmed empty. Post-#5262 the cost is bounded (a duplicate generation, the owner-accepted trade) and hosts with no probe lane (SSR/embed, expressed as confirmedMediaProbe: null) keep the old defer with a deferTick recovery.
  5. 3 · Image identity should anchor to the doc, not the prompt text (#5245)
    With no _id the doc id is hash(prompt+key+model); and even WITH _id, 3 of 5 drill apps interpolate editable fields (recipe.title, creature.name) into the prompt — so fixing a typo is a 'genuinely different input' and mints a billed regeneration. Ought: the skill teaches freezing an illustrationPrompt field at creation; renames change the label, not the art.
  6. 4 · Nothing after BILL may refuse the write
    Billing happens after storage but before the doc write; an app access.js that runs author-equality on every update denies the version append the platform already billed (#3784/#3832), arming a billed retry loop. ctx.isImgGenVersionAppend exists; the state machine ought to treat post-bill denial as a platform-refunded path, not an app bug the user pays for.
  7. 5 · store failure after Prodia succeeded is a billed-nothing path
    8 of 9 real img_gen_failed in the last 30d were store_r2_transient — the model was paid, the user got an error card. Retry budget is 3 fast attempts; #4396 says budgets are durations, not booleans. Ought: the caller re-runs storage.ensure with the same uploadId over a longer window before giving up.

Feedback