Uh oh!
There was an error while loading. Please reload this page.
gate: reconcile the documented HTTP status of an error code against the status the runtime emits (#8967) - #9023
Merged
os-project-manager merged 1 commit intoAug 16, 2026
Conversation
…he status the runtime emits (#8967) `content/docs/api/error-catalog.mdx` and `content/docs/protocol/kernel/error-handling.mdx` publish an HTTP status per error code; the doors decide it. Nothing reconciled the two, so `MISSING_REQUIRED_FIELD` was documented 400 in both pages while five `controlled_by_parent` refusal paths answered 422, with CI green throughout. `scripts/check-error-status-conformance.mjs` closes that. The runtime side is DERIVED, never listed: error classes' own `readonly status` / `readonly statusCode`, both `sendError` doors' literal arguments, the REST mapper's `{ status, body }` terminals, and `HttpStatusErrorCodeMap`'s explicit entries (never the bucket fallback). Identifiers resolve through a repo-wide constant index including computed-key status tables; an unresolvable declaration is reported, not dropped; comments are stripped first, because this repo narrates envelopes in prose and two of the gate's first findings were docblocks describing a bug that had been fixed. The assertion is a set comparison in BOTH directions, so a code may carry more than one documented status: every status the runtime can emit is documented, and every status the docs claim is reachable. Doc statements are graded — per-code lines and quick-reference rows CLAIM (both directions), `## … (NNN)` section headings only COVER (direction A), because a category heading is not a per-code assertion. Verified red on PR #8963's merge parent (90197e1), where the real pre-fix pages filed the code under 400 with no exception: x MISSING_REQUIRED_FIELD: the runtime can emit HTTP 422, and no doc publishes that status for it (documented: 400). Emitted at packages/plugins/plugin-security/src/errors.ts: class MasterReferenceMissingError. Swapping in ONLY #8963's two doc files on that same tree clears it. The real pre-fix text is embedded as a `--self-test` red case so the property is pinned, not demonstrated once. Two more of the same defect class, found by the new instrument on `main` and corrected here in `error-catalog.mdx`'s quick reference (docs only, no runtime status moved): `NOT_IMPLEMENTED` is served 501 by four storage routes while the page filed it under 500, and `TIMEOUT` is the code a 504 carries while the 500 row claimed it and nothing emits it at 500. Refs #8967
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-project-manager
marked this pull request as ready for review
August 16, 2026 06:41
os-project-manager
enabled auto-merge
August 16, 2026 06:42
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager
deleted the
claude/issue-8967-error-status-conformance
branch
August 16, 2026 07:03
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 freeto 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#8967
content/docs/api/error-catalog.mdxandcontent/docs/protocol/kernel/error-handling.mdxpublish an HTTP status per error code; the doors decide it. Nothing reconciled the two, soMISSING_REQUIRED_FIELDwas documented 400 in both pages while fivecontrolled_by_parentrefusal paths answered 422, on a public error contract, with CI green throughout.scripts/check-error-status-conformance.mjs+pnpm check:error-status-conformance, wired into.github/workflows/lint.yml.Lead evidence — RED on the pre-#8963 tree, not on a synthetic fixture
Checked out PR #8963's merge parent (
90197e15e) as a second worktree, dropped the gate in, ran it against the real pre-fix pages and the pre-fix runtime:The A/B isolates it to the two doc files. On that same pre-fix tree,
git checkout 02042bef2 -- content/docs/api/error-catalog.mdx content/docs/protocol/kernel/error-handling.mdx(only #8963's docs, runtime untouched) ⇒ 3 findings, theMISSING_REQUIRED_FIELDline gone; restoring the pre-fix docs brings it back. The three remaining are theTIMEOUT/NOT_IMPLEMENTEDfindings this PR corrects.That is a demonstration, so it is also pinned: the pre-fix
**HTTP Status:** 400line and the pre-fix quick-reference 400 row are byte-copied out of90197e15einto the--self-testas a permanent red case, with the landed post-#8963 text as its green twin.Positive control for the zero-hit case. A green run that reconciled nothing is a blind run, not a clean one. The gate publishes
reconciled: N code(s) … M (code, status) pair(s) matched, exits 1 if that count is ever 0, and a--self-testcase asserts the green fixture reports exactly 2 matched pairs while a producer-free fixture reports 0.(a) The runtime side is derived, never listed
No
code → statustable exists in this PR. Every runtime status is read out of source:readonly code+readonly status/readonly statusCodein one brace-matched class body@objectstack/typesdoorsendError(res, status, code, message)literal arguments@objectstack/restdoorsendError(res, { code, …, status }){ status: N, body: { …, code } }HttpStatusErrorCodeMap— explicit entries only, neverstandardErrorCodeForHttpStatus's bucket fallback, the same boundstandardSynonymOfdraws inerror-code-ledger.zod.tsIdentifiers resolve through a repo-wide constant index (scalars, object members,
MAP[OBJ.key], and computed-key status tables writtenas const satisfies …— theexternal-errors.tsshape). A name bound to two different literals is refused, not guessed. An unresolvable declaration is reported, never dropped — the run prints anunresolved:block; it is currently empty, and the three declarations that were in it are now read.Comments are stripped first, and that is load-bearing. This repo documents envelopes in prose, and two of the gate's very first findings on
mainwere docblocks:quickjs-runner.tsnarrating a bug it fixed, andprotocol.tsnaming a shape it exists to prevent. Read as producers, both manufacture a disagreement out of a sentence saying the opposite. Pinned as a--self-testcase using those two real sentences."No in-package declaration" is a stated finding, not a pass.#8880 recorded that
ValidationErrordeclares no status, so nothing pinned the doc's claim on either side. Every documented standard code with zero derived producer lands inscripts/error-status-unpinned-baseline.json— 37 today, shrink-only: a new one fails, and a row that becomes pinned fails until ratcheted down. The baseline-expanding remedy carries the⛔ MAINTAINER-ONLYmarker per the existing convention, and a--self-testcase holds the marker on that message and off the ratchet-down one.(b) One code may carry more than one status
The assertion is a set comparison in both directions — A: every status the runtime can emit is documented; B: every status the docs claim is reachable (asserted only for codes with a derived producer, which is what the census above bounds).
Doc statements are graded, deliberately:
error-handling.mdx's**HTTP Status:** …line, every 4xx/5xx integer on it, which is exactly how a documented exception is expressed; anderror-catalog.mdx's quick-reference rows.## … Errors (NNN)section headings.## Validation Errors (400)mirrorsErrorCategory.validation → 400: it states the category's status, not each member's. It can absolve an emitted status (a 405 under## Request Errors (405/428)is documented) but never demand reachability. Grading it up would have the gate manufacture findings from a heading that never claimed them.Two more of the same defect class, found by the new instrument and corrected here
Docs only — no runtime status moved, and #8880 direction 2 is untouched. Both are in
error-catalog.mdx's quick reference, both mechanical and pinned by source:NOT_IMPLEMENTEDis served 501, bypackages/services/service-storage/src/storage-routes.ts:625, 674, 692, 734, plusHttpStatusErrorCodeMap[501]. The page filed it under## Server Errors (500)and named 501 nowhere. ⇒ added a501row.TIMEOUTis the code a 504 carries (HttpStatusErrorCodeMap[504]), and nothing emitsTIMEOUTat 500. The500row claimed it. ⇒ moved to a new504row (both directions fired on this one).## Server Errors (5xx)with one sentence saying why, since those five codes are served at 500/501/503/504 — otherwise the corrected table contradicts the heading above it. No page links that anchor (checked here and inobjectui);check:doc-anchorsis green.Scope covered / not covered — the gate says so itself, on every run
Covered: the 53
StandardErrorCodemembers, the vocabulary both pages publish statuses for. Not covered: the registeredERROR_CODE_LEDGERcodes — 42 are derived and counted, but neither page publishes their status, so there is nothing to reconcile them against; documenting them is a separate card. The run prints both numbers, so a bounded gate cannot read as a complete one.Verification — union re-run at
bb0bc3397, the final commitpackages/spectests that read these pages:error-catalog-docs.test.ts,error-code-ledger.test.ts,errors.test.ts— 3 files, 46 tests, all pass.check:type-check-debtappears in this PR's derived gate set via the.github/workflows/lint.ymlglob; it needs the whole workspace build closure and re-measures per-package tsc errors. This diff contains no TypeScript (one.mjs, one.json,package.json, a workflow comment + step, one.mdx), so nothing it measures can have moved; left to CI.check:type-check-coverage's own half ran green here.skip-changeset: ascripts/gate plus workflow wiring publishes no package source.Generated by Claude Code
Generated by Claude Code