feat: remix worker engine (t0.5) — RemixJob orchestration + claim/report/upload#19
feat: remix worker engine (t0.5) — RemixJob orchestration + claim/report/upload#19mtmtian wants to merge 7 commits into
Conversation
Add an independent worker-engine RemixJob path alongside the existing
Seedance2-direct remix: control plane creates a pending RemixJob + brief via
POST /api/creatives/remix-jobs, an external HMAC-authenticated worker claims
it, generates beats, assembles/QCs the clip, and reports progress/results
back via /api/worker/remix-jobs/{claim,report,upload}. Purely additive —
reuses buildRemixBrief/competitorCreativeToAnalysis from remix-brief.ts
without touching any existing files.
…n upload Close the code-review gaps on the worker-facing RemixJob surface: report/ now enforces an atomic ALLOWED_PREV transition table (succeeded/failed are terminal), validates outputUrl against the canonical GCS upload path, and surfaces brand-QC failures via Creative.reviewNotes instead of silently promoting. upload/ pre-checks HMAC headers and content-length before buffering the body. claim/ recycles stale in-flight jobs after a configurable lease window and collapses the duplicated jobId/queue branches into one atomic updateMany. De-dupes asJson + canvas-dims math into src/lib/growth/remix-job.ts, and shares the e2e HMAC-signing helper between remix-jobs.spec.ts and competitor-ingest.spec.ts.
Review — remix worker engine (t0.5)Thanks for this — the control plane is well-built. Claim atomicity via the single guarded The findings below are concentrated in one endpoint plus a transactionality gap. Severity labels are ours; happy to discuss any of them. 🔴 Blocking —
|
…-path index Review fix for PR oratis#19: claim/upload/report need a way to detect a preempted worker so it can never overwrite a new holder's output. Adds claimToken (reissued per claim) + attempt (bumps per claim, versions the output object key) plus @@index([status, updatedAt]) for the claim hot path. audit.ts gets the four remix.job_* AuditAction variants used by the routes in the next commits.
Review fixes for PR oratis#19: - claim: mint+return claimToken/attempt, rate-limit (worker-claim, 600/min/IP), audit-log remix.job_claim. - upload: require x-adex-claim-token, bind the HMAC signature to jobId:claimToken:sha256 so a preempted worker's old signature can't be replayed; verify claimToken + in-flight status before writing; version the object key as v${attempt}.mp4; rate-limit (worker-upload, 120/min/IP); audit-log remix.job_upload. - report: require claimToken in the body, include it in both updateMany wheres (atomic fencing), disambiguate count!==1 into 404/409-stale-claim/ 409-illegal-transition; wrap updateMany + re-read + linked-Creative update in a single $transaction so the job can never end up terminal with an orphaned Creative; exact-match the canonical (versioned) outputUrl instead of substring; best-effort delete the current attempt's blob on failed; rate-limit (worker-report, 300/min/IP); audit-log remix.job_report. - creatives/remix-jobs: wrap Creative+RemixJob create in one transaction, move the rate-limit key from 'remix-jobs' to 'remix' (shared bucket with the sibling /api/creatives/remix so the two endpoints can't double the effective burst), audit-log remix.job_create.
Review fix for PR oratis#19: e2e.yml had no DB, so competitor-ingest.spec.ts and remix-jobs.spec.ts self-skipped in CI, silently. Adds a postgres:16-alpine service container + DATABASE_URL, INGEST_WEBHOOK_SECRET/WORKER_WEBHOOK_SECRET (testing-only values, not real secrets) and INVITE_CODES_DISABLED=true so register works without minting codes, plus a `prisma migrate deploy` step. playwright.config.ts's webServer.env now reads WORKER_WEBHOOK_SECRET and INGEST_WEBHOOK_SECRET from process.env (falling back to the same literals the specs themselves fall back to) instead of hardcoding WORKER_WEBHOOK_SECRET — previously a runner that set the secrets in its own env to un-skip the specs but didn't also override this hardcoded value would sign requests the dev server couldn't verify.
…e cases Review fix for PR oratis#19: existing claim/report/upload cases updated for the new contract (claimToken/attempt from claim, claimToken required on report, versioned canonical outputUrl). New negative cases: report with a wrong claimToken → 409 stale claim; upload missing x-adex-claim-token → 401; upload signed against the wrong claimToken → 409. All reject before touching GCS, so no credentials needed; the upload happy path stays untestable locally (same as before). .env.example documents REMIX_JOB_LEASE_MINUTES (already read by claim/route.ts, was undocumented).
Fixes pushed —
|
What
Adds a second, worker-driven remix engine alongside the existing in-app single-shot text2video path (which is untouched). This phase ships tier t0.5 only: per-beat text-described regeneration — the storyboard from
buildRemixBriefis generated beat-by-beat by an external worker (Cloud Run Job, separate repo), assembled with ffmpeg, brand-QC re-scanned (whisper + OCR), and lands as the same review-gatedCreative(source:'remix', reviewStatus:'pending'). Competitor material stays analysis-only — no competitor pixels enter generation, consistent with the "borrow structure, not pixels" rules inremix-brief.ts/competitor-media.ts.RemixJobmodel + migration: brief snapshot, per-beat progress,qcReport,costTokens;tier/segmentPlanfields reserved for later tiers (API rejects anything butt0_5for now).POST/GET /api/creatives/remix-jobs(session auth): reusesbuildRemixBrief/competitorCreativeToAnalysis; shares the existing remix burst + daily-cap guardrails./api/worker/remix-jobs/*(HMAC viaingest-auth.verifyHmac, newWORKER_WEBHOOK_SECRET):claim— atomic pickup (updateManycount check) withREMIX_JOB_LEASE_MINUTESstale-lease recyclingreport— atomic status state machine (pending→claimed→running→assembling→qc→succeeded/failed, terminal states locked, 409 on illegal transitions);outputUrlmust match the canonical upload path; QC-fail is surfaced to reviewers viaCreative.reviewNotes(promotion not blocked — reviewer decides)upload— raw mp4 → GCS via server-sideuploadToGCS(content-sha256-bound HMAC, pre-buffer header checks, 100MB cap)Design notes
storage.tsgains one additive export (gcsPublicPrefix)./creatives/reviewgate; remix history keeps working viaCreative(source:'remix').Test plan
npm run lint/npx tsc --noEmitclean; migration verified against schema withprisma migrate diff(no drift).e2e/remix-jobs.spec.ts: 15 passed — happy path (ingest → job → claim → running/assembling/qc → succeeded promotes Creative to ready), claim atomicity, HMAC rejection, terminal-state 409, unclaimed→succeeded 409, non-canonical outputUrl 400, QC-fail reviewNotes.Context: this is the first slice of the tier-ladder proposal (t0 = your existing path / t0.5 = this PR / t1 & t2 gated pending the policy question in the alignment doc we shared). Happy to adjust
RemixJobmodeling orStoryboardBeatintegration to your preference.🤖 Generated with Claude Code