Skip to content

docs(adr): ADR-0129 records the enforced object-naming contract; decision letters for the letter-cited ADRs - #13067

Merged
os-elon merged 3 commits into
mainfrom
claude/issue-12786-adr-decision-letters
Aug 29, 2026
Merged

docs(adr): ADR-0129 records the enforced object-naming contract; decision letters for the letter-cited ADRs#13067
os-elon merged 3 commits into
mainfrom
claude/issue-12786-adr-decision-letters

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#12786
Fixes#12918

Ruled two-card convoy (2026-08-29, maintainer, live director session batch #1 — verbatim ruling on both cards: 「执行,批 #1 其他卡同意」).

Ruling on #12786 (option 1, scoped): "give decision-letter indices to the ADRs that live code cites by letter today — ADR-0014 (D2), ADR-0024 (D4, D5.2), ADR-0067 (D2 — its own amendment already names 'the D2 join'), ADR-0081 (D1). Not all 67: only cited records get letters. … Letters are recognition, never invention: each letter heading must name a decision the record already states. Where a clean mapping would distort a record's structure, that ADR takes the ruled fallback (option 2: respell those code citations to what the record does say) instead — decided per-record in the PR, with reasoning shown."

Ruling on #12918 (option 甲, new ADR): "one page recording the enforced current-state naming contract — the object name IS the canonical id everywhere, the module prefix is embedded literally in the name, there is no separate namespace field … written with decision letters so citations verify. The same page reconciles ADR-0028: 0028's D1–D6 are a Deferred target model pointing the other way; until its recorded re-open trigger fires, the new ADR is the governing record."

Governed surface (docs/adr/**): this PR stays draft for the maintainer's pinned approval and hand merge. Clause-②: no — no schema shape moves; the diff is ADR pages, code comments, two rejection-message strings, and one test title.

Part A — ADR-0129 (new, next free number; tree max was 0128 and no open PR adds one)

docs/adr/0129-object-name-is-the-canonical-id.md records the enforced current-state object-naming contract, with letters:

LetterDecision it records (all already enforced)
D1The object name IS the canonical id everywhere (API, ObjectQL, REST, SDK, DB table); resolveTableName is a pass-through; no tableName field
D2The module prefix is embedded literally in the authored name; validateNamespacePrefix() (stack.zod.ts) + MetadataManager.publishPackage() from the single-source rule in kernel/namespace-prefix.ts
D3No separate namespace field — retired, rejected on the parse path with a tombstone prescription (UNKNOWN_KEY_GUIDANCE in object.zod.ts; silent strip closed by #4001); platform-wide (translation contract too)
D4Reconciliation with ADR-0028: its D1–D6 are the Deferred target model; until 0028's recorded re-open trigger fires, ADR-0129 is the governing record

ADR-0028 reconciliation line: 0028's Supersedes-line note previously read "there is no standalone ADR for that rule today" — this PR gives it the one-line factual correction the new record makes true (it now points at ADR-0129 and its governing-until-re-open rule). Nothing else in 0028 moves; its Deferred status and re-open trigger stand as written.

Part B — per-record letter dispositions

ADR-0067 — letters attached (clean recognition). The record numbers its decisions ordinally (headings "1."–"6.") and its own status line and 2026-08-06 Amendment already call Decision-2 "D2" ("the D2 join"). The six Decision headings are relabeled to D1–D6 at the same positions, same order, same titles:

LetterPre-existing decision it labels
D1A turn is a commit
D2Commits are atomic (the letter live code cites — 4 anchored files)
D3Rollback is first-class and append-only
D4The confirm gate, relocated
D5Metadata reverts cleanly; data is made reversible
D6Substrate and restore points

ADR-0014 — ruled fallback (respell). The record is the record form-field type; the two engine.ts citations claimed a service-principal audit-attribution decision the record nowhere states — a phantom number (no repo ADR letters that decision). Attaching a D2 would make the citation resolve while still pointing at an unrelated decision — the exact "swap the phantom for a lie" guardrail from #12913. Respelled both sites to the real contract: ExecutionContext.actor in @objectstack/spec kernel/execution-context.zod.ts.

ADR-0024 — ruled fallback (respell with the cloud qualifier). The record here is MCP connectors; the cited D4 (identity provenance) and D5.2 (break-glass last-admin invariant) are the cloud repo's enterprise-identity ADR-0024. Evidence already landed in this repo: the guard implementation itself (last-admin-guard.ts line 4), plugin index.ts, and both guard test headers spell it "cloud ADR-0024 D5.2", as do plugin-auth CHANGELOG entries. The four anchored-surface citations gain the same qualifier (the anchors gate's structural cross-repo escape, kept honest for readers); the adjacent bare ADR-0071 on the same edited auth-plugin line — also a cloud record (SCIM verification), unrelated to this repo's 0071 — is qualified in the same stroke. This session could not read the cloud repo (no access), so the D4 qualifier extends the identification the D5.2 spellings already established rather than a fresh verification — recorded on #9072, the standing card for the cloud-side check.

ADR-0081 — ruled fallback (respell). The record is the trusted React page tier; ADR-0105's citation note records that its "Decision section is numbered 1–4 and has no D-numbered decisions at all", and both ADR-0093 (D9 citation note) and ADR-0105 document that the "ADR-0081 D1/D2" labels come from a pre-repo decision record whose number collides with this repo's series. Minting a D1 in the React-page record would be pure invention. Respelled: the sys-member action-gate sites now cite ADR-0093 D9 (the repo record that restates exactly the leaned-on invariant — single-org sessions carry an active org); the auth-plugin bootstrap docblocks describe the pre-repo decision and point at ADR-0093 D9's citation note. Whether the pre-repo record is cloud ADR-0081 stays an open question owned by #9072 — this PR deliberately does not assert it.

Part C — surviving phantom spellings re-pointed to ADR-0129 D3

Changeset: one patch changeset for @objectstack/spec (published rejection-message strings changed; not breaking, no ADR-0087 disposition required — check:adr-0087-registration green).

Gate counts — the closure proof (all at head 9a7a7ec)

check:adr-anchors cannot-verify note, before (base e4902d2) vs after:

ReadingBeforeAfter
Unverifiable decision-letter citations360 (the note no longer prints)
Letter/file sites110
ADR ids affected50
Letter citations on the anchored surface that resolve886917
Decision numbers in the corpus126127

Verdict lines at 9a7a7ec: "check-adr-anchors --self-test: 106 assertions" (exit 0) and "check-adr-anchors: OK (52 anchored file(s) … 917 decision-letter citation(s) on the anchored surface name a decision the record makes)" (exit 0) — the target ("empty on the letter-cited surface") is met exactly.

Other gates run locally at 9a7a7ec (exit codes captured before any pipe): check:adr-links (575 link destinations resolve) · check:nul-bytes · spec check:generated ("All 14 generated artifacts are up to date" — includes check:docs, check:authorable-surface, check:api-surface, check:skill-refs, check:strictness-ledger, check:liveness, check:test-typecheck) · check:empty-state · check:variant-docs · check:i18n · check:i18n-stale-fill · check:type-check-coverage · check:type-check-debt --re-measure ("none above its recorded number") · check:engine-double-contract · check:where-matcher · check:query-options-erasure · check:dispatcher-error-vocabulary · check:cross-package-test-inputs · check:durability-log-level · check:merge-driver · check:doc-authoring · check:engine-split-ratio (after deepening the shallow clone as the gate prescribes) · plus the remaining dispatch-gates union — all exit 0. Not measurable in this container: scripts/pm/check-half-states.mjs (no GitHub credential — exit 3 "no reading", per its own text; CI runs it with credentials).

Tests at 9a7a7ec: spec object.test.ts — 185 passed; the renamed title runs and passes when selected by name ("1 passed | 184 skipped"); translation.test.ts — 118 passed.

Out of scope, recorded

The phantom-label families extend beyond the anchored surface (about 18 more live ADR-0014-D2 sites, about 26 more ADR-0024 letter sites including runtime error strings pinned by tests, about 15 more ADR-0081-D1 sites). They are outside the gate's population today, but any of those files gaining an anchor JSON re-arms the tripwire. Census recorded as a comment on #9072 (the standing pre-repo-label card) rather than a new issue — dedup search hit it directly.

Generated by Claude Code


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/objectql, @objectstack/platform-objects, @objectstack/plugin-auth, @objectstack/spec, touching 5 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/invitation-role-cap.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/contracts/auth-service.mdx(via AuthManager (symbol))
  • content/docs/kernel/services-checklist.mdx(via AuthManager (symbol))
  • content/docs/permissions/authentication.mdx(via AuthManager (symbol))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-auth/src/invitation-role-cap.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 130 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 6c6157af05b25b28be0b3a7e92ce03ef85a98653packageMentionDocs.

Which tree this was computed on

This run read content/docs from 57301f0d7786c12ad09a37386831412f8d88f996 — the merge of head 9a7a7ec79366f6f0b61b09f010ac8bae27c8bd0b into base 6c6157af05b25b28be0b3a7e92ce03ef85a98653, 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 57301f0d7786c12ad09a37386831412f8d88f996 && git checkout 57301f0d7786c12ad09a37386831412f8d88f996
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6c6157af05b25b28be0b3a7e92ce03ef85a98653 9a7a7ec79366f6f0b61b09f010ac8bae27c8bd0b && git checkout -B drift-repro 6c6157af05b25b28be0b3a7e92ce03ef85a98653 && git merge --no-ff 9a7a7ec79366f6f0b61b09f010ac8bae27c8bd0b
node scripts/docs-audit/affected-docs.mjs --json 6c6157af05b25b28be0b3a7e92ce03ef85a98653

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6c6157af05b25b28be0b3a7e92ce03ef85a98653 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-zhuang
os-zhuang marked this pull request as ready for review August 29, 2026 09:46
@os-zhuang
os-zhuang enabled auto-merge August 29, 2026 09:46
@os-zhuang
os-zhuang added this pull request to the merge queueAug 29, 2026
@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Aug 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33246299866 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL test/serve-publishes-bound-port.e2e.test.ts > #13062 the non-zero half — nothing an ordinary boot publishes may move > follows the DEV AUTO-SHIFT onto the port it really ↳ 失败原因: @objectstack/cli:test: Error: ENOENT: no such file or directory, open '/tmp/os-bound-port-home-B84noq/runtime.env_local.json'
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 15 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:dataprotocol:systemsize/mteststooling

Projects

None yet

3 participants

@os-elon@os-zhuang@claude