Repository navigation
docs(adr): spell the ADR-0005/0010 payload code tokens as the ledger members the runtime emits - #19765
Conversation
…dger members the runtime emits The wire-payload examples in ADR-0005 (sample 422) and ADR-0010 §5.3 printed `code` in lowercase, which is not a member of the published `ErrorCode` union (packages/spec/src/api/error-code-ledger.zod.ts). A matcher copied from them never fires. - 0005: invalid_metadata -> INVALID_METADATA (protocol.ts saveMetaItem spec-validation refusal) - 0010 L1: not_overridable -> NOT_OVERRIDABLE (protocol.ts / SysMetadataRepository.assertAllowed) - 0010 L2: package_locked -> ITEM_LOCKED (no PACKAGE_LOCKED exists; package-layer locks are refused as ITEM_LOCKED, per ADR-0010 §3.2's L2-into-L3 normalisation and the runtime's lockWriteRefusal / readOnlyBaseOverrideError with lockSource 'package') - 0010 L3: item_locked -> ITEM_LOCKED (protocol.ts lockWriteRefusal) - 0010 L4: frozen_path left unchanged: L4 is unimplemented and no producer emits any code for it Only the `code` token changes; the `error` openers and every other byte stay as they were. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgmentsRendered in-seat by the skills seat 1 session at the served tier the maintainer named on the previous date (the fable tier word at
② Semver levelNone — no release. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19706
维护者速读(草稿)
改了什么
两份 ADR 里的错误响应示例(ADR-0005 的 422 示例、ADR-0010 §5.3 的四层保护示例),把
code字段从小写改成运行时真正返回、且在已发布错误码台账里登记的大写码。共 5 处,改 4 处:invalid_metadata→INVALID_METADATAnot_overridable→NOT_OVERRIDABLEpackage_locked→ITEM_LOCKED——PACKAGE_LOCKED这个码,"包级锁"的拒绝一律返回ITEM_LOCKED(并带lockSource: 'package')item_locked→ITEM_LOCKEDfrozen_path保持不动 —— 路径冻结(L4)至今没有实现,运行时不会返回任何码,不能凭空造一个只动
code这一个值。error文本开头的[tag]、状态码、其它字段一个字节都没改([tag]那一半由另一张卡处理)。为什么改
错误码是客户端和 AI 写的代码用来判断"为什么失败"的依据,ADR 里的示例会被照抄。照抄小写码写出来的判断永远不会命中,因为运行时按 ADR-0112 返回的是大写码。没有任何门禁读 ADR 正文,所以这个错误不会报错,只会悄悄失效。
风险与代价(含回滚)
纯文档改动,不发布任何包,不影响运行时行为。需要您拍板的判断只有一处:L2 改成
ITEM_LOCKED。依据有三条:ADR-0010 §3.2 自己写明包级锁会被规范化成单项锁(L3)的形状;运行时两个拒绝点都返回ITEM_LOCKED;并且有测试钉住lockSource: 'package'的那个信封。如果您希望将来 L2 有独立错误码,那是给 spec 新增错误码的决定,本 PR 不预设。回滚:revert 这一个 commit。席位意见
建议批准并合并;L2 认可
ITEM_LOCKED。 四处改动逐一在运行时生产者处核实(protocol.ts的 spec 校验拒绝、NOT_OVERRIDABLE的两个拒绝点、lockWriteRefusal,以及包级锁的readOnlyBaseOverrideError—— 它返回的正是ITEM_LOCKED+lockSource: 'package',有测试钉住),账本成员逐一在error-code-ledger.zod.ts里读到;席位自己重跑成员核对:改前 0 / 5、改后 4 / 5。L2 不是简单改大写而是映射:树里没有PACKAGE_LOCKED,ADR-0010 §3.2 自己写明包级锁会规范化成单项锁的形状 —— 席内定 A(维持ITEM_LOCKED);若你希望包级锁有独立错误码,那是 spec 线的线协议决定,目前没有任何消费者需要它。L4 的frozen_path留空是量出来的:没有账本成员、也没有任何生产者(_frozenPaths在packages/**下零读者,Phase 2 评估器不存在),按「不编造」条款不动 —— 席内定 A(等 L4 实现时登记码并回改此行);如你想让 ADR 今天就诚实,B(加一句「L4 未实现」)便宜,属 spec 线。另有一条界外发现,席位身份过不了立卡门,请你定夺是否立卡:packages/cli/src/commands/meta/delete.ts:115 的--if-match帮助文本写409 metadata_conflict,而 CLI 实际返回METADATA_CONFLICT(非受管代码,不能搭本 PR)。复核记录:PR 评论 5787341220(PASS,在席按服务档渲染);ACCEPT 在卡 #19706。CI 读数:31 latest-per-name check runs — 22 success, 9 skipped, 0 in progress, 0 other。你要做的
package_locked→ITEM_LOCKED)是否认可。docs/adr/**属于 Tier H,需要您亲手合并,PR 保持 draft。frozen_path留给 spec 线决定:要么等 L4 实现时在台账登记错误码,要么修订 ADR-0010 标注 L4 尚未实现。不在本 PR 范围内。Clause-②: no
What this changes
The refusal-envelope examples in
docs/adr/0005-metadata-customization-overlay.md(sample 422) anddocs/adr/0010-metadata-protection-model.md§5.3 printedcodein lowercase. None of those tokens is a member of the publishedErrorCodeunion (packages/spec/src/api/error-code-ledger.zod.ts, ADR-0112), so a matcher copied from them never fires. Eachcodevalue now carries the ledger member that the runtime actually emits for that refusal, measured against both the ledger and the producer. Only the token changes: theerrorstring's bracketed opener (tracked separately on #19705), thestatusnumbers and every other byte in both ADRs are untouched. The persistedsys_metadata_audit.codecolumn keeps its lowercase spelling by ADR-0112 D6b. These payloads are wire envelopes, not audit rows, and ADR-0112 (:164) says the catalog governs "the code a failing request answers with".Per-site evidence (tree
dc1b98680, commit58bf48af1)0005:349invalid_metadataINVALID_METADATAerror-code-ledger.zod.ts:615metadata-protocol/src/protocol.ts:16099-16100:saveMetaItem's "failed spec validation" refusal,INVALID_METADATA/ 422 (the same sentence the sample prints)0010:480L1not_overridableNOT_OVERRIDABLE:623protocol.ts:15758-15759(saveMetaItem, 403);sys-metadata-repository.ts:1639,1652-1653(assertAllowed, 403)0010:483L2package_lockedITEM_LOCKED:617PACKAGE_LOCKEDexists; package-layer locks are refused asITEM_LOCKED/ 4030010:486L3item_lockedITEM_LOCKED:617protocol.ts:14144-14145(lockWriteRefusal),:14212-14213(assertLockAllowsDelete), 4030010:489L4frozen_pathL2: mapped, not uppercased
The ledger has no
PACKAGE_LOCKED, and no spelling ofpackage_lockedexists anywhere outside ADR-0010 itself (whole-treegit grep -i, exit 1). The runtime answers a package-layer lock withITEM_LOCKED, in two places:metadata-protocol/src/sys-metadata-repository.ts:1766-1768(readOnlyBaseOverrideError):code = 'ITEM_LOCKED',status = 403,lockSource = 'package'. Its TSDoc calls'package'"ADR-0010's own reserved value for a lock the PACKAGE layer asserts". It is pinned bysys-metadata-repository.package-writability.test.ts:767as{ code: 'ITEM_LOCKED', status: 403, lockSource: 'package', packageId }.protection.lockis translated byapplyProtection(spec/src/shared/protection.zod.ts:276-277,_lockSource = 'package') into the item's_lock, and refused bylockWriteRefusalasITEM_LOCKED/ 403. Thesetupapp the example names carriesprotection: { lock: 'full' }(platform-objects/src/apps/setup.app.ts:39-40).ADR-0010 §3.2 already specifies this normalisation: the loader writes the resolved lock "into the artifact's
_lockand_lockSource: 'package'fields, so downstream protocol code sees a single normalised shape (L3)". So the L2 token is established from the ledger, the producer and the ADR's own semantics section. It is flagged here because it is the one site where the new token is not the uppercase form of the old one.Unresolved site: L4
frozen_path(left unchanged)The runtime emits no code for a frozen-path refusal because no frozen-path refusal exists. There are zero readers of
_frozenPaths/defaultFrozenPathsunderpackages/**: the only non-ADR hit is a forward reference in the header comment ofspec/src/kernel/metadata-protection.zod.ts:10. The Phase 2 evaluatorpackages/spec/src/shared/json-path.tsdoes not exist, and ADR-0010 Phase 2 carries no "Implemented" mark. Replacing the token would invent a code, so the line stays as it was. The answer belongs to the spec lane: either register a code when L4 is implemented, or amend ADR-0010 to say L4 is unimplemented.Verification
Ledger membership (checklist item
api-backend.error-envelope-ledger), one-shot scratchpad script, not added to the tree: it importsErrorCodefrom the zod source, readsErrorCode.options(338 members), and checks every"code":value in the two ADRs. Control leg:NOT_OVERRIDABLEin the union,not_overridablenot.dc1b98680):VERDICT sites=5 members=0 non-members=5, exit 158bf48af1):VERDICT sites=5 members=4 non-members=1, exit 1. The remaining non-member is0010:489 "frozen_path", left unchanged on purpose.Gates:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat58bf48af1derived 19 commands. All 19 ran, exit codes captured before any pipe, and--ranreconciliation printed "19 derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED". Among them:node scripts/check-adr-links.mjs: exit 0, "690 relative link destination(s) under docs/adr/ resolve"node scripts/check-adr-symbol-anchors.mjs: exit 0, "2121 anchors across 140 records resolve"pnpm check:adr-anchors: exit 0, "check-adr-anchors: OK (53 anchored file(s) ...)"pnpm check:doc-authoring: exit 0, "no growth, no burn-down unrecorded"pnpm check:nul-bytes: exit 0, "scanned 9242 text file(s) ... no raw ASCII control bytes"pnpm --filter @objectstack/lint run check:doc-formula-expressions: first run exit 3 (prerequisite:@objectstack/lintunbuilt, nothing measured); built@objectstack/formula+@objectstack/lintunder the verify lock (VERDICT command-exit 0); re-run exit 0Additionally
pnpm check:error-code-casingexited 0. It is a neighbour, not a measurement of this diff: it scans onlypackages/**/*.ts(x)(SCAN_ROOTS = ['packages']) and never reads ADR prose.No changeset:
docs/adr/**publishes nothing. None of the 70 publishedpackage.jsonfiles[]entries resolves todocs/adr, the root package is private, and nopackage.jsonor tsup config referencesdocs/adr.Acceptance notes
"code"lines::97and:103(the layer diagram),:213,:501,:601-602. This PR's contract is the five payload sites only; the prose is untouched.metadataDefaults.lock=full, a"package"key where the runtime's package-layer refusal carriespackageId+lockSource). Only the token was in scope.--if-matchhelp text says409 metadata_conflict(packages/cli/src/commands/meta/delete.ts:115, pinned bydelete-reset-carriers.test.ts:367via/metadata_conflict/) while the CLI surfacesMETADATA_CONFLICT. That is non-governed code and does not ride this governed PR.Generated by Claude Code
Generated by Claude Code