Skip to content

fix(smoke): scope §14 usage rollup to O(1) + wrap it legibly (cloud#224) - #225

Merged
unforced merged 1 commit into
mainfrom
ag-fix-usage-rollup-scope
Jul 27, 2026
Merged

fix(smoke): scope §14 usage rollup to O(1) + wrap it legibly (cloud#224)#225
unforced merged 1 commit into
mainfrom
ag-fix-usage-rollup-scope

Conversation

@unforced

Copy link
Copy Markdown
Contributor

What & why

deploy-staging.yml went red again right after #218 merged — but four sections earlier, at §14 (the usage rollup). It fell off the same O(fleet) cliff #218 fixed for the snapshot sweep: POST /__test/usage-run enumerated every staging vault, and with the fleet at ~268 (nothing reclaims smoke debris — see #224 item 1) the rollup outgrew the runtime's fetch timeout. It passed at 3m47s ~23h ago and timed out at 4m26s tonight.

Worse, §14 had neither an explicit AbortSignal nor a liveCatch, so the throw escaped uncaught to main().catch() as a bare, section-less DOMException with an empty stack — identifying which section died required diffing two CI logs.

This PR is #224 items 2 + 3 (item 1 — actually reclaiming staging debris — is owned separately). It does not touch §17, and does not delete any staging data.

The change

1. Scope the rollup O(1) — exactly the #218 pattern:

  • /__test/usage-run now takes ?vault=<name>; smoke §14 passes its own fresh arrival vault, so the rollup is O(1) not O(fleet).
  • Param is read after the if (!deps.exposeDevLinks) return c.notFound() guard — the prod 404 gate fully precedes it.
  • SQL is parameterized: WHERE name = ? LIMIT 1 (scoped) vs the unchanged ORDER BY name LIMIT ? (fleet).
  • The no-param path is byte-for-byte the old behavior, so the nightly USAGE_CRON and every ops.ts caller are untouched.

2. Make §14 fail legibly — the trigger now carries an explicit, generous timeout and routes through liveCatch: a genuine stall is ADVISORY (couldn't-verify, does not gate); any other throw stays FATAL; either way it now has a section label instead of an anonymous crash.

3. Apply #221's own lesson — don't couple a slow setup call to the assertions behind it. The rollup trigger and the console-render assertions now live in separate blocks: an unverified rollup skips the dependent surface checks with a named advisory rather than blinding them. This deliberately does not reproduce the shared-try coupling I flagged on §17 (that decoupling of §17 is a separate follow-up).

The timeout was not raised anywhere — scoping is what removes the fleet-size dependency, same as #218.

Advisory-vs-fatal, restated so the next person can classify without asking

Unchanged from #218's rule, applied here: FATAL = the thing is broken / a wrong answer (every assert() is fatal; ambiguous stays fatal). ADVISORY = we couldn't verify it right now — the only downgrade is isUnverifiable() (a client-side timeout / network-unreachable while driving live fleet infra). So a rollup that answers wrong (200 but recorded=0, or the console 200s without the usage markup) is still a fatal assert(); only a couldn't-complete becomes advisory.

Verified vs reasoned

Run in an isolated worktree (app/hub/vault siblings symlinked):

  • bun run typecheckexit 0.
  • root bun test177 pass / 0 fail. (One env-only failure first appeared: spa-build-source.test.ts clones ../parachute-app, which the worktree lacks; the SPA-build path is untouched by this diff, and the file is 13/0 once the sibling is linked.)
  • identity vitest — 1053 pass / 0 fail across 38 files (1050 baseline + 3 new scope tests), run under singleWorker because the shared box's other-actor workerd exhaust ephemeral ports under the default parallel pool (environmental, same class as fix(smoke): split staging gate advisory-vs-fatal + O(1) snapshot sweep #218's run).
  • Reasoned, not run: scripts/smoke-staging.ts itself (it creates live debris) — live behavior is reasoned from the code and the fix(smoke): split staging gate advisory-vs-fatal + O(1) snapshot sweep #218-identical scoping pattern.

🤖 Generated with Claude Code

deploy-staging.yml went red again after #218 — this time FOUR sections
earlier. §14, the usage rollup (POST /__test/usage-run), fell off the
same O(fleet) cliff the snapshot sweep hit: it enumerated EVERY staging
vault, and with the fleet at ~268 (nothing reclaims smoke debris —
cloud#224 item 1) the rollup outgrew the runtime's fetch timeout. §14 had
NEITHER an explicit AbortSignal NOR a liveCatch, so the throw escaped to
main().catch() as a bare, section-less DOMException (empty stack) —
pinning it down meant diffing two CI logs to see which section died.

Three parts (cloud#224 items 2 + 3):

1. Scope the rollup the way #218 scoped the sweep. /__test/usage-run now
   takes ?vault=<name>; smoke §14 passes its own fresh arrival vault, so
   the rollup is O(1), not O(fleet). The param is read AFTER the
   exposeDevLinks 404 guard (prod gate fully precedes it), the SQL is
   parameterized (WHERE name = ? LIMIT 1 vs the unchanged ORDER BY name
   LIMIT ?), and the no-param path is byte-for-byte the old behavior — so
   the nightly USAGE_CRON and every ops.ts caller are untouched.

2. Make §14 fail legibly. The trigger now carries an explicit, generous
   timeout and routes through liveCatch: a genuine stall is ADVISORY
   (couldn't-verify, does not gate), any other throw stays FATAL, and
   either way it now carries a section label instead of an anonymous
   crash.

3. Apply cloud#221's own lesson — don't couple a slow setup call to the
   assertions behind it. The rollup TRIGGER and the console-render
   ASSERTIONS now live in SEPARATE blocks: an unverified rollup skips the
   dependent surface checks with a named advisory rather than blinding
   them. This deliberately does NOT reproduce the shared-try coupling I
   flagged on §17.

The timeout was NOT raised anywhere; scoping is what removes the
fleet-size dependency (same fix shape as #218). Does not touch §17, does
not reclaim staging data (that is #224 item 1, owned separately).

Verified locally in an isolated worktree (app/hub/vault siblings
symlinked): root typecheck exit 0; root `bun test` 177 pass / 0 fail (the
single env-only failure is spa-build-source.test.ts cloning
../parachute-app, absent in the worktree — 13/0 once the sibling is
linked, and the SPA-build path is untouched by this diff); identity
vitest 1053 pass / 0 fail across 38 files (1050 + 3 new scope tests), run
under singleWorker because the shared box's other-actor workerd exhaust
ephemeral ports under the default parallel pool (environmental, same
class as #218's run). Did NOT run smoke-staging.ts (creates live debris)
— live behavior is reasoned from code.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLZtmuSs1RirWGMGyCB1QB
@unforced
unforced merged commit d773237 into main Jul 27, 2026
3 checks passed
@unforced
unforced deleted the ag-fix-usage-rollup-scope branch July 27, 2026 03:57
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