Submit, poll and recover a job
Step 1: choose the wallpaper or avatar preset and open the tool workspace. Enter a prompt (≤2000 chars), optionally a negativePrompt (≤1000 chars), and pick a width/height from the preset whitelist. Model and steps are fixed server-side; arbitrary model URLs are not accepted.
Step 2: press Run. The browser first GETs /api/v1/presets and checks available; when false it shows "service temporarily unavailable" and stops — no fake result. When available it POSTs /api/v1/jobs with an Idempotency-Key header and receives 202 with jobId, deadlineAt and pollAfterMs.
Step 3: poll GET /api/v1/jobs/:id with 2→5→10 s backoff; polling pauses in a hidden tab and resumes when visible. With no observation data the UI shows phase names and "waiting for available resources" instead of fabricated percentages or ETA. Cancel POSTs /api/v1/jobs/:id/cancel.
Step 4: the download button appears only after status succeeded and the written-back object passed size/type/dimension/SHA-256 verification; POST /api/v1/jobs/:id/download returns a 5-minute scoped credential link. After refresh, only jobId and expiry are restored under al:create:v1:recovery; prompts and tokens are never stored. Results expire after 7 days and access is refused.
Operations: workers use the separate /api/worker/v1 register/claim/heartbeat/complete/fail endpoints; the browser never holds worker credentials. The state machine is testable in Node with an injected in-memory store; the real PostgreSQL DDL lives in migrations/001_init.sql.