Skip to content

Commit 67add13

Browse files
huangyiireneclaude
andauthored
docs(adr): spell the ADR-0005/0010 payload code tokens as the ledger members the runtime emits (#19765)
Fixes #19706 ## 维护者速读(草稿) ### 改了什么 两份 ADR 里的错误响应示例(ADR-0005 的 422 示例、ADR-0010 §5.3 的四层保护示例),把 `code` 字段从小写改成运行时真正返回、且在已发布错误码台账里登记的大写码。共 5 处,改 4 处: - ADR-0005:`invalid_metadata` → `INVALID_METADATA` - ADR-0010 L1:`not_overridable` → `NOT_OVERRIDABLE` - ADR-0010 L2:`package_locked` → `ITEM_LOCKED` —— ⚠️ 这一处不是简单改大写:运行时没有 `PACKAGE_LOCKED` 这个码,"包级锁"的拒绝一律返回 `ITEM_LOCKED`(并带 `lockSource: 'package'`) - ADR-0010 L3:`item_locked` → `ITEM_LOCKED` - ADR-0010 L4:`frozen_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。 ### 你要做的 1. 看 L2 那一处(`package_locked` → `ITEM_LOCKED`)是否认可。 2. 认可就批准并合并:`docs/adr/**` 属于 Tier H,需要您亲手合并,PR 保持 draft。 3. L4 的 `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) and `docs/adr/0010-metadata-protection-model.md` §5.3 printed `code` in lowercase. None of those tokens is a member of the published `ErrorCode` union (`packages/spec/src/api/error-code-ledger.zod.ts`, ADR-0112), so a matcher copied from them never fires. Each `code` value 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: the `error` string's bracketed opener (tracked separately on #19705), the `status` numbers and every other byte in both ADRs are untouched. The persisted `sys_metadata_audit.code` column 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`, commit `58bf48af1`) | Site | Before | After | Ledger member | Producer (the token the runtime emits) | |---|---|---|---|---| | `0005:349` | `invalid_metadata` | `INVALID_METADATA` | `error-code-ledger.zod.ts:615` | `metadata-protocol/src/protocol.ts:16099-16100`: `saveMetaItem`'s "failed spec validation" refusal, `INVALID_METADATA` / 422 (the same sentence the sample prints) | | `0010:480` L1 | `not_overridable` | `NOT_OVERRIDABLE` | `:623` | `protocol.ts:15758-15759` (`saveMetaItem`, 403); `sys-metadata-repository.ts:1639,1652-1653` (`assertAllowed`, 403) | | `0010:483` L2 | `package_locked` | `ITEM_LOCKED` | `:617` | see below: no `PACKAGE_LOCKED` exists; package-layer locks are refused as `ITEM_LOCKED` / 403 | | `0010:486` L3 | `item_locked` | `ITEM_LOCKED` | `:617` | `protocol.ts:14144-14145` (`lockWriteRefusal`), `:14212-14213` (`assertLockAllowsDelete`), 403 | | `0010:489` L4 | `frozen_path` | **unchanged** | none | none: see "Unresolved site" | ### L2: mapped, not uppercased The ledger has no `PACKAGE_LOCKED`, and no spelling of `package_locked` exists anywhere outside ADR-0010 itself (whole-tree `git grep -i`, exit 1). The runtime answers a package-layer lock with `ITEM_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 by `sys-metadata-repository.package-writability.test.ts:767` as `{ code: 'ITEM_LOCKED', status: 403, lockSource: 'package', packageId }`. - A package-declared `protection.lock` is translated by `applyProtection` (`spec/src/shared/protection.zod.ts:276-277`, `_lockSource = 'package'`) into the item's `_lock`, and refused by `lockWriteRefusal` as `ITEM_LOCKED` / 403. The `setup` app the example names carries `protection: { 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 `_lock` and `_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` / `defaultFrozenPaths` under `packages/**`: the only non-ADR hit is a forward reference in the header comment of `spec/src/kernel/metadata-protection.zod.ts:10`. The Phase 2 evaluator `packages/spec/src/shared/json-path.ts` does 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 imports `ErrorCode` from the zod source, reads `ErrorCode.options` (338 members), and checks every `"code":` value in the two ADRs. Control leg: `NOT_OVERRIDABLE` in the union, `not_overridable` not. - Before (`dc1b98680`): `VERDICT sites=5 members=0 non-members=5`, exit 1 - After (working tree = `58bf48af1`): `VERDICT sites=5 members=4 non-members=1`, exit 1. The remaining non-member is `0010:489 "frozen_path"`, left unchanged on purpose. Gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `58bf48af1` derived 19 commands. All 19 ran, exit codes captured before any pipe, and `--ran` reconciliation 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/lint` unbuilt, nothing measured); built `@objectstack/formula` + `@objectstack/lint` under the verify lock (`VERDICT command-exit 0`); re-run exit 0 Additionally `pnpm check:error-code-casing` exited 0. It is a neighbour, not a measurement of this diff: it scans only `packages/**/*.ts(x)` (`SCAN_ROOTS = ['packages']`) and never reads ADR prose. No changeset: `docs/adr/**` publishes nothing. None of the 70 published `package.json` `files[]` entries resolves to `docs/adr`, the root package is private, and no `package.json` or tsup config references `docs/adr`. ## Acceptance notes - ADR-0010 also spells these codes in lowercase in **prose**, not in the payload `"code"` lines: `:97` and `:103` (the layer diagram), `:213`, `:501`, `:601-602`. This PR's contract is the five payload sites only; the prose is untouched. - The L2 payload's other fields still describe the unimplemented Phase 3 surface (`metadataDefaults.lock=full`, a `"package"` key where the runtime's package-layer refusal carries `packageId` + `lockSource`). Only the token was in scope. - The CLI `--if-match` help text says `409 metadata_conflict` (`packages/cli/src/commands/meta/delete.ts:115`, pinned by `delete-reset-carriers.test.ts:367` via `/metadata_conflict/`) while the CLI surfaces `METADATA_CONFLICT`. That is non-governed code and does not ride this governed PR. --- _Generated by [Claude Code](https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 595621d commit 67add13

2 files changed

Lines changed: 4 additions & 4 deletions

File tree

‎docs/adr/0005-metadata-customization-overlay.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ Sample 422 response:
346346
```json
347347
{
348348
"error": "[invalid_metadata] view/test_bad_view failed spec validation: columns: Invalid input",
349-
"code": "invalid_metadata"
349+
"code": "INVALID_METADATA"
350350
}
351351
```
352352

‎docs/adr/0010-metadata-protection-model.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,13 +477,13 @@ branching and a human-readable hint pointing at this ADR:
477477

478478
```jsonc
479479
// L1
480-
{ "error": "[not_overridable] ...", "code": "not_overridable", "status": 403 }
480+
{ "error": "[not_overridable] ...", "code": "NOT_OVERRIDABLE", "status": 403 }
481481
// L2
482482
{ "error": "[package_locked] App 'setup' belongs to package 'com.objectstack.setup' which sets metadataDefaults.lock=full.",
483-
"code": "package_locked", "status": 403, "package": "com.objectstack.setup" }
483+
"code": "ITEM_LOCKED", "status": 403, "package": "com.objectstack.setup" }
484484
// L3
485485
{ "error": "[item_locked] App 'setup' is locked (_lock=full). See ADR-0010 §3.3.",
486-
"code": "item_locked", "status": 403, "lock": "full" }
486+
"code": "ITEM_LOCKED", "status": 403, "lock": "full" }
487487
// L4
488488
{ "error": "[frozen_path] Path 'fields.id.type' is frozen on packaged object 'crm_account'.",
489489
"code": "frozen_path", "status": 422, "path": "fields.id.type" }

0 commit comments

Comments
 (0)