Skip to content

finding: no gate sees rest-server.ts's response envelopes — check:route-envelope does not scan the file, and neither gate has a rule for envelope POSITION #7295

Description

@os-help

Measured while implementing #7035 (PR #7293), whose triage ruling asked to extend the error-code-casing / route-envelope gate family to cover the two non-conforming /meta 501 sites if it does not already. It does not, and extending it is not the one-rule change the dispatch hoped for — so this is filed rather than built there.

Filed unassigned, finding-class: nothing a user hits today. It is a gate gap, not a defect.

1. check:route-envelope never looks at rest-server.ts

scripts/check-route-envelope.mjs's discover() collects only files whose name ends in -routes.ts, plus the single hard-coded i18n-service-plugin.ts. packages/rest/src/rest-server.ts matches neither, so it has never been audited — while being, by a wide margin, the largest response-emitting file in the repo (#5949).

Running the gate's own exported scanSource against it by hand:

packages/rest/src/rest-server.ts {"responses":208,"ok":2,"err":0,"privateOk":0,"stringError":44}

For comparison, every module the gate does audit declares 0 / 0 / 0 — they route every body through the shared sendOk / sendError in packages/types/src/response-envelope.ts.

The gate's header states its own thesis on exactly this point: "A module discovered by the scan but absent from the table is an ERROR, not a default", and "a module nobody thought to convert still gets audited." Both were written about the discovery surface. The file with 208 hand-built write sites sits outside it.

2. Even if it were scanned, the sibling-code dialect is invisible

scanSource counts responses, ok, err, privateOk and stringError. There is no counter for a top-level code sitting as a sibling of error — which is one of the two dialects #7035 was about:

// what the gate can see:            { error: 'some message' }        → stringError
// what it cannot:                   { error: 'some message', code }  → nothing

So adding the file to the table would catch the bare-string half of #7035 and silently pass the sibling-key half.

3. check:error-code-casing scans the file, but only for casing

scripts/check-error-code-casing.mjs does walk packages/**, rest-server.ts included — but its patterns match lowercase literals in code positions (code: 'x', .code = 'x', code === 'x', union types). Position is outside its thesis, and it is textual by design; the header explains that check-route-envelope went AST precisely because textual counting of response shapes was wrong twice over. Teaching position to the textual gate would re-make that mistake.

Why this is not a small fix

Bringing rest-server.ts into check:route-envelope means entering it as a ratchet at responses: 208. That number moves whenever any of the file's ~208 write sites is added or removed, in a file that changes several times a day — so the gate would go red constantly for reasons unrelated to envelopes, and the pressure would be to raise the ratchet rather than fix anything. The gate's model — "declare zero, route everything through the shared pair" — is #7035's option 3 (a shared envelope constructor for the file), which that card's triage explicitly ruled out of scope.

So the honest options are a decision, not a patch:

  1. Convert rest-server.ts onto the shared sendOk / sendError, then declare 0 / 0 / 0 like every other module. Largest, and the only one that ends the problem. Effectively finding: rest-server.ts 里三个相邻 /meta handler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035 option 3 + the gate for free.
  2. Ratchet the file in as-is (responses: 208, stringError: 44) and add a siblingCode counter plus self-test cases. Cheap to write, but pins a hot number and creates recurring unrelated red.
  3. Extend discovery, but count only the error-shape facts (stringError, a new siblingCode), not responses. Stable under ordinary edits because it counts dialects rather than write sites, and it would have caught both halves of finding: rest-server.ts 里三个相邻 /meta handler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035. Middle-sized; needs a ratchet at 44 that only ever ticks down.

My read, from having just measured it: option 3 is the one worth costing, because it is the only one whose number does not move under unrelated edits and it covers both dialects. But this is a gate-architecture decision, not a dev call.

Not measured (do not cite as fact)

  • Whether other unscanned files have the same exposure. discover()'s -routes.ts convention was only checked against rest-server.ts here; a repo-wide census of response-emitting files outside the convention was not run.
  • The 44 stringError sites in rest-server.ts were counted, not read. How many are /meta, how many are reachable in a default deployment, and whether any consumer reads them — unmeasured.

Related

#7035 (the card this came out of; PR #7293 converged three /meta 501 sites and deliberately left the other ~44) · #5949 (rest-server.ts size) · #3877 (response bodies never checked against their declaring schemas — adjacent, but about schema validation rather than gate discovery) · ADR-0112 · ADR-0049


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-10, directed in session session_01BPWqbmEFU8gJepBJTHESXd): Option 3 — extend discovery, count dialect facts only.

    check:route-envelope learns rest-server.ts, counting stringError plus a new siblingCode counter (with self-test cases), NOT responses — the number must not move under unrelated edits in the repo's hottest response file. Ratchet starts at the measured 44 and only ticks down. Option 1 (full conversion to sendOk/sendError) remains the eventual end state but was already ruled out of #7035's scope and is not resurrected here; option 2's hot-number ratchet is rejected for the reason the card gives.

    needs-user-decision → pm:queue.


    Generated by Claude Code

  2. self-assigned this
    on Aug 10, 2026
  3. os-help commented on Aug 10, 2026

    @os-help
    CollaboratorAuthor

    Claim — PM loop domain:cli, session session_0158ZQo7LiHSxGWpYKuPq1wu (os-help seat, #6024), wave 2 round 6.

    • Branch: claude/issue-7295-route-envelope-dialect-discovery
    • Scope as ruled (maintainer comment above, Option 3): check:route-envelope learns rest-server.ts via extended discovery; counts dialect facts only — stringError (ratchet starts at the measured 44, only ticks down) plus a new siblingCode counter with self-test cases. ⛔ No responses count for this file; ⛔ no conversion of rest-server.ts itself (option 1 stays out of scope).
    • File surface: scripts/check-route-envelope.mjs + its self-tests. Does not edit packages/rest/src/rest-server.ts.
    • Serial check: no open PR touches the gate script; [console][nav] filterNav never drops a group DECLARED with children: [] — Setup renders an inert 「Approvals」 group on a runtime without plugin-approvals #7380 (dispatched this wave) edits rest-server.ts (filterNav) but changes no error-dialect site, so the 44 baseline is expected to hold — the dev must re-measure the baseline at branch time rather than trusting this number.
    • Tier / container: M · claude-opus-5 · mode:cloud (own container).

    Generated by Claude Code

  4. os-help commented on Aug 10, 2026

    @os-help
    CollaboratorAuthor

    ACCEPT — PR #7459, head bb9966fa3.

    Reviewed against the ruling (option 3), self-proved on the diff rather than on the report — the dev's container could not reach the GitHub API, so there is no report comment; its full rationale is in the commit message of bb9966fa3 and the PR body.

    • Scope: exactly one file, scripts/check-route-envelope.mjs (+277/−17). packages/rest/src/rest-server.ts is untouched — verified by git diff --stat, so option 1 stayed out of scope and [console][nav] filterNav never drops a group DECLARED with children: [] — Setup renders an inert 「Approvals」 group on a runtime without plugin-approvals #7380 (same wave, same file) has no interaction with it.
    • Dialect facts only: the module entry declares dialectOnly + ratchet with stringError: 44, siblingCode: 77, and responses is deliberately not declared — the header states why, and dialectOnly refuses to be declared beside responses. That is the ruling, enforced structurally rather than by comment.
    • Baseline re-measured at the branch point, as the dispatch required, not carried over from the card.
    • Self-test: three positives (literal pair, computed message, shorthand) and four negatives (nested code, code inside data, a lone code, commented-out prose) — the siblingCode counter has both directions pinned.
    • CI, conclusions read personally: ESLint success, TypeScript Type Check success. Check Changeset failed first (no changeset — correct, this touches no package), skip-changeset applied, and the re-run concludes skipped; the original failed run stays red and does not self-clear, which is expected, not a defect.

    Ready flipped, auto-merge armed, queue branch pr-7459-… observed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions