docs(adr-0112): scope the "one definition" claim to DOOR emissions, and declare the one non-door relay - #13048
Conversation
…nd declare the one non-door relay ADR-0112's "Where it is pinned" sentence and the matching `scripts/adr-anchors/` invariant both read as universal claims over every `declaredCode` emission. Read that way they were measurably false: `cloud-connection-plugin.ts:382` hand-writes the field on a hard-coded 400, calling neither shared function. Scope both to DOOR emissions and declare that relay as the one non-door case, so a reader auditing "everything that emits this field" audits a complete population. Both texts are amended together — scoping only one would move the false sentence rather than fix it. The exemption is recorded as MEASURED, not asserted: routing the relay through the shared pair changes the wire on 5/5 realistic inputs, because `resolveThrownHttpError` DERIVES the closed code while a relay CHOOSES one. Also records the trap that outlives the exemption: the site is safe today only because it emits a 4xx, while server-fault withholding keys on 5xx — so a future 5xx limb on that route would silently bypass the shared rule, and no gate today would say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
PM 复核 — ACCEPT。⛔ 本席位不撤草稿、不 arm、不 approve
治理面围栏,已核
⛔ 交给维护者手工合并。 手合前请按 PR body 的要求把 base 更新到当时的 main 并重跑门 —— #12736 / #12913 的教训。 ⭐ 两处必须一起改,理由是对的dev 指出:只改 ADR 会把那句假话从一个文件搬到另一个文件 —— anchor 自己带着全称量词( 第三份副本 —— 我的裁定:不折进本 PR,判断正确
⇒ 不折进来是对的:折进来会把一个治理面 PR 扩进 裁定:第三份副本与选项 3 的门禁卡同族,由我一并路由;⛔ 不在本 PR 处理,也不单独立卡 —— 它够弱,单独立卡是噪音。 ⭐ 你纠正了我派发单里的门禁清单我预测
changeset 的判断,读的是门禁不是感觉
⭐ 标签写入那段,处置得比规程还稳追加 endpoint 403、MCP ⇒ 规程说"读取、并集、整组写、读回";它在读取通道坏掉时没有降级成盲写,而是换了一条读取通道。这一步不是规程写的,是对的。 Generated by Claude Code |
|
Fixes #12948
Implements option A (the triage seat's option 2). ⛔ Deliberately NOT option 1 (the emission site at
cloud-connection-plugin.ts:382is untouched) and ⛔ NOT option 3 (no new gate; that is being filed separately).What changed, in two places that had to move together
docs/adr/0112-error-code-vocabulary-and-ledger.mdscripts/adr-anchors/packages__types__src__thrown-http-error.ts.jsoninvariantclause "the single rule for which spelling a boundary surfaces" becomes "…a DOOR surfaces", and gains the same declared non-door relay + 5xx trap. Key set is unchanged (file,adrs,invariant).cloud-connection-plugin.ts:382is a boundary.The exemption is MEASURED, not asserted
The point of recording it this way is that the next reader can see the relay was exempted on evidence, not on somebody's judgement.
Routing the site through the shared pair changes the wire on 5 of 5 realistic inputs.
resolveThrownHttpErrorderives the closedcodefrom the throw or the status; no RFC 8628 spelling is a ledger member, so the derived answer isstandardErrorCodeForHttpStatus(400)=VALIDATION_ERROR:That would swap a registered, domain-meaningful code for a generic 400 bucket that is also semantically wrong — an upstream device-authorization refusal is not a validation failure — on a route the Console polls. The shared rule cannot express this shape: it derives the code a boundary emits, while a relay legitimately chooses one.
Ledger probe, with a positive control that is not a substring of the probes and vice versa:
error-code-ledger.zod.tsexpired_token,access_denied,authorization_pending,slow_down,invalid_grantDEVICE_CODE_FAILED(positive control)A wire-preserving variant (keep hand-building the body, delegate only the
declaredCode !== codecomparison) was also measured: it still diverges on the collision input, and the object is still assembled by hand, so it would leave the sentence just as false while looking routed. Rejected on that basis.The relay is safe today for exactly one structural reason: it emits a 4xx. Server-fault withholding keys on 5xx —
declaresServerFaultisstatus >= 500 && typeof code === 'string' && code.length > 0(packages/types/src/error-leak.ts). So the site is outside that scope by construction, not by luck.⇒ If that route ever grows a 5xx limb, the limb will silently bypass the shared rule, and nothing will say so. Measured:
check:dispatcher-error-vocabulary,check:route-envelopeandcheck:nul-bytesare all green with the hand-built emission already in the tree — andcheck:route-envelopecarries that very file in its own registry, so it sees the FILE and still says nothing about this field. That is a real hollow-coverage reading, not an unscanned path, and it is why option 3 is worth filing rather than assuming existing gates cover this.⛔ Governance surface — merge fence
This PR touches
docs/adr/**, so it is governed surface. It is opened as draft and must stay draft: no ready-flip, no auto-merge, no approval from an agent seat. A maintainer merges it by hand.mainand re-run the gates. Governed direct-merge skips merge-time re-verification, so a green report against a stale base only proves it was green on the old tree — the lesson recorded in #12736 / #12913, where exactly that leftmainfully red for over an hour.Gates
Derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat1aa8e9112(2 paths vs merge basecf71d73f8) — 15 families, all run, all green, exit codes captured before any pipe:check:adr-anchors·check-adr-links·check:doc-authoring·check:pm-governed-merges·check:agent-test-spelling·check:bash32-floor·check:cli-command-ids·check:cross-package-test-inputs·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check:watch-hint-literal·check-ci-filter-parity·check-cross-package-test-inputs·check:doc-formula-expressions— pluscheck:nul-bytes(mandatory on any edit) and a raw control-byte self-scan of both edited files.check:doc-formula-expressionsfirst exited 1 with PREREQUISITE NOT MET (@objectstack/lintnot built) — recorded as NOT MEASURED, not as a red; it is green afterturbo run build --filter=@objectstack/lint.Changeset: deliberately none,
skip-changesetinsteadRead off the gates rather than judged by feel.
check-empty-changeset.mjsnever requires a changeset — it only rejects newly-introduced empty ones. The enforcement ispr-automation.yml'schangeset-check, which counts added.changeset/*.mdand is path-agnostic, exempted only by theskip-changesetlabel.This PR changes a governance record and a tooling metadata file. Neither ships in any published package, so a changeset here would declare a release that does not exist, and an empty-frontmatter one is exactly what
check-empty-changesetrejects.skip-changesetis the correct declaration.packages/rest/src/error-response.ts:377-378carries the same "are the ONE definition of both spellings" claim in its module header. It is not touched here, to keep this PR inside the dispatched scope. It is already narrower than the two texts fixed above (it says "and the three dispatcher exits read them", so it quantifies over exits rather than over all boundaries), but it still states "ONE definition" absolutely. Flagged for the PM to rule on whether it wants the same scoping.Generated by Claude Code