You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Architecture scan: the bug rate is not a size or coverage problem — it is two specific blind spots, and the codebase already invented the fix for one of them
Prompted by "we're getting lots of bugs lately". I used the 14 issues filed in one session (#420-#433) as the sample, then looked for shared structural causes rather than surveying file sizes.
What is NOT the problem (checked, so it can be ruled out)
Test volume.workers/api/src: 273 test files / 296 sources. Routes: 44 / 59. agent-do alone has 5 test files. This is a well-tested codebase by any normal measure.
So "write more tests" and "split big files" are both the wrong prescription. The bugs are getting through a suite that is large and a codebase that is well-decomposed.
Blind spot 1 — no SQL in the test suite is ever executed
test files mocking D1 by string-matching SQL : 59
@cloudflare/vitest-pool-workers / miniflare : not configured
Every D1 test is of the shape if (sql.includes("FROM error_log")) return { results: … }. The SQL string is matched, never parsed, never run. So the suite cannot detect a syntax error, a wrong column, or a platform limit.
This is precisely how #423 shipped.activeInstancesForDay builds 6 SELECTs joined by 5 UNIONs — which D1 rejects outright. It failed on every cron tick for 29.6 hours, produced 1780 identical error rows (97% of the entire error log), and the stats feature it powers has never written a row in production. It passed CI green, because stats-rollup.test.ts covers buildSeries / completedDay / enumerateDays / trendCards — the pure helpers — and never executes the query.
A test that runs SQL against real D1 would have caught it the moment it was written.
Blind spot 2 — the contracts between layers are unverified
tests asserting the query string a route passes to its DO : 0
This is how #428 shipped. "Load older messages" is broken in three places at once:
the client sends a UUID as an ordering cursor (InstanceDetail.tsx:475),
the route rebuilds the query string with only limit, silently dropping before (chat.ts:160),
the DO never implemented before at all (agent-do.ts:768).
Each layer is individually tested. Each test passes. Nothing tests the hop. Verified against production: page 2 is byte-identical to page 1.
#432 is the same shape one layer down — terminal snapshots are persisted, fetched in full, and then all but the last are discarded in the client.
The pattern behind the rest: a fix applied to one sibling and not the other
Two of these are literally the same bug in two places (#421/#427, #426/#431). Nothing propagates a fix to analogous code, so a defect class gets fixed once and survives everywhere else.
The codebase already invented the right tool — and applies it inconsistently
This is the important finding. There is an existing, sophisticated pattern for exactly this failure mode:
mute-invariant.test.ts is the model: it does not pin today's implementation, it asserts a reachability property across every phase, and it includes a structural scan of use-voice.ts because "no pure test can see it". Its header explains why a locally-correct one-line fix would have silently deleted a feature — the exact failure mode above.
That tool exists for mute, security, prompt claims, usage claims and seed drift. It does not exist for:
1. Execute SQL in tests. Adopt @cloudflare/vitest-pool-workers for the query-bearing modules so D1 statements actually run against real D1/miniflare. Highest value per unit of effort in this list: it converts an entire invisible defect class (#423) into a CI failure. It does not require rewriting the 59 existing mocks — start with the modules that build dynamic SQL, which is where the risk concentrates.
2. Contract tests at the seams. For each parameter crossing client → route → DO, one test asserting it survives the hop. Cheap, mechanical, and would have caught #428 and #432. The natural home is beside the existing route tests.
3. Extend the invariant-test pattern to the four properties above. This is not new methodology — it is applying a proven in-house pattern to the properties that are currently unguarded. Each is a small, pure, scanning test in the shape mute-invariant.test.ts already established.
What I deliberately do NOT recommend
A large refactor or re-architecture. The evidence does not support it. Modules are well-separated, purity is used heavily and deliberately, and the docs are accurate. The defects are at joins and in unexecuted branches, which a re-architecture would move rather than remove.
Splitting more files. The ratchet already governs size and passes.
More unit tests. The ratio is already 273/296. More of the same kind would not have caught a single one of the 14.
Acceptance criteria — three of five are MET; only the last two remain (audit 2026-08-23)
Both blind spots the title names are CLOSED. Do not rebuild them.
A deliberately malformed D1 query fails a test rather than production. — workers/api/src/lib/d1-sqlite.ts (298 lines, a real SQLite engine for the suite), lib/sql-execution.test.ts, lib/sql-schema.test.ts. Commit 77e17c1c. The sweep runs 502 statements across 117 modules against the schema the migrations build.
Adding a styleGuidance branch without a length rule fails a test — satisfied by the TYPE SYSTEM, which is stronger than the scan this asked for. lib/agent-style-prompt.ts:196-217 makes StyleBranch.lengthDefault a required field, so a fifth branch omitting it fails tsc --noEmit, which CI runs as a named gate. Behaviour also pinned at lib/agent-style-prompt.test.ts:59describe("every styleGuidance branch carries a length rule (#430)").
What remains — two guards, and one of them needs a floor built first
Adding an external call with no deadline fails a test.
Do not write the scan first — it would go red on 62 of 65 call sites and be suppressed. Measured on main, workers/api/src, excluding tests: 65 fetch( sites, 3 files constructing an AbortController (lib/user-ai.ts:191,421, lib/steps.ts:332, lib/mcp-credentials.ts:520), and 0 uses of AbortSignal.timeout.
Two steps, in order:
The floor — give safeFetch (workers/api/src/lib/ssrf.ts) a default AbortSignal.timeout. It is already the chokepoint every connector, fetch_url and ingest path goes through, so this converts most of the 62 in one edit without touching a call site. This is a finding in its own right and is the higher-value half.
The ratchet — only then the scan, in the sql-schema.test.ts shape: extract fetch( sites, assert each carries a signal or routes through safeFetch. Pin it EXACTLY (like control-shapes.test.ts), not as a <= ceiling.
Adding a mobile control row without responsive labels fails a test.
No responsive-label scan exists. Its natural home is the existing harness, not a new file: store/console/src/lib/control-shapes.test.ts (the exact-pinned ratchet over hand-authored button/card shapes), alongside store/console/src/lib/control-classes.test.ts:160-169 which already holds the console / agents/coder/web / store/admin copies equal.
Its first assertion should be the live defect the harness already records — control-shapes.test.ts:150: // className="hidden sm:flex" on a <Button> does not hide it — BUTTON_BASE wins, silently. That is a real class of silently-dead responsive utility, not a hypothetical.
Files this work touches:workers/api/src/lib/ssrf.ts (the floor) · a new fetch-deadline scan beside lib/sql-schema.test.ts · store/console/src/lib/control-shapes.test.ts. Collides with nothing else currently open — the two halves are independent of each other and of every other issue in the delivered-work audit.
Sequencing note
#423 should be fixed before (1) lands, not after — 97% of the error log is that one bug, and the log is the instrument for judging whether any of this works. #424's audit and its alerting gap (errors24h is computed, thresholded by nobody) are the other half: these changes are only verifiable if someone finds out when they fail.
Related: #423 (the SQL that no test ran), #428 / #432 (the seam), #421 / #427 and #426 / #431 (one defect class, two sites each), #430 (computed everywhere, used in one branch), #302 (the ratchet that proves guards hold where good intentions did not), #424 (nobody is watching the log).
Architecture scan: the bug rate is not a size or coverage problem — it is two specific blind spots, and the codebase already invented the fix for one of them
Prompted by "we're getting lots of bugs lately". I used the 14 issues filed in one session (#420-#433) as the sample, then looked for shared structural causes rather than surveying file sizes.
What is NOT the problem (checked, so it can be ruled out)
scripts/check-file-size.mjs(from The #138 refactor was undone within hours — add a size ratchet so decomposition holds #302) passes: "14 pinned files at 15961/15990 lines; 593 source files scanned, none new over 800." The ratchet is working. Margin is thin (29 lines), but nothing is over budget.workers/api/src: 273 test files / 296 sources. Routes: 44 / 59.agent-doalone has 5 test files. This is a well-tested codebase by any normal measure.So "write more tests" and "split big files" are both the wrong prescription. The bugs are getting through a suite that is large and a codebase that is well-decomposed.
Blind spot 1 — no SQL in the test suite is ever executed
Every D1 test is of the shape
if (sql.includes("FROM error_log")) return { results: … }. The SQL string is matched, never parsed, never run. So the suite cannot detect a syntax error, a wrong column, or a platform limit.This is precisely how #423 shipped.
activeInstancesForDaybuilds 6SELECTs joined by 5UNIONs — which D1 rejects outright. It failed on every cron tick for 29.6 hours, produced 1780 identical error rows (97% of the entire error log), and the stats feature it powers has never written a row in production. It passed CI green, becausestats-rollup.test.tscoversbuildSeries/completedDay/enumerateDays/trendCards— the pure helpers — and never executes the query.A test that runs SQL against real D1 would have caught it the moment it was written.
Blind spot 2 — the contracts between layers are unverified
This is how #428 shipped. "Load older messages" is broken in three places at once:
InstanceDetail.tsx:475),limit, silently droppingbefore(chat.ts:160),beforeat all (agent-do.ts:768).Each layer is individually tested. Each test passes. Nothing tests the hop. Verified against production: page 2 is byte-identical to page 1.
#432 is the same shape one layer down — terminal snapshots are persisted, fetched in full, and then all but the last are discarded in the client.
The pattern behind the rest: a fix applied to one sibling and not the other
CodingTab.tsx:807—hidden sm:inlineon the 2-button toggle:887— the 4-button row, 80 lines below, same filelengthRulecomputed atagent-think.ts:645for every agentif (plainSpeech)— 1 of 4 branchesTwo of these are literally the same bug in two places (#421/#427, #426/#431). Nothing propagates a fix to analogous code, so a defect class gets fixed once and survives everywhere else.
The codebase already invented the right tool — and applies it inconsistently
This is the important finding. There is an existing, sophisticated pattern for exactly this failure mode:
mute-invariant.test.tsis the model: it does not pin today's implementation, it asserts a reachability property across every phase, and it includes a structural scan ofuse-voice.tsbecause "no pure test can see it". Its header explains why a locally-correct one-line fix would have silently deleted a feature — the exact failure mode above.That tool exists for mute, security, prompt claims, usage claims and seed drift. It does not exist for:
styleGuidancebranch carries a length rule" ([bug] Coding agents get NO length rule in the prompt — lengthRule is computed for all and emitted only inside plain-speech, and the Behaviour tab shows "balanced" for a field that is unset #430)sm" ([bug][mobile] Copy and Delete sit on top of the message timestamp — 42px of 110px covered in WebKit, and removing the year only recovers 30px #426, [bug][mobile] Terminal/Issues/Pulls/Actions tabs overflow — the icon-only pattern exists 80 lines above in the same file and was not applied #431)Recommendation — three changes, in order of value
1. Execute SQL in tests. Adopt
@cloudflare/vitest-pool-workersfor the query-bearing modules so D1 statements actually run against real D1/miniflare. Highest value per unit of effort in this list: it converts an entire invisible defect class (#423) into a CI failure. It does not require rewriting the 59 existing mocks — start with the modules that build dynamic SQL, which is where the risk concentrates.2. Contract tests at the seams. For each parameter crossing client → route → DO, one test asserting it survives the hop. Cheap, mechanical, and would have caught #428 and #432. The natural home is beside the existing route tests.
3. Extend the invariant-test pattern to the four properties above. This is not new methodology — it is applying a proven in-house pattern to the properties that are currently unguarded. Each is a small, pure, scanning test in the shape
mute-invariant.test.tsalready established.What I deliberately do NOT recommend
Acceptance criteria — three of five are MET; only the last two remain (audit 2026-08-23)
Both blind spots the title names are CLOSED. Do not rebuild them.
workers/api/src/lib/d1-sqlite.ts(298 lines, a real SQLite engine for the suite),lib/sql-execution.test.ts,lib/sql-schema.test.ts. Commit77e17c1c. The sweep runs 502 statements across 117 modules against the schema the migrations build.workers/api/src/lib/do-seam.ts+lib/do-seam.test.ts(commit597e2377, the dispatch table that can be asked which query parameters a path takes) andworkers/api/src/routes/do-seam.contract.test.ts(435 lines, commit99174a9c). Proven red on the exact [bug] "Load older messages" re-returns the newest page — the before cursor is a UUID, the route drops it, and the DO never implemented it #428 hunk.styleGuidancebranch without a length rule fails a test — satisfied by the TYPE SYSTEM, which is stronger than the scan this asked for.lib/agent-style-prompt.ts:196-217makesStyleBranch.lengthDefaulta required field, so a fifth branch omitting it failstsc --noEmit, which CI runs as a named gate. Behaviour also pinned atlib/agent-style-prompt.test.ts:59describe("every styleGuidance branch carries a length rule (#430)").What remains — two guards, and one of them needs a floor built first
Adding an external call with no deadline fails a test.
Do not write the scan first — it would go red on 62 of 65 call sites and be suppressed. Measured on
main,workers/api/src, excluding tests: 65fetch(sites, 3 files constructing anAbortController(lib/user-ai.ts:191,421,lib/steps.ts:332,lib/mcp-credentials.ts:520), and 0 uses ofAbortSignal.timeout.Two steps, in order:
safeFetch(workers/api/src/lib/ssrf.ts) a defaultAbortSignal.timeout. It is already the chokepoint every connector,fetch_urland ingest path goes through, so this converts most of the 62 in one edit without touching a call site. This is a finding in its own right and is the higher-value half.sql-schema.test.tsshape: extractfetch(sites, assert each carries a signal or routes throughsafeFetch. Pin it EXACTLY (likecontrol-shapes.test.ts), not as a<=ceiling.Adding a mobile control row without responsive labels fails a test.
No responsive-label scan exists. Its natural home is the existing harness, not a new file:
store/console/src/lib/control-shapes.test.ts(the exact-pinned ratchet over hand-authored button/card shapes), alongsidestore/console/src/lib/control-classes.test.ts:160-169which already holds the console /agents/coder/web/store/admincopies equal.Its first assertion should be the live defect the harness already records —
control-shapes.test.ts:150:// className="hidden sm:flex" on a <Button> does not hide it — BUTTON_BASE wins, silently. That is a real class of silently-dead responsive utility, not a hypothetical.Files this work touches:
workers/api/src/lib/ssrf.ts(the floor) · a new fetch-deadline scan besidelib/sql-schema.test.ts·store/console/src/lib/control-shapes.test.ts. Collides with nothing else currently open — the two halves are independent of each other and of every other issue in the delivered-work audit.Sequencing note
#423 should be fixed before (1) lands, not after — 97% of the error log is that one bug, and the log is the instrument for judging whether any of this works. #424's audit and its alerting gap (
errors24his computed, thresholded by nobody) are the other half: these changes are only verifiable if someone finds out when they fail.Related: #423 (the SQL that no test ran), #428 / #432 (the seam), #421 / #427 and #426 / #431 (one defect class, two sites each), #430 (computed everywhere, used in one branch), #302 (the ratchet that proves guards hold where good intentions did not), #424 (nobody is watching the log).