Skip to content

docs(rest,spec): qualify the ADR-0112 "producer names the condition" citations to the code axis - #12453

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-11735-adr-0112-citation-axis
Aug 26, 2026
Merged

docs(rest,spec): qualify the ADR-0112 "producer names the condition" citations to the code axis#12453
os-litant merged 1 commit into
mainfrom
claude/issue-11735-adr-0112-citation-axis

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#11735

Nine in-repo sites cite the sentence "the producer names the condition" as an unqualified principle of ADR-0112. The ADR does not contain the sentence, and does not rule the axis the unqualified form was read on. Each site now names the axis.

What was measured, on origin/main at 8515954fb

The card's zero-hit reproduces exactly, both halves. Re-run rather than inherited:

git grep -n -iE "producer names|names the condition" origin/main -- docs/adr/0112-error-code-vocabulary-and-ledger.md -> 0 hits
git grep -n -iE "producer names|names the condition" origin/main -- docs/adr/ -> 0 hits

Reverse-check on the same file, terms taken from the ADR's own subject rather than from the phrase under test: declaredCode 7, vocabulary 22, ledger 16. The instrument produces positives over that file; the phrase is absent.

The substance reading holds, checked directly against the ADR. D1-D9 (plus D6b, D6c and five amendments) every one rule the semantic code: D2 renames the closed StandardErrorCode catalog, D3 registers service codes in the ledger, D4 closes ApiErrorSchema.code and opens declaredCode beside it, D5 fixes one eventual location for the semantic code. HTTP status appears only as what a code derives from ("error.code carries the member the HTTP status derives", maintainer rulings 2026-08-16 / 2026-08-17) and what lives on the transport (D5). Nothing in ADR-0112 rules what HTTP status an undeclared throw deserves.

The premise fork resolved toward the ruled remedy, and there is a landed sibling that already says so

Triage's binding condition was to stop and report if the status-axis reading turned out to be a real ruling from somewhere. It is not:

  • Zero hits across docs/adr/ for producer names / producer that knows / producer declares / one condition, one wire shape / producer-declared.
  • Zero hits for declaredHttpStatus anywhere under docs/.
  • No ADR, maintainer ruling or governed doc places the sentence on the status axis.

More decisively, packages/rest/src/rest-hook-refusal-message-parity.test.ts:630 — landed 2026-08-25 in #12280, after this card was filed — already carries the ruled qualification and reaches the same conclusion independently:

"ADR-0112's 'the producer names the condition' is a rule about the code, not the status. Read D1-D9 and its five amendments: every one of them rules on the code vocabulary, its closure, and the declaredCode demote channel. The phrase this reading is built on is not in the ADR at all — it is error-response.ts's own prose."

It also names what does rule the status: the /data door's structural branch (declaredHttpStatus(error) ?? 400) with two end-to-end pins behind it. This PR aligns the remaining sites with that already-landed reading rather than inventing a second formulation.

The census is not nine

Measured independently and not reconciled with the card's table. The card's own re-check command returns 11 on today's origin/main, and the wrapped-phrase form returns 13 citation sites. All nine of the card's files are still present; two line numbers moved (error-response.ts 871 to 923, analytics-filter-refusal-envelope.test.ts 405 to 447).

Four sites are outside the declared file surface and are therefore untouched here:

sitestatewhy untouched
packages/rest/src/rest-server.ts:9646unqualifiedFENCED by PR #12421 (#11926)
packages/rest/src/rest-sandbox-declared-status.test.ts:130unqualifiedoutside the declared surface
packages/rest/src/rest.test.ts:2422unqualifiedoutside the declared surface
packages/rest/src/rest-hook-refusal-message-parity.test.ts:630already qualifiedno work needed; it is the canonical form

So two sites plus the fenced one still carry the unqualified citation after this PR. Reported for the PM to route rather than absorbed silently — the surface was declared closed and "stop on breach and explain" applies.

packages/spec/src/shared/external-errors.ts:47 is making a DIFFERENT claim

The card flagged this site as unexamined. Read on its own terms, it is not one of the eight: the other eight all say "no code was declared, so none is invented" — squarely the code axis. This one sits under the heading "Why HERE and not in a REST error map" and cites ADR-0112 to justify declaring the HTTP status on the error at the producer. That is the status axis, and it is the one place where the ruled remedy could not be applied verbatim: qualifying it to the code axis would have made the sentence a non-sequitur in its own paragraph.

It is corrected on its own terms instead. The paragraph's argument is preserved and its real warrant named — this repo's convergent HTTP-exit convention (status then statusCode, 400-599, the exits the docblock already enumerates) rather than an ADR-0112 ruling. Note the same file already cites ADR-0112 D3 correctly, two paragraphs above, for the ledgered code; that citation is untouched.

Nothing else in packages/spec is touched: no schema, no key, no describe-text, no shape.

Why this is worth more than a comment tidy

The unqualified form was read as governing status and nearly settled a contract question the wrong way: #11684 named it as the argument for 500 on /analytics/dataset/query, and PR #11731 carried a binding stop that would have landed nothing had both readings had live pins. It survived only because a dev measured that the ADR does not say it. Same class as #11032.

rest-hook-refusal-status-passthrough.test.ts is the site nearest that misreading — a status-passthrough test whose comment could be read as ADR-0112 ruling the 500 — so it now says explicitly that the 500 is the classifiers' default (#7525), not an ADR-0112 ruling.

The card's other unmeasured question, answered

Whether any other principle is attributed to ADR-0112 in the same unqualified way: no. The only other principle-style attribution in the tree is packages/metadata-protocol/src/protocol.legacy-overlay-delete.test.ts:405, and it is already correctly scoped ("the catalog governs error.code"). The widespread "ADR-0112 envelope (code + status)" idiom (50+ sites) is a different usage — it names the wire envelope as an assertion contract, not a principle about who names what — and is the form the repo's own agent contract prescribes for rejection tests. Not a defect; measured and left alone.

Scope

Comment text only. Zero behaviour change, zero assertion changes, no schema, no key, no validity boundary. docs/adr/** untouched — the ADR amendment remains a maintainer-merge path and is explicitly out of scope; nothing here is written as though the ADR were about to change, and the correction holds under either outcome of that open question, which is why it forecloses nothing.

Verification

All runs below are on the final commit a17b6b649, working tree clean at measurement time.

Gate families re-derived in this worktree from the actual changed files — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, which derives its own change set from the merge base (8 paths vs 8515954fb) and reported 22 matched families.

checkverdict line
pnpm lint (full repo, eslint . --no-inline-config)exit 0 — 5195 files linted, 0 errors, 0 warnings (population read from ESLint's own config; no narrowing claimed)
pnpm check:nul-bytesOK (scanned 6888 text file(s) ... no raw ASCII control bytes)
pnpm check:route-envelope4 module(s) discovered and audited ... 2 conformant, 2 ratcheted (pre-existing ratchets, unmoved)
pnpm check:dispatcher-error-vocabularyOK — 21 unregistered code-stamping site(s), all classified
pnpm check:test-source-aliasOK — 72 packages with tests scanned
pnpm check:cross-package-test-inputsOK: 18 package(s) read outside themselves, all declared
check-comment-mask-adoption.mjsOK — 23 private comment-stripper(s) ... all 23 recorded
pnpm check:engine-double-contractexit 0, 398 (file, verb) row(s) held by the RETAINED ledger
pnpm check:where-matcherconformance holds: 303 matcher(s) ... none new
pnpm check:query-options-erasureratchet holds ... none new
@objectstack/spectypecheckexit 0, all three legs echoed; test layer compiles ... 263 error(s) held (unmoved)
@objectstack/speccheck:liveness / check:empty-state / check:variant-docsall exit 0
6 edited @objectstack/rest test filesTest Files 6 passed (6), Tests 151 passed (151)

Build closure first, as a new worktree requires: turbo run build --filter='@objectstack/rest...' --filter='@objectstack/spec...' — 25/25 tasks successful. Every heavy run went through scripts/pm/os-verify-lock.sh; exit codes were captured before any pipe, and each row above quotes the gate's own verdict line rather than a shell $?.

One NOT MEASURED, stated rather than papered over:@objectstack/rest's tsc --noEmit program excludes *.test.ts — verified with --listFiles, which returns 0 hits for each of the six edited test files and 1 for error-response.ts. So the rest typecheck is a real green over the one edited source file and says nothing about the six test files; those are measured by the vitest run instead, not by typecheck.

No changeset: comment text only, nothing user-visible to publish. skip-changeset could not be applied from the dev seat — api.github.com returns GitHub access is not enabled for this session, and gh is not installed, so the label needs applying by the PM.


Generated by Claude Code

…citations to the code axis
The sentence is cited at nine in-repo sites as an unqualified principle of
ADR-0112. The ADR does not contain it, and every decision it does contain
(D1-D9 plus five amendments) rules the semantic CODE vocabulary; HTTP status
appears only as what a code derives from and what lives on the transport.
Read unqualified, the citation was taken for a status ruling and argued for a
500 on a contract question the ADR is silent on. Each site now names the axis:
ADR-0112 D4 governs the semantic-code channel, and rules no HTTP status for an
undeclared throw. error-response.ts carries the full statement once, including
that the phrase is that file's own prose rather than an ADR quotation, and the
other sites echo it.
Comment text only -- no behaviour, no schema, no assertion changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️1 changed file(s) yielded no anchor (packages/spec/src/shared/external-errors.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/shared/external-errors.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8515954fb6dd4102e120b59ce6410f94600d5710packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4bb5f339e8e6a2cabeecdb695c93fa3f126a907d — the merge of head a17b6b649e9fad4ffba228fe1ec02142b284b237 into base 8515954fb6dd4102e120b59ce6410f94600d5710, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4bb5f339e8e6a2cabeecdb695c93fa3f126a907d && git checkout 4bb5f339e8e6a2cabeecdb695c93fa3f126a907d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8515954fb6dd4102e120b59ce6410f94600d5710 a17b6b649e9fad4ffba228fe1ec02142b284b237 && git checkout -B drift-repro 8515954fb6dd4102e120b59ce6410f94600d5710 && git merge --no-ff a17b6b649e9fad4ffba228fe1ec02142b284b237
node scripts/docs-audit/affected-docs.mjs --json 8515954fb6dd4102e120b59ce6410f94600d5710

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-litantos-litant added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 26, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review August 26, 2026 04:23
@os-litant
os-litant added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit b6c96bcAug 26, 2026
40 of 41 checks passed
@os-litant
os-litant deleted the claude/issue-11735-adr-0112-citation-axis branch August 26, 2026 04:45
os-litant pushed a commit that referenced this pull request Aug 27, 2026
… axis
PR #12453 qualified nine sites; three carried the unqualified attribution
still — one because it was fenced by an in-flight PR at the time, two
because they sat outside that card's declared file surface.
Each now echoes the form `error-response.ts`'s docblock states in full:
ADR-0112 D4 governs the semantic-CODE channel, the producer names the
condition on the CODE axis, and the ADR rules no HTTP status for an
undeclared throw. Comment text only — the emitted JS is byte-identical
under a `removeComments` transpile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-litant@claude