The 2026-07-06 prod outage was a missed manual migration: the 0002
job_events migration never ran against prod, so the worker crash-looped
on every job. Make the deploy pipeline own it:
- railway.json sets `npm run db:migrate` as the pre-deploy command.
Railway runs it with the service's env before starting the new
deployment; a failed migration fails the deploy and the old version
keeps serving.
- runMigrations() now serializes on a Postgres advisory lock so api and
worker pre-deploys firing off the same push can't race Drizzle's
journal writes.
- DEPLOY.md: document the automatic path; keep the manual command for
first-time setup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two changes that multiply effective queue throughput:
- In-flight dedup: POST /v1/clones now attaches identical submits (same
cacheKey) to the already queued/running job instead of enqueueing a
duplicate capture. Retry-spam previously multiplied queue load (observed
4x same-URL submissions in prod); now it's free. Best-effort — a
same-instant race can still double-submit. Gated on !noCache.
- WORKER_CONCURRENCY (default 1): the worker registers N independent
pg-boss pollers, running up to N captures concurrently per process with
no head-of-line blocking. Each slot gets its own verify harness dir
(harnessDir/slot-i) so concurrent framework builds never collide;
concurrency 1 keeps the flat harnessDir layout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ported from PR #15 (packages-side surface only; the fork's dev UI page is
intentionally dropped - the API surface is the product):
- job_events table + repository, worker emits stage transitions, additive
GET /v1/clones/:id/events polling route. The fork's hand-written
migration was never registered in Drizzle's journal (it silently never
applied); regenerated properly via drizzle-kit from schema.ts with the
(job_id, seq) index declared, and verified against a live Postgres
- In-memory backend moves to the documented enqueue-202 + poll contract
with single-flight BUSY guard; POST honors backend httpStatus
- app-preview static serving routes; the preview BUILD half is deferred
until the compiler exports buildApp/DEFAULT_HARNESS_DIR - routes 404
cleanly until then
- preview option threaded through core types; cache key intentionally
unchanged while the flag is inert
All workspaces typecheck; api 18/18, core 13/13, worker 1/1 pass;
migration verified applying (table + index + FK) on postgres:16.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>