fix: fal drift probe skips on 403/locked/infra instead of poisoning baseline - #329
fix: fal drift probe skips on 403/locked/infra instead of poisoning baseline#329jpr5 wants to merge 1 commit into
Conversation
…aseline The #327 live fal queue canary threw a hard InfraError on a fal-account 403 (user-locked / exhausted-balance). The drift collector cannot parse that into a finding, so it exit-5 quarantines it and the "Base drift report" step (run against main) failed on every PR and the scheduled run. Fix: infra-class statuses (401/402/403/429/5xx) become an honest FalCanarySkip the live leg catches and turns into ctx.skip(), exactly like the no-key skip. FalCanarySkip is not an InfraError, so withInfraErrorTag re-throws it untouched and the collector never quarantines it. A genuine 2xx-with-wrong-shape drift is never an infra status, so it still fails and reports drift.
commit: |
CI note — the one red check (
|
|
Superseded by fix/fal-probe-in-progress, which folds in this infra-skip (FalCanarySkip + tests) AND adds the IN_PROGRESS lifecycle handling that the funded-fal reruns exposed. #329 alone couldn't merge (its base step ran main's still-broken probe → poisoned drift-live-pr) and doesn't cover the IN_PROGRESS AssertionError. Branch kept for reference; the consolidated fix re-enables the live leg correctly after the descope lands. |
… leg The live fal queue-lifecycle canary (#327) assumed jobs sit in IN_QUEUE long enough to cancel for $0. Funded flux/schnell returns status:"IN_PROGRESS" on submit immediately; by cancel time the job is running/completed and fal's cancel envelope is {detail:...} (no status field). The leg hard-asserted `typeof cancel.body.status === 'string'` -> AssertionError -> collector exit-5 quarantine -> the drift-live-pr base step red on every PR (descoped in #330). Fix (probe-only, no fal.ts change): - Lifecycle: state-agnostic submit -> status -> CANCEL; accept any valid queued/running/completed status; cancel always fires even if status polls throw. Relax the brittle cancel assertion to require only a JSON envelope ({status} OR {detail}); leave shape grading to triangulate so a genuine 2xx-wrong-shape still reports critical drift. - Infra-skip (folds in #329, which is being closed): 401/402/403 locked-or- exhausted/429/5xx raise a distinguished FalCanarySkip -> honest ctx.skip, never an InfraError that quarantines the baseline. - Re-enable: drop the FAL_LIVE_QUEUE opt-in gate #330 added so the corrected leg runs live in CI again (still gated on FAL_KEY). Cost-safe: cheapest model, cancel ASAP, result endpoint never fetched. Residual: <= one sub-cent generation if the model races to completion. Red-green in fal-canary-skip.test.ts drives the real canary + real triangulate: IN_PROGRESS+{detail} handled (leg passes), 403 -> FalCanarySkip, 2xx-wrong- shape still critical.
… leg The live fal queue-lifecycle canary (#327) assumed jobs sit in IN_QUEUE long enough to cancel for $0. Funded flux/schnell returns status:"IN_PROGRESS" on submit immediately; by cancel time the job is running/completed and fal's cancel envelope is {detail:...} (no status field). The leg hard-asserted `typeof cancel.body.status === 'string'` -> AssertionError -> collector exit-5 quarantine -> the drift-live-pr base step red on every PR (descoped in #330). Fix (probe-only, no fal.ts change): - Lifecycle: state-agnostic submit -> status -> CANCEL; accept any valid queued/running/completed status; cancel always fires even if status polls throw. Relax the brittle cancel assertion to require only a JSON envelope ({status} OR {detail}); leave shape grading to triangulate so a genuine 2xx-wrong-shape still reports critical drift. - Infra-skip (folds in #329, which is being closed): 401/402/403 locked-or- exhausted/429/5xx raise a distinguished FalCanarySkip -> honest ctx.skip, never an InfraError that quarantines the baseline. - Re-enable: drop the FAL_LIVE_QUEUE opt-in gate #330 added so the corrected leg runs live in CI again (still gated on FAL_KEY). Cost-safe: cheapest model, cancel ASAP, result endpoint never fetched. Residual: <= one sub-cent generation if the model races to completion. Red-green in fal-canary-skip.test.ts drives the real canary + real triangulate: IN_PROGRESS+{detail} handled (leg passes), 403 -> FalCanarySkip, 2xx-wrong- shape still critical.
… leg (#332) ## What Proper fix for the fal live queue-lifecycle drift probe (`falQueueLifecycleCanary` + the live block in `src/__tests__/drift/fal-queue.drift.ts`, merged #327), and **re-enables** the live leg that #330 temporarily descoped. Probe-only — no change to aimock's `fal.ts` core. ## The bug (verified live in CI, root cause) The probe assumed fal jobs sit in `IN_QUEUE` long enough to cancel for \$0. Funded `flux/schnell` returns `status:"IN_PROGRESS"` on the submit envelope **immediately**. By cancel time the job is running/completed, so fal's cancel envelope is `{detail:"..."}` — **no `status` field**. The live leg hard-asserted `typeof cancel.body.status === "string"` → AssertionError → the collector could not map it → **exit-5 quarantine** → the `drift-live-pr` **base** step (main's code) went red on every PR. #330 was the fast, reversible descope; this is the proper fix. ## The fix 1. **Lifecycle** — state-agnostic submit → status → CANCEL. Accept any 2xx submit and any valid queued/running/completed status (`IN_QUEUE` **or** `IN_PROGRESS` …), never assert the job is specifically queued. Cancel still fires even if the status poll throws (a job is never left running). 2. **Envelope shapes / drift** — the cancel assertion now requires only a JSON envelope (`{status}` **or** `{detail}`); precise shape grading is left to `triangulate` against the mock, so a genuine **2xx-wrong-shape still reports critical drift**. 3. **Infra-skip (folds in #329, which is being closed in favor of this PR)** — `401/402/403` (locked/exhausted) `/429/5xx` raise a distinguished `FalCanarySkip` → honest `ctx.skip`, never an `InfraError` that quarantines the baseline. Includes #329's `fal-canary-skip.test.ts` guard tests. 4. **Re-enable** — removes the `FAL_LIVE_QUEUE` opt-in gate #330 added; the corrected leg runs live in CI again (still gated on `FAL_KEY`). ## Cost safety Cheapest reliably-available model (`fal-ai/flux/schnell`), cancel ASAP, the paid `response_url` result endpoint is **never** fetched. Residual: at most **one sub-cent** generation if the model races to completion before the immediate cancel lands. ## Red-green proof (local, drives the real canary + real `triangulate`) RED captured against pre-fix probe (origin/main @ d43701c): ``` × RED(a): IN_PROGRESS submit + {detail} cancel — old assert `typeof cancel.body.status === "string"` → AssertionError: expected 'undefined' to be 'string' × RED(b): 403 user-locked at submit → rejects with InfraError: "INFRA_ERROR: fal Queue Lifecycle …" (quarantine-causing, not a skip) ``` GREEN post-fix (`src/__tests__/drift/fal-canary-skip.test.ts`, 4/4 pass): - (a) IN_PROGRESS submit + `{detail}` cancel → canary handles it, `triangulate` reports no critical → leg passes. - (b) 403 user-locked → `FalCanarySkip` (not `InfraError`); 401/402 also skip. - (c) **guard**: 2xx submit whose envelope diverges from the mock → still critical drift (fix did not swallow genuine drift). ## Checks (local) `prettier` ✓ · `eslint` ✓ · `tsc --noEmit` (tests-inclusive) 0 errors ✓ · `test:drift` 16 passed / 9 skipped ✓ · full suite 4693 passed ✓ · `tsdown` build ✓ Live validation (corrected leg against funded fal) = CI `drift-live-pr` on this PR — see checks below. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01KamK73Wu5heLxJEogM6hHC
The regression
The live fal queue-lifecycle canary merged in #327 (
falQueueLifecycleCanaryinsrc/__tests__/drift/providers.ts, driven by the live leg insrc/__tests__/drift/fal-queue.drift.ts) submits to the real fal queue. WithFAL_KEYset but the fal account balance exhausted, fal returns:The probe threw a hard
InfraErroron this. The drift collector cannot parse anInfraErrorinto a drift finding, so it routes it to exit-5 quarantine — which makes the "Base drift report" step (run againstmain) fail on every PR and on the scheduled drift run. Observed:This is a main-baseline regression: it poisons the drift baseline for every PR, not just fal work. It blocks #325.
The infra-skip fix
Infra-class fal statuses are an availability signal, not drift. The canary now converts 401 / 402 / 403 (incl. user-locked / exhausted-balance) / 429 / 5xx at any lifecycle step (submit, status) into a distinguished
FalCanarySkip, which the live leg catches and turns intoctx.skip()with a clear reason:Key properties:
FalCanarySkipis not anInfraError, sowithInfraErrorTagre-throws it untouched and the collector never quarantines it.parseJsonResponse→triangulateand still fails / reports critical drift. The expected400 ALREADY_COMPLETEDcancel race is untouched.Change is scoped to the fal probe/leg plus a one-line skip-preserving guard in the shared
withInfraErrorTag. Test-infra only — no shipped-bundle change, no version bump.Red-green proofs
RED→GREEN #1 — the regression (stubbed fal 403 user-locked at submit):
ERROR CLASS : InfraError/MESSAGE : INFRA_ERROR: fal Queue Lifecycle — fal queue submit: API returned 403 ... Exhausted balance→ quarantine.ERROR CLASS : FalCanarySkip/STATUS : 403/MESSAGE : fal infra/auth unavailable (403 user-locked) at submit — skipping live canary.Guard — real drift must still be caught (stubbed fal 2xx submit whose
queue_positionis a string where the mock has a number):triangulate(exemplar, real, mock)still yields a critical diff → the leg still fails on real drift. Proven green infal-canary-skip.test.ts.New regression/guard tests live in
src/__tests__/drift/fal-canary-skip.test.ts(3 tests: 403-user-locked → skip, 401/402 → skip, 2xx-wrong-shape → still drifts). Full unit suite (4643 passed / 44 skipped) and full drift suite (93 passed / 65 skipped) green;tsc --noEmiton touched files clean; prettier/eslint/build clean.Impact
Restores
main's drift baseline (the "Base drift report" step no longer quarantines on a locked fal account) and unblocks #325. WhenFAL_KEYis later funded, the live canary runs for real again; while the account is locked it honestly skips.