feat(devx): discover check-route-envelope's express population instead of enumerating it - #10351
Merged
os-zhuang merged 1 commit intoAug 20, 2026
Merged
Conversation
…d of enumerating it (#9937) Surface 4 shipped enumerated in #9813 — one file named by hand — because a discovery walk for the express dialect needs something the other three surfaces never did: a read/write discriminator. `res.json` is one spelling for two opposite acts, and the reading half is the majority of it in this repo: res.json({ success: true, data }) // express WRITE, one argument const body = await res.json(); // fetch READ, zero arguments Measured on origin/main (736cfb1) BEFORE widening the population, because the cost of being wrong is a gate that reddens on innocent files — 301 `res` `.json` calls under packages/: 218 writes in 7 files, 83 reads in 13 others, and not one file mixes them. Argument count alone would sweep in 11 DSL files (`Field.json({…})`, `t.json(c.name)`); the receiver name alone would sweep in all 13 fetch readers, 65% of the 20 files touching the spelling. Together: zero false positives. - `discoverExpressRoutes()` walks packages/ and refuses any undeclared express writer, mirroring surface 3. Both walks now share `discoverResponseWriters()`, so the two populations cannot drift apart in whichever copy nobody edits. - Both express spellings count: `res.json(body)` (81 calls) and the chained `res.status(400).json(body)` (137) — the second is where the refusals live, so a bare-form-only walk would count the successes and miss the errors. - The discriminator is self-tested in BOTH directions, the reject side asserted positively as `reads: 1` rather than as `bodies: 0` — a count a scanner that had simply stopped matching the receiver would also produce. The audit prints the reject side too (13 files skipped as fetch readers), since a walk can otherwise only ever show what it swept in. - First-audit verdict for all 3 newly swept-in files: `inbound-rate-limit.ts` conformant (its 429 goes through `buildApiError`); `query-allowlist.ts` and `query-multiplicity.ts` ratcheted at `unenveloped: 1` each — the shared 400 refusals are ADR-0112-nested already but carry no `success` above it. - `IHTTP_ROUTE_MODULES` becomes `EXPRESS_RESPONSE_MODULES`: the walk found a middleware and two refusal helpers, none of them route modules, and the repo types its handlers `any` at exactly the seam a mounting-mechanism split would need. The dialect is the population — the third open question in #9937. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
This was referenced Aug 20, 2026
os-zhuang
marked this pull request as ready for review
August 20, 2026 16:15
os-zhuang
enabled auto-merge
August 20, 2026 16:15
os-zhuang
deleted the
claude/issue-9937-route-envelope-express-population
branch
August 20, 2026 17:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #9937
Surface 4 of
scripts/check-route-envelope.mjsshipped enumerated in #9813 — one file named by hand — while surfaces 1 and 3 refuse any undeclared response-writing module. This builds the walk.Why it needed a discriminator, and what the discriminator cost
res.jsonis one spelling for two opposite acts, byte-identical in receiver name:Triage's sizing note said the false-positive surface was unmeasured and had to be measured before sweeping. It was, on
origin/main(736cfb1), and the result is what made a full sweep affordable rather than a narrower shape:resonlyField.json({…}),t.json(c.name),table.json(name)301
res.jsoncalls underpackages/: 218 writes in 7 files, 83 reads in 13 others, and no file mixes the two. The 20 in row 1 reproduces the card's own "~20res.json(files measured on main" exactly.Both express spellings are writes, because the second is where the refusals live:
res.json(body)(81 calls) and the chainedres.status(400).json(body)(137). A bare-form-only walk would have counted the successes and missed the errors — this gate's recurring failure, one dialect at a time (#7295, #8884, #9267).What landed
discoverExpressRoutes()— parses every non-test.tsunderpackages/and refuses any undeclared express writer, with failure text mirroring surface 3's. Surfaces 3 and 4 now share onediscoverResponseWriters(), so the two populations cannot drift apart in whichever copy nobody edits.reads: 1, not asbodies: 0— a scanner that had simply stopped matching the receiver producesbodies: 0too. The audit prints the reject side as well (13 file(s) skipped as fetch readers, 83 zero-argument calls), because a walk can otherwise only ever show what it swept in.MODULESandSHARED_BUILDER—rest-server.ts(137 of the write calls),error-response.tsandresponse-envelope.tsare held there by write-site counts that see these same calls.IHTTP_ROUTE_MODULES→EXPRESS_RESPONSE_MODULES. The card's third open question — one population or two — is answered one, and not as a preference: the walk found a middleware and two refusal helpers, none of them route modules, and the repo types its handlersanyat exactly the seam a mounting-mechanism split would need (query-allowlist.tssays so in its own signature). No references to the old name exist outside this file.First-audit verdict for every file swept in
packages/runtime/src/dispatcher-plugin.ts{}— already declared; unchangedpackages/runtime/src/security/inbound-rate-limit.ts{}— the 429 is{ success: false, error: buildApiError({…}) }, the same doordispatcher-plugin.ts's conformant exits usepackages/rest/src/query-allowlist.tsunenveloped: 1(L194)packages/rest/src/query-multiplicity.tsunenveloped: 1(L139)The two ratchets are the finding this walk was worth building for: the shared 400 refusals
rest-server.tsdelegates to are ADR-0112-nested already (error: { code, message }, the reference shapesecurity-suggested-bindings-envelope.test.tspins the family onto) but carry nosuccessabove them, sounwrapResponsehands them to callers raw. Neither file is a route module, neither follows the*-routes.tsconvention, and both were invisible to all four surfaces. They ratchet against #9559, the standing owner ofpackages/rest's convergence onto the sharedsendOk/sendError; converting the wire answers is that line's work, not this card's.The premise, re-derived on current
mainHalf of it held, half did not, and the report says which:
IHTTP_ROUTE_MODULESwas hand-named with no walk.dispatcher-plugin.tsreads{}onmaintoday: runtime dispatcher's SSE-fallback{ events }body is off-envelope — the one non-conforming literal body left in dispatcher-plugin.ts after #9813's discovery flip #9936 landed (PR fix(runtime): implement the buffered-send streaming fallback on both dispatcher write-less-transport branches #9973) and itsunenveloped: 1was already stepped down. So the walk's population starts from one conformant declared entry, not a drifting one.Verification
Every gate quoted by its own verdict line, exit codes captured before any pipe, all on the final commit
4594bd26.Surfaces 1, 2 and 3 are unmoved by the scanner change — 10 route modules, 16 dispatcher domains, 11 Hono modules / 165 bodies, identical to the baseline run on
736cfb14. That is the no-collateral evidence for touching the shared scanner.A green self-test proves nothing until it can go red
Six mutations, each confirmed on disk before running (injected marker present, anchor text gone) and each restored byte-identically afterwards:
--self-testNOT DECLAREDNOT DECLAREDinbound-rate-limit.tsNOT DECLARED … EXPRESS_RESPONSE_MODULESM4 is the reproduce-before-believing pin, kept permanently in the self-test: the walk must re-find every file the enumerated list named on the real tree, before it is trusted on files nobody enumerated.
Scope
skip-changeset: rootscripts/is not a publish surface and nopackages/*file is touched. Clause ② does not attach — its path limb ispackages/spec/src/**and nothing here is under it, and on the content limb this changes a lint gate's accept/reject behaviour, not any wire contract, Zod schema or published API. No route behaviour changed; not one byte of product code is in this diff.Generated by Claude Code