Skip to content

fix: fal drift probe skips on 403/locked/infra instead of poisoning baseline - #329

Closed
jpr5 wants to merge 1 commit into
mainfrom
fix/fal-probe-infra-resilient
Closed

fix: fal drift probe skips on 403/locked/infra instead of poisoning baseline#329
jpr5 wants to merge 1 commit into
mainfrom
fix/fal-probe-infra-resilient

Conversation

@jpr5

@jpr5 jpr5 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

The regression

The live fal queue-lifecycle canary merged in #327 (falQueueLifecycleCanary in src/__tests__/drift/providers.ts, driven by the live leg in src/__tests__/drift/fal-queue.drift.ts) submits to the real fal queue. With FAL_KEY set but the fal account balance exhausted, fal returns:

403 {"detail":"User is locked. Reason: Exhausted balance. Add funds to continue."}

The probe threw a hard InfraError on this. The drift collector cannot parse an InfraError into a drift finding, so it routes it to exit-5 quarantine — which makes the "Base drift report" step (run against main) fail on every PR and on the scheduled drift run. Observed:

InfraError: INFRA_ERROR: fal Queue Lifecycle — fal queue submit: API returned 403
(https://queue.fal.run/fal-ai/flux/schnell): {"detail":"User is locked. Reason: Exhausted balance..."}

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 into ctx.skip() with a clear reason:

fal infra/auth unavailable (403 user-locked) at submit — skipping live canary

Key properties:

  • FalCanarySkip is not an InfraError, so withInfraErrorTag re-throws it untouched and the collector never quarantines it.
  • The submit-step skip fires before the job is enqueued (no cost, nothing to cancel); the status-step skip is rethrown only after the cancel fires, so a job is never left to run.
  • Not over-broadened: only infra-class statuses convert. A genuine 2xx-with-wrong-shape drift is never an infra status, so it still flows through parseJsonResponsetriangulate and still fails / reports critical drift. The expected 400 ALREADY_COMPLETED cancel 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):

  • RED (pre-fix)ERROR CLASS : InfraError / MESSAGE : INFRA_ERROR: fal Queue Lifecycle — fal queue submit: API returned 403 ... Exhausted balance → quarantine.
  • GREEN (post-fix)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_position is a string where the mock has a number):

  • The canary does not skip (200 is not infra); the leg's exact triangulate(exemplar, real, mock) still yields a critical diff → the leg still fails on real drift. Proven green in fal-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 --noEmit on 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. When FAL_KEY is later funded, the live canary runs for real again; while the account is locked it honestly skips.

…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.
@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@329

commit: 062a650

@jpr5

jpr5 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

CI note — the one red check (drift-live-pr) is expected and clears on merge

drift-live-pr runs two legs. The base leg checks out origin/main and runs the collector against main's still-buggy probe — so on this PR it reproduces the exact regression live:

InfraError: INFRA_ERROR: fal Queue Lifecycle — fal queue submit: API returned 403
  ... User is locked. Reason: Exhausted balance...
WARNING: 1 test failure(s) could not be parsed as drift reports — quarantined for review (exit 5).
##[error]Base collector quarantined unparseable output (exit 5) — manual triage required

The head leg (this PR's fixed code) was skipped only because the base leg fails first. The base leg cannot go green on the PR because it re-runs main by design — that is precisely why this is a main-baseline regression, and it clears the moment this merges (scheduled main runs then produce clean, reusable base reports and future PRs' base legs run the fixed code).

Every check that gates this PR's own code is green: all 15 test matrix jobs (Node 20/22/24 × Python 3.10–3.13), prettier, eslint, commitlint, build-and-push, exports, agui-schema-drift, scan, preview. The failure is the known 403/exhausted-balance quarantine, not a genuine 2xx envelope drift.

@jpr5

jpr5 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

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.

@jpr5 jpr5 closed this Jul 23, 2026
jpr5 added a commit that referenced this pull request Jul 23, 2026
… 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.
jpr5 added a commit that referenced this pull request Jul 23, 2026
… 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.
jpr5 added a commit that referenced this pull request Jul 23, 2026
… 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant