Optional product examples

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 b16b416.

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=0

Start pnpm dev:all, sign in with the seeded demo account, and open /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

  1. POST /api/tasks/image-generation checks same origin, rate limit, authentication, tasks.image_generation, and the shared monthly task limit.
  2. (user_uuid, type, idempotency_key) identifies one task. A SHA-256 request fingerprint rejects reusing that key with another prompt.
  3. task_image:<task uuid> identifies the five-credit spend. Concurrent request replays return the existing logical debit instead of spending again.
  4. task_image_generation:<task uuid> deduplicates the durable job. A crash between task, spend, and dispatch is repaired by replaying the request.
  5. 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.
  6. 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 → failed

Provider 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.

Adapt the example for production

Implement the real adapter in src/services/ai/image.ts, honoring the supplied idempotency key and abort signal. Keep provider credentials and SDK types inside that adapter, and preserve task, credit, job, storage, and public error contracts.

Replacing the adapter alone does not enable production generation. The page, API route, and task service each check isImageGenerationMockEnabled(), which is always false in production. Replace those demo admission checks with an explicit production feature configuration, and keep same-origin, authentication, entitlement, quota, and credit checks at their existing boundaries.

The current GeneratedImage type also permits only image/svg+xml, the svg extension, and provider mock. Define and validate the real provider's allowed output types, extensions, provider identity, and matching file metadata before accepting its bytes. Test the production admission path as well as retries, private output delivery, and compensation.

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.