Five-Credit Image Generation Workflow
Trace the starter's complete asynchronous paid task through authentication, entitlements, idempotent credits, durable work, private storage, and failure compensation.
Verified against starter commit
2a1a04a.
Image generation is the starter's minimal reference for a production-shaped paid workflow. The local provider returns a generated SVG rather than calling a commercial model; the important part is the chain around it. One accepted request costs exactly five pooled organization credits.
Run it locally
Enable the development-only provider:
ENABLE_DEMO_FEATURES=true
ENABLE_IMAGE_GENERATION_MOCK=true
IMAGE_GENERATION_MOCK_FAILURES=0Start pnpm dev:all, sign in with the seeded demo account, and open /en/tasks/image-generation. The UI submits one idempotency key and polls the tenant-scoped task endpoint until the worker reaches a terminal state.
The recoverable chain
POST /api/tasks/image-generationchecks same origin, rate limit, authentication,tasks.image_generation, and the shared monthly task limit.(user_uuid, type, idempotency_key)identifies one task. A SHA-256 request fingerprint rejects reusing that key with another prompt.task_image:<task uuid>identifies the five-credit spend. Concurrent request replays return the existing logical debit instead of spending again.task_image_generation:<task uuid>deduplicates the durable job. A crash between task, spend, and dispatch is repaired by replaying the request.- The worker calls the provider with the stable task UUID, writes a deterministic private Garage/S3 object, verifies it, activates its file row, and marks the task succeeded.
GET /api/tasks/[uuid]resolves the organization-scoped file and creates a short-lived signed URL. Bucket credentials and object keys never enter the public task contract.
The task state machine is:
pending_payment → queued → running → succeeded
↘ refunding → failedProvider attempts stop after five; the job has eight attempts, leaving three attempts for compensation if the database is temporarily unavailable. A terminal provider failure moves the task to refunding, appends the deterministic ledger refund, and only then records failed. Replaying a refunding or terminal task never runs the provider or refund twice.
Set IMAGE_GENERATION_MOCK_FAILURES=5 to exercise the retry and compensation path. The real-database suite proves one spend and one refund after five failures; Playwright proves request replay, exact five-credit balance change, worker execution, Garage storage, and signed download.
Replace the provider, not the workflow
Implement the real adapter in src/services/ai/image.ts. Honor the supplied idempotency key and abort signal, return bytes plus content type, and keep provider credentials and SDK types inside that adapter. Preserve task, credit, job, storage, and public error contracts.
Ready to launch means: duplicate HTTP requests spend once, provider retries use one external identity, private output is tenant-scoped, a crash after upload repairs the task link, terminal failure refunds once, and operators can recover a buried compensation job.
Related: Organization Credit Ledger, Private Storage Uploads, Durable Jobs and Readiness, and Testing and CI Contracts.