Skip to content

docs(adr): ADR-0150 records its children's rulings; ADR-0048 §5, ADR-0129 and ADR-0130 point at it (ADR-0150 child A) - #31841

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-4jg54r
Nov 9, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-4jg54r

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #31685

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:只改文档(四份 ADR 和一处代码注释,不改代码)。ADR-0150 补记子卡上已裁定、或由专题席位记录的七条:闭包外的包被拒绝(含同一发布包里没声明依赖的兄弟包)、阶段一之前就重名的部署被拒绝、同对象同名动作被拒绝(落在动作收尾卡)、base/system/sys 不受同命名空间唯一规则约束、扩展目标和 everyone/guest 锚点按无包调用方解析、无归属的引擎函数算平台持有者。ADR-0048 §5 的 lint 条目改成现状;ADR-0129、ADR-0130 加带日期的交叉引用。
  • 为什么改:这些裁决都已在子卡上定下并有记录,但 ADR-0150 正文还写着旧说法(比如「阶段一覆盖的类型不会有两个持有者」「动作已被阶段一兜底」);P 合入后 ADR-0048 §5 还说 lint 对所有裸名告警、阶段三会改成拒绝。不改的话,后续子卡会照着过时的正文去做。
  • 风险与回滚:只改文字,无运行时风险,不发版(skip-changeset)。回滚即撤销这一个提交。
  • 先看:ADR-0150 D3 新增的 (c) 段和「Covered and uncovered types」段;ADR-0048 §5 第一个条目;ADR-0130 D1 下的注记。
  • 两分钟验证:运行 pnpm check:adr-index && node scripts/check-adr-links.mjs,两条都应退出 0。
  • 需要你做的:契约复审通过后,审阅并合并(Tier H,docs/adr/**)。

What changes

Docs only: four ADRs and one code comment. No code, schema, lint or skills change.

Why

Child A of epic #31527. The epic seat carried seven items into A's ADR-0150 text (comment 6729937614 on #31685), on top of the card's ADR-0048 and ADR-0129 items. Each item was checked against the record it cites before its sentence was written.

Tests

None: a docs-only change (skip-changeset). The one code edit is a comment, so there is no tested behaviour to ablate.

Acceptance notes

Verification

Readings

Measured on commit bfc1d99 (base origin/main 732def5); after merging origin/main at ca23ce3, none of the five files changed, and check:adr-index, check-adr-links and check-adr-anchors were re-run there: exit 0 each.

  • dispatch-gates: 56 gates derived for this diff, 56 run, all exit 0. --ran reconcile: 0 NOT-MEASURED, 0 UNRUN.
  • Prerequisite builds under the verify lock: spec + formula + lint prebuild, VERDICT 0; full turbo run build --filter=./packages/**, VERDICT 0 (81/81 tasks).
  • First pass: 6 gates exited 3 (prerequisites missing, not a measurement): the teardown self-test (pinned fixture commit 621a487 not fetched in a shallow clone), and dual-build-cjs-loads, i18n, i18n-coverage, i18n-walk-parity and lean-entry-closure (needed built packages). After fetching the commit and running the full build, all six exit 0.
  • Body gates with this body as the event payload: check-closing-keyword-parity, check-changeset-no-major, check:adr-0087-registration and check-partof-closing-keyword, exit 0 each.
  • Diff: 5 files, +106 / -26. The review-fix commit 0a18e76 touches only ADR-0150 and ADR-0130; check:adr-index, check-adr-links and check-adr-anchors were re-run on it: exit 0 each.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind. (No name added.)
  • 3. No formatter ran; pnpm lint passes. (No formatter ran. The repo-wide pnpm lint scan is CI's run; the only source edit is a comment.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Publishes nothing: skip-changeset.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jUkMujHycV1pMLHL8h


Generated by Claude Code

…§5 lint bullet, ADR-0129 and ADR-0130 cross-references

ADR-0048: §5's authoring-lint bullet is rewritten in place for what
naming/namespace-prefix reports since child P; a dated note under §3.4
says its duplicate sentence still holds. ADR-0129: a cross-reference to
ADR-0150 under D4. ADR-0130: one dated note under D1 (ruling N1). ADR-0150:
D2 shareable namespaces, D3 (c) and caller-less references, D4 owner-less
functions, TL;DR 7 and the execution plan's covered-types paragraph. The
picklist-references.ts comment points at ADR-0150 D3. No code changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUkMujHycV1pMLHL8h
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 9, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Nov 9, 2026
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/cli/src/utils/picklist-references.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/cli/src/utils/picklist-references.ts) — pages documenting those are invisible to this run
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 35 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 3487ccc12a6ad00dc75450f3f8b29a8a0630701d → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd

Contract-review-claim: session_01QsR2jUe9c6YdtNkrzUnMUd · head ca23ce3 · 2026-11-09T06:52Z

The seat runs the CONTRACT_REVIEW_TIER review on an isolated sub-agent (director seat off duty; maintainer rule 2026-11-08 「有 fable 车道就可以契约复审啊」). CI on this head: 36 success, 20 skipped, 0 failed.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ca23ce32cd53f905904f2faf772b6e89e9f0e4ce
Local-runs: none
Scope: docs/adr/0048-cross-package-metadata-collision.md, docs/adr/0129-object-name-is-the-canonical-id.md, docs/adr/0130-release-artifact-as-co-ownership-boundary.md, docs/adr/0150-behavioural-metadata-addressed-by-package-objects-keep-the-literal-prefix.md + packages/cli/src/utils/picklist-references.ts (the module comment the ADR-0130 note and ADR-0150 D3 (c) describe; comment-only hunk, lines 45–53 at head)

Inputs read: card #31685 body and its four comments (triage 6727733487, the epic seat's seven items 6729937614, the claim 6733414680, the dev report 6733853724); PR #31841 body, file list and git diff origin/main...pr-31841 (5 files, +104/−25; head = bfc1d99 merged with main ad622d3); the 55 check-runs on the head; and every record the seven items and the ADR sentences cite (6723462584, 6728487836, 6729820123, 6728478528, 6728613177, 6728571147, 6729484996, 6730444942, 6729831159, 6729825528, 6721941146, 6732998040). Cited code was read on live origin/main cc16008 (#31731 merged 06:11Z, after this head's merge base), never reviewed. Rendered on the isolated child session 9f9f0eb5-2885-550b-8927-ffc7a5154621; adopted and posted by the seat named below.

① Derived judgments

  • No accept-set change, no public-surface change. The diff is four ADR files plus one hunk inside the block comment of packages/cli/src/utils/picklist-references.ts (every changed line begins *; no token outside the comment moves). No schema, gate, lint rule, skill or export changes. Right.
  • Clause-②: no and Metadata-protocol: none. The diff changes no protocol and narrows nothing itself; the narrowings the ADR text records (D3 (c), the N1 sibling case, the sealed clash, the action pair, D2's shareable namespaces, D4's platform holder) were each ruled or read on their own card and are declared there by the PR that carries them. The ADR declares and implements nothing, as its own status line says. Right.
  • Check-runs on the head: every run that ran concluded success (Lint & Repo Gates, Lint · docs & code guards, Check Documentation Links, Check Changeset, Governed Surface Queue Guard, PR-body guards, Test Core 8/8, TypeScript Type Check, Dogfood Regression Gate, Live E2E UI, Build Core); the skipped ones are the UI/docs/opt-in jobs a docs-only diff does not trigger. Those conclusions are the gate verdicts; nothing was re-run.

② Semver level

  • Labels: documentation, size/m, skip-changeset; no .changeset/ file in the diff; Check Changeset success. Nothing publishes: ADR prose, and a comment in @objectstack/cli that changes no emitted code or declaration. skip-changeset matches what the diff publishes. Right.
  • Clause-②: line on the PR body reads no, and the claim (6733414680) reads no with Narrowing: no on the card. The diff takes no declared capability away at any consumer. Matches.

③ Boundary flags

Every dev flag (report 6733853724 deviations, out_of_scope_findings; PR body Acceptance notes) and the seat's two accepted deviations, judged against the tree:

  1. ADR-0048 §3.4 noted, not amended — holds. §3.4's lint sentence on main is "an author shipping two page/home in one package — stays covered by the naming/namespace-prefix lint". packages/cli/src/commands/lint.ts namespacePrefixIssues still emits that duplicate finding for DUPLICATE_KEYED_TYPES = apps, pages, dashboards, flows, actions (keyed objectName:name), reports, datasets, independent of the prefix, and ADR-0150 D9 says "the intra-package duplicate warning stays". The card's premise that §3.4 goes stale after P was wrong; the dated note is the right instrument. Accepted.
  2. ADR-0130 note under D1 — holds. ADR-0130 has no sentence on resolving a sibling's reference: the only "sibling" mentions on main are the ADR-0141 D5 install-gate line (16), the D4 entry-name line (310) and the 2026-10-25 addendum (775), none about resolution. The CLI comment cites ADR-0130 by number for D1's co-ownership claim, so D1 is the sentence N1 means. The ADR-0130 text is not rewritten, as ruling 6729820123 requires. Accepted.
  3. tool joined phase 1 but ADR-0150 still counts 12 and lists tool as uncovered — answered as finding F1 (should-fix) in ④. The dev left it "for the seat"; the seat cannot leave it, because the PR rewrites the very sentence that enumerates BARE_NAME_ONE_HOLDER_TYPES.
  4. ADR-0150 status still "Proposed"; ADR-0048's pointer "takes effect when ADR-0150 is accepted" — answered as finding F4 (nit). Not among the seven items; the maintainer's merge of docs(adr): ADR-0150 addresses behavioural metadata by (package, name); objects keep the literal prefix #31586 made the record Accepted and the ADR-0129 note added here says so, so the three lines disagree with each other. Follow-up card or the maintainer's edit at merge; no effect on any rule.
  5. E3 cited as an epic-seat reading, not a ruling — right. Record 6732998040 says "my reading, recorded"; item 4 of 6729937614 carried it into A; the ADR sentence labels it "an epic-seat reading" and Tier H leaves the wording to the maintainer. Accepted.
  6. ADR-0028's and ADR-0048's status-line pointers not duplicated — right (they landed with docs(adr): ADR-0150 addresses behavioural metadata by (package, name); objects keep the literal prefix #31586; the status line on main carries them).
  7. open_questions: [], security_findings: none — nothing to escalate.

④ Design judgment

Every sentence the PR adds was read against the record it cites and against the live code it names.

ADR-0150 (the governing record; first consumers: R's resolver packages/core/src/package-scoped-resolution.ts, N's packages/objectql/src/namespace-co-owner-names.ts, E6's registry.ts picklist judge).

  • Status line, Amended entry: lists exactly the sections the diff touches (D2, D3, D4, TL;DR 7, releasability, covered types). Holds.
  • TL;DR 7, three new ruled bullets: step 3 (6723462584 "otherwise refuse with the candidate list"; 6728487836 "step 3 refuses it as outside-closure"), the sealed clash (6729831159 "refusal naming both packages (409 on the doors)"), the action pair (6729825528 "409 RESOURCE_CONFLICT naming both packages ... lands in THIS card (the action family's B card)"). Each matches its record. "Where measured first-party" is the right hedge: the action pair's count is E2's and may be non-zero per its record. Holds.
  • D2, shareable namespaces: 6728571147 item 2 rules base/system/sys not judged, G1 pins the 409. The added clause "as the ADR-0048 §3.2 namespace gate does" is not in the record but is true in the tree: SHAREABLE_NAMESPACES = {base, system, sys} in namespace-co-owner-names.ts mirrors isShareableNamespace() of the install gate (RESERVED_NAMESPACES ∪ sys, ADR-0048 §3.2), pinned by registry-namespace-co-owner-names.test.ts. Holds.
  • D3 (c): 6728487836 verbatim in substance ("Today getItem's first-composite fallback serves Q's item" — registry.ts getItem step 3 "first composite match (legacy first-registered-wins fallback)"; "each E and G PR declares it in its Narrowing: line, next to D3 (a)"). Holds. The core resolver's step 3 returns outside-closure naming every holder, as the sentence says.
  • D3 (c) sibling sub-bullet: substance matches 6729820123 (declared closure only; author's fix is one dependencies line; severity: kernel:ready audit warns naming candidates, read serves no options and the validator refuses, post-seal install judge refuses; D10 report lists each). On live main the install judge indeed goes through R with the pending manifests (withPendingManifests → picklistReferenceVerdict), so "the post-seal install judge refuses" is what the code does. Finding F2 (should-fix) on the citation label — below.
  • D3, caller-less references: extension target per 6728613177 ("caller-less (option X) ... the unique holder in the environment, else a refusal naming every holder"; "Fields resolve by referrer") — holds, and registry.ts findOrphanPicklistExtensions judges the target with callerPackageId = null. Audience anchors per 6732998040 ("everyone and guest read caller-less (my reading, recorded)") — holds, correctly labelled a reading.
  • D4, owner-less engine function: 6729484996 item 2 and 6730444942 item 1, sentence by sentence against resolvePackageScopedHolders: package caller → step 1 own (holders.includes(callerPackageId)), step 2 unique among reachable plus platformItem, two or more → ambiguous-in-closure naming each (lines 568–582); caller with no package → environmentItem first, then the platform as one more holder: alone it answers, beside any package holder ambiguous-in-environment naming each (lines 520–529). Holds exactly.
  • Execution plan, releasability: now names D3 (a) and (c), as 6728487836 required ("the plan's 'except D3 (a)' sentence names it"). Holds.
  • Execution plan, covered types: the sealed clash and the action pair match 6729831159 and 6729825528; "the object-less action (ADR-0048 P1.7)" is right (P1.7 covers the object-less action; §3.4's 2026-11-06 note keeps the object-bound action out). The narrowing of "no two package holders ... exist while its refusal stands" to the two declared cases is what item 2 asked. Finding F1 (should-fix) on the type list — below.

ADR-0048. The §5 bullet was checked clause by clause against lint.ts: two warning-only findings (severity: 'warning', "⛔ It is not promoted to an error here"); duplicate finding over DUPLICATE_KEYED_TYPES asking "rename one" / a distinct objectName, never a prefix; bare-name finding only over PREFIX_JUDGED_COLLECTIONS = capabilities, positions; judged only when manifest.namespace is a non-empty string; isOverrideDeclaration exempt; spellings per carriesPackageNamespace (the namespace itself, NS_, dotted NS., sys_); guest_portal no longer reaches the predicate because permissions is not a judged collection (the PLATFORM_REQUESTED_BARE_NAMES constant survives for R's PLATFORM_REQUESTED_BARE_NAMES_BY_TYPE mirror, which is consistent with "no longer judged at all"); phase 3 withdrawn per 6721941146 "The name rewrite is withdrawn ... S1 ... do not ship". Every sentence holds. The §3.4 note holds (③.1). The status-line entry summarises the same. No finding.

ADR-0129. Status cross-reference and D4 note: "supersedes ADR-0028 D1 and D3 in part, for buckets 2–4" is ADR-0150's own status line; bucket 1's members (objects, fields, object extensions, views, capabilities, docs, api path) are the ruled table's bucket 1 (6723462584) and ADR-0150 D1's row; "the name is the table name, read by consumers the platform does not control" is ADR-0129 D1 ("the table name always equals name") and ADR-0150 D1/Alternatives; phase 3's planned amendment of D2 withdrawn per 6721941146 §"What it supersedes" ("Ruling C's literal-prefix requirement for behavioural types ... moot"). "Nothing in D1–D3 changes" is true of the diff. No finding.

ADR-0130. One dated note, under D1, no rewrite (ruling 6729820123's ⛔ respected). The rule it states (own items → declared closure plus sys → refusal naming every candidate; sibling only when declared; fix is one dependencies entry) is the ruling and R's code. "D1's co-ownership claim, and the install gate it corrects, are unchanged" is true: the next paragraph on main is the install-gate correction. Finding F3 (nit) on tense — below.

packages/cli/src/utils/picklist-references.ts (implementing file, comment). The note says what the ruling says and admits what the code does: "The code below still resolves against every body's picklists until the CLI reader is wired to that rule" — true at head and on live main (the file's last change is 788715e; #31731 touched only its boot-parity test). The comment agrees with the contract and does not overclaim. No finding.

Findings

  • F1 — should-fix — ADR-0150 "Covered and uncovered types" misstates the constant it cites. The rewritten sentence says BARE_NAME_ONE_HOLDER_TYPES is "12 plus permission sets" and enumerates it without tool; the next rewritten paragraph says "The other 12 bucket 2/3 types have no backstop: ... and tool". On main, packages/core/src/package-scoped-resolution.ts lines 103–122 list 13 entries, tool last ("joined after the AI family, in its own change (objectql: two packages' tools under one name, /meta/tool/NAME silently serves the first-registered one; measure cloud's by-name tool read, then decide whether tool joins the bare-name one-holder list #31414)"), ADR-0048 P1.10 (2026-11-08) records it, and N's own namespace-co-owner-names.ts says "tool is in both lists once phase 1 takes it". So tool has the phase-1 backstop and the contract says it has none; E8's row and the B-children count (13, line 348) rest on the wrong figure. The dev saw it (PR body "Not changed, noted for the seat") and rewrote the sentence anyway. The record is the contract: a sentence this PR writes may not contradict the constant it cites. Fix: name 13 with tool (P1.10) in the covered list, drop tool from the "no backstop" list (11 remain), and either adjust "13 in all" at line 348 or leave it with a pointer; no ruling is needed, the maintainer approves the wording at merge as Tier H.
  • F2 — should-fix — the letter "ruled A" in the D3 (c) sibling sub-bullet points at the opposite option. Record 6729820123 rules N1 "by meaning, not letter" because the two analyses used opposite letters: "letter B (epic seat's table 6728478528; = letter A of server seat 2's table)". In the epic seat's own table, A = "the closure includes the sibling in the same artifact" (widen), B = "refuse by declaration" — and 6728487836, cited two lines above in (c), says "If the ruling widens the closure (option A there), the widening lands in R2". A reader following the ADR's own citations reads "N1, ruled A" as the widening. The sentence's substance is right; the label is wrong. Fix: delete the letter ("N1, ruled 2026-11-09: declared closure only, record 6729820123"). The TL;DR 7 bullet and the ADR-0130 note already cite it without a letter.
  • F3 — nit — present tense for the install judge in the ADR-0130 note. "The CLI's picklist-reference check and the post-seal install judge read the paragraph above as letting a package resolve a list a sibling ... owns" is true of the CLI (picklist-references.ts) and was true of the install judge until fix(objectql)!: a field's picklist resolves from the package that declares it; the api index keeps one item per package and name #31731 merged (06:11Z, after this head's merge base ad622d3); on live main the install judge already judges a sibling through R (withPendingManifests, picklistReferenceVerdict). The note's rule sentence is right and the note is dated, so nothing in the contract is affected; "read" → "read, until E6 (fix(objectql)!: a field's picklist resolves from the package that declares it; the api index keeps one item per package and name #31731)" would be exact. Accepted deviation or the next ADR touch.
  • F4 — nit — three status lines disagree about ADR-0150's status. ADR-0150 reads "Proposed (2026-11-08) ... becomes Accepted by the maintainer's merge"; ADR-0048's pointer reads "takes effect when ADR-0150 is accepted"; the new ADR-0129 note reads "accepted by the maintainer's merge of docs(adr): ADR-0150 addresses behavioural metadata by (package, name); objects keep the literal prefix #31586". The merge happened (2026-11-08T21:16Z). No rule depends on the word. Follow-up card, or the maintainer's one-word edit at merge.
  • F5 — nit — "(first-party 0, HotCRM 6961ab1)" in the sealed-clash bullet generalises one measurement. Record 6729831159 gives 0 for E5's webhooks on HotCRM 6961ab1 and tells E1 to declare its own; the parenthetical reads as every E child's count. Harmless (every E PR must state its own number on Narrowing: anyway); "E5 measured 0" would be exact.

Nits F3–F5 alone would end the round; F1 and F2 are should-fix, so their fix earns a delta review on the new head (the two sentences only; everything else in this record carries over unchanged if the diff stays within them).

Implemented-by: claude/project-thread-4jg54r
Reviewed-by: session_01QsR2jUe9c6YdtNkrzUnMUd

VERDICT: PASS

Seat adoption note: read before adopting. F1 is verified (package-scoped-resolution.ts lists 13 entries ending in tool; ADR L291 says "12" and L296 lists tool as uncovered). F2 is verified (L121 "N1, ruled A"). Adopted as rendered. F1 and F2 go to the dev for a fix in place. F3 and F5 are prose and ride the same push. F4 is left for the maintainer's status-line edit at merge. A delta review on the new head covers those sentences only.

…contract review

ADR-0150 now counts tool among phase 1's covered types (14 with permission
sets; 11 uncovered; 14 B children), cites N1 by meaning instead of a letter,
and scopes the sealed-clash count to E5's measurement. ADR-0130's note says
the install judge read the allowance only until E6.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUkMujHycV1pMLHL8h
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0a18e76827fa88b30301582267e8dee5df499d97
Local-runs: none
Scope: docs/adr/0048-cross-package-metadata-collision.md, docs/adr/0129-object-name-is-the-canonical-id.md, docs/adr/0130-release-artifact-as-co-ownership-boundary.md, docs/adr/0150-behavioural-metadata-addressed-by-package-objects-keep-the-literal-prefix.md + packages/cli/src/utils/picklist-references.ts (unchanged since the first record)

This is a DELTA record on the fix commit 0a18e768 ("docs(adr): fix ADR-0150's phase-1 type count and N1 citation per the contract review"), one commit on ca23ce32cd53f905904f2faf772b6e89e9f0e4ce, whose record this one carries forward. Read: git diff ca23ce32 pr-31841 (2 files, ADR-0150 and ADR-0130 only, +7/−6), the epic #31527 body's children table, the filed ADR-0150 child B cards, and ADR-0048 P1.10 on main. Everything the first record judged outside these hunks stands unchanged. Check-runs on 0a18e768 at review time: Governed Surface Queue Guard success; Lint, Type Check, docs-links, PR-size and PR-body guards still in_progress. Their conclusions are the gate verdicts; landing waits on them, and this record says nothing a green run would not.

Findings of the first record, and their status

What the fix introduces

Two nits (N1, N2) and the standing nit F4 remain; no must-fix, no should-fix. Nits alone end the round.

Implemented-by: claude/project-thread-4jg54r
Reviewed-by: session_01QsR2jUe9c6YdtNkrzUnMUd

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd

维护者速读

  • 改了什么:只改文档,即四份 ADR 和一处代码注释,没有代码改动。ADR-0150 补记了子卡上已裁定、或由专题席位记录在案的几条规则:

    • 闭包外的包按裸名解析时被拒绝,同一发布包里没声明依赖的兄弟包也一样;
    • 阶段一之前就已重名的部署,以及同一对象上的同名动作,仍会留下两个持有者;
    • base/system/sys 不受同命名空间唯一规则约束;
    • 扩展目标和 everyone/guest 锚点按无包调用方解析;
    • 无归属的引擎函数算平台持有者。

    另外三处:「阶段一覆盖类型」更正为 14 个(含 tool);ADR-0048 §5 的 lint 条目改成 P 合入后的现状;ADR-0129 和 ADR-0130 各加一条带日期的交叉引用。

  • 为什么改:这些裁决都已在子卡上定下并有记录,但 ADR-0150 正文还写着旧说法;P 合入后,ADR-0048 §5 也还写着 lint 对所有裸名告警。不改的话,后续子卡会照着过时的正文去做。

  • 风险与回滚:只改文字,没有运行时风险,也不发版(skip-changeset)。回滚就是 revert 本 PR。

  • 先看:ADR-0150 D3 新增的 (c) 段,以及「Covered and uncovered types」段;然后看 ADR-0048 §5 的第一个条目、ADR-0130 D1 下的注记。

  • 两分钟验证:运行 pnpm check:adr-index && node scripts/check-adr-links.mjs,两条命令都应退出 0。

  • 需要你做的:批准本 PR(Tier H,改了 docs/adr/** 四个文件);批准后由本席落地。契约复核 PASS 在案:评论 6734337758,Head-sha: 0a18e76827fa88b30301582267e8dee5df499d97(这是增量记录,承接 ca23ce3 上的首份记录 6734267031)。

  • 席位意见:建议合。CI 全绿;契约复核的两条应改项(工具类型计数、删掉「ruled A」字样)已在 0a18e76 改好,增量复核通过。剩下的三条 nit 都是计数标签和状态行(N1 B1…B13 应为 B1…B14,N2 两处「10」应为 9,F4 状态行仍写 Proposed),不影响任何规则,可在合并时顺手改一个词,或留到下次碰 ADR 时一起改。

席位核对 · ACCEPT · 2026-11-09T07:24Z · head 0a18e76827fa88b30301582267e8dee5df499d97

Seat domain:spec#2 reviewed the diff of its own dispatch: claim 6733414680 on #31685, card thread session_01QsR2jUkMujHycV1pMLHL8h, report 6733853724. Checked against GitHub, not against the report.

  1. Shape
    • 5 files, +106 / -26 (132 lines, under the 3000 threshold).
    • Draft, base main, and the body opens with Fixes #31685.
    • Four ADRs change: 0048, 0129, 0130 and 0150. One comment-only hunk is in packages/cli/src/utils/picklist-references.ts.
    • No changeset (docs only).
    • Every file is inside the claim. git merge-tree against origin/main is clean.
  2. Premise
    • It held at claim time: ADR-0150 still carried the pre-ruling sentences, and ADR-0048 §5 still described the pre-P lint.
    • The dev verified all seven epic-seat items against their cited records at step 0. The contract reviewer re-read each record and confirmed it.
  3. Gates and check-runs
    • On this head: 55 runs, 36 success, 19 skipped, 0 red.
    • check-expected-skips --pr 31841: OK, every skip is in main's roster (exit 0).
  4. Accepted deviations
    • The contract review's should-fix items F1 (tool among the covered types; uncovered list 11) and F2 (no "ruled A" letter) are fixed on 0a18e768.
    • Nits F3 and F5 are fixed. F4 (the ADR-0150 Status line still reads Proposed), plus delta nits N1 (L343 B1…B13 vs "14 in all") and N2 (L300/L302 "other 10" → 9), are left as the review allowed: the maintainer can make the one-word edit at merge, or they ride the next ADR touch. No rule rests on any of them.
    • The PR body's "What changes" still writes "N1, ruled A". The body is not the record, and the ADR text is right.
  5. Out of scope and Skills-docs
  6. Landing
    • check-governed-merges --pr 31841: governed, Tier H (docs/adr/** ×4).
    • Clause-②: no, Metadata-protocol: none.
    • Contract review at CONTRACT_REVIEW_TIER: PASS. The first record is 6734267031 on ca23ce32; the delta record is 6734337758 on this head. Both were written by an isolated Fable reviewer and adopted by this seat, under the maintainer's 2026-11-08 16:12Z rule.
    • Next step: the maintainer merges by hand, or an authorized GOVERNED_APPROVERS approval lets this seat land it (ready, then auto-merge).
    • On merge, Fixes closes docs/adr: ADR-0048 addendum (§3.4 and §5 lint text) and ADR-0129 cross-reference for ADR-0150 (ADR-0150 child A) #31685, and this seat posts the landing note.

@objectstack-fleet objectstack-fleet Bot added the pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue label Nov 9, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review November 9, 2026 09:12
@os-zhuang
os-zhuang enabled auto-merge November 9, 2026 09:12
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 8079fa9 Nov 9, 2026
61 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-4jg54r branch November 9, 2026 09:32
@objectstack-fleet objectstack-fleet Bot removed the pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue label Nov 9, 2026
os-steve pushed a commit that referenced this pull request Nov 9, 2026
…header

The door now resolves through ADR-0150 D3's rule (this PR), so the dated
note saying the code still resolves against every body's picklists
contradicts the code below it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo
rajeev-kumar-se pushed a commit to rajeev-kumar-se/objectstack that referenced this pull request Nov 10, 2026
…klist without a declared dependency (objectstack-ai#31864)

<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi97hkDTw4QcCCcbBHrZv1dPd)_

Fixes objectstack-ai#31832

Clause-②: no (narrowing)
Metadata-protocol: none
Narrowing: yes. Measured: on base `cc16008a` the door accepts a field
naming a sibling package's list with no declared dependency (the new
refusal pin is RED there, see Verification); on head it refuses it as
`picklist-reference-unknown`. The runtime already refuses the same
reference since E6 (objectstack-ai#31731): the boot warns and serves no options, the
post-seal install judge refuses. Narrowing face: `os validate` / `os
build` exit 1 for a `packages[]` artifact whose field `picklist` is held
only by packages outside the declaring package's declared dependency
closure (plus `sys`), when that package declares no dependency outside
the artifact. Two further refusals, both already refused by the boot
(`INVALID_METADATA`, quoting the same sentence) and refused here
whatever the outside dependencies: (a) a field whose package declares
dependencies on two packages of the artifact that both declare the list
(`ambiguous-in-closure`); (b) a `picklistExtensions` target two packages
of the artifact declare (`ambiguous-in-environment`). Both were accepted
on base. First-party count: 0 (no `examples/**` app and no platform
package declares a picklist; E6 measured the same).
Skills-docs: none. No published skill teaches a field naming another
package's picklist; `skills/objectstack-data/SKILL.md` mentions picklist
only for `fieldPatches`.

## 维护者速读

- 改了什么:`os validate` / `os build` 现在按运行时的规则解析字段的
`picklist`:先找声明该字段的包自己的列表,再找它声明的依赖闭包(加
`sys`)。同一发布包里兄弟包的列表,如果没有声明依赖,会被拒绝,提示句与运行时一字不差,并给出要加的那一行 `dependencies`。
- 为什么改:E6 之后,运行时对这种引用会告警、字段没有选项、安装被拒;可作者期的门仍然放行,作者直到安装才发现。现在 `os
validate` 当场说清楚。
- 风险与回滚:收窄。多包制品里依赖兄弟包列表却没声明依赖的字段,从“校验通过”变成“校验失败”;运行时早已拒绝它,所以只是提前报错。第一方计数
0。回滚即 revert 本 PR,无数据迁移。
- 先看:`packages/cli/src/utils/picklist-references.ts` 的
`referenceResolver`(直接调用 `resolvePackageScopedHolders` +
`packageResolutionClosure`,没有第二套闭包遍历)和
`refusedReferenceHint`;`picklist-references.boot-parity.test.ts` 末尾新
describe(门的句子等于启动告警的句子)。
- 两分钟验证:`pnpm --filter @objectstack/cli exec vitest run
src/utils/picklist-references.test.ts
src/utils/picklist-references.boot-parity.test.ts`,预期 53 passed;其中 “a
field naming a sibling package's list (ADR-0150 D3)” 一组在 main 上是红的。
- 需要你做的:无,席位落地(落地前席位做 CONTRACT_REVIEW_TIER 合同审查并告知 epic 席位)。

## What changes

1. **The door resolves a reference with the runtime's rule, not a copy
of it.** `judgePicklistReferences` builds the holders of each list from
the bodies that declare it (`packageScopedHolderSource`), and asks
`resolvePackageScopedHolders` (`@objectstack/core`) with the closure
from `packageResolutionClosure` (`@objectstack/metadata-core`), over a
registry view of the artifact (each body answers `getPackage` for its
own id). Those are the calls the registry's `picklistReferenceVerdict`
makes, so no second closure walk exists. Both packages were already
direct dependencies of `@objectstack/cli`.
2. **A field's `picklist` resolves from the package that declares the
field**: own list, then the unique holder in its declared dependency
closure plus `sys`, else R's refusal. A refusal is
`picklist-reference-unknown` with R's message verbatim (it names every
candidate package and says to declare the dependency), and a hint
carrying the exact entry, for example `'com.example.core': '^1.0.0'`
added to the `dependencies` of the declaring package, plus the
consequence (no options, values refused). No new rule id, error code or
export.
3. **The outside-dependency arm softens only what an outside package
could fix.** When the declaring package also depends on a package the
artifact does not carry, a name nothing here holds, or one held only
outside the closure (`outside-closure`), is reported as
`picklist-reference-unverified` (info, never gating), because that
dependency could supply the list. Two holders one reference reaches
(`ambiguous-in-closure`, `ambiguous-in-environment`) are refused even
then: an outside package can add a holder, never remove one (contract
review F1).
4. **A `picklistExtensions` entry's `extend` resolves with no caller**
(epic-seat ruling X on the E6 card; the registry does the same): any
single holder in the stack, so an extension of a sibling's list still
needs no dependency (pinned as a CONTROL), and a target two packages
hold is refused, naming both.
5. **A stack with no `packages[]` is unchanged**: it is one package,
every list it declares is its own.
6. **`os explain picklist-reference-unverified` follows the rule**
(`packages/lint/src/rule-explanations.ts`, added to the surface by the
seat's claim amendment 6735317750): its first paragraph now says how a
field's `picklist` resolves (declaring package, declared dependencies
transitively, plus `sys`; a sibling counts only when declared; an
extension against the one package that holds the list; two holders
refused), and its fix paragraph adds declaring the dependency.
7. **The closure notice is one short verdict line.** When the declaring
package also has an outside dependency, the notice names the holders
(`FIELD: 'LIST' is only in 'PKG', undeclared — not judged: 'DEP' may
hold it`) instead of quoting R's whole sentence; pinned at most 200
characters with realistic package and field names, and its hint names
the dependency to declare. The refusal keeps R's sentence verbatim.
8. The module header's ADR-0130 paragraph is rewritten to the ruled
rule, citing ADR-0150 D3 and ruling 6729820123, worded after child A's
ADR-0130 note in PR objectstack-ai#31841. The text-face heading over the reference
refusals now names the closure case too.

## Tests

- `picklist-references.test.ts`, new describe "a field naming a sibling
package's list (ADR-0150 D3)", 12 cases: refused with R's sentence
(`packageScopedRefusalMessage`) and the dependency hint; two undeclared
sibling holders both named; reported, not refused, when the declaring
package has an outside dependency (hint names the dependency); that
notice at most 200 characters with realistic names; two declared sibling
holders refused `ambiguous-in-closure`, with and without an outside
dependency; an `objectExtensions` field judged from the extending
package; CONTROL declared dependency; CONTROL transitive dependency;
CONTROL own list; CONTROL `sys` list with no dependency; CONTROL sibling
extension with no dependency; an extension target two packages hold
refused `ambiguous-in-environment`, with and without an outside
dependency.
- `picklist-references.boot-parity.test.ts`, new describe, 4 cases on a
real kernel: the door's refusal message equals the boot's
`findOutsideClosurePicklistReferences()` refusal for the same field;
CONTROL with the dependency declared, the door accepts and the boot
reports nothing; a two-holder field and a two-holder extension target,
each refused by the door and by the boot (`INVALID_METADATA`, the boot's
message quoting the door's sentence).
- **Forced test changes:** `picklistRefusalHeading` CONTROL pin (heading
text now names the closure case; renamed from "keep the heading they
always had"); the boot-parity file's objectstack-ai#29898 describe docblock (it
described the sibling allowance under ADR-0130). the first boot-parity
describe's CONTROL stack now declares the core body's dependency on the
app body, and asserts the door accepts the whole stack (before, it was a
stack this door now refuses, passing only because it filtered to
`picklist-residual-unreferenced`). No assertion of an existing case
changed otherwise.

## Acceptance notes

- PR objectstack-ai#31841 (child A, Tier H) adds a dated note to this file's module
header saying the code still resolves against every body's picklists.
objectstack-ai#31841 landed first (8079fa9), so this branch merged main (no
conflicts) and deleted that note in `661fbd90`; the file is again the
blob it was at `e32fe7b2`.
- File surface: `packages/cli/src/utils/picklist-references.ts`, its two
test files, root `.changeset/31832-*.md`, and
`packages/lint/src/rule-explanations.ts` (the
`picklist-reference-unverified` entry's text only, claim amendment
6735317750). No test pin on that text moved: `rule-explanations.test.ts`
and `packages/cli/test/explain-rule-id.test.ts` list the id only.

## Verification

<details><summary>RED on main, then green on head (e32fe7b)</summary>

Head implementation swapped for base `cc16008a`'s blob (`931bc8f2`),
tests from head, under the verify lock: 11 failed / 42 passed. Failing:
every new refusal and notice pin (sibling refusal, two undeclared
holders, outside-dependency notice, realistic-length notice, two
declared holders, `objectExtensions` field, two-holder extension), the
three refusal boot-parity pins, and the forced heading pin. Every new
CONTROL passed on main. Restored with `git checkout HEAD`, `git diff
HEAD` empty.

Head: Test Files 2 passed, Tests 53 passed.
</details>

<details><summary>Ablations (each through scripts/ablation-replace.mjs;
each restored: blob equals HEAD 0e54d2b, git diff HEAD empty)</summary>

- **Closure back to whole-stack resolution** (`closureOf` returns every
artifact package): 5 failed / 48 passed — sibling refusal,
outside-dependency notice, realistic-length notice, `objectExtensions`
field, boot-parity sentence. The two-holder pins stay green in the
expected direction: with every package reachable they are still refused
as ambiguous.
- **F1: an outside dependency softens every refusal again** (the refuse
branch tests only `judged.outside.length === 0`): 2 failed / 51 passed —
two declared holders with an outside dependency, two-holder extension
with an outside dependency.
- **F2: a two-holder verdict passes, as on base** (a
non-`outside-closure` refusal skipped like a resolution): 4 failed / 49
passed — both two-holder unit pins and both two-holder boot-parity pins.
</details>

<details><summary>Gates on head e32fe7b, rerun on 661fbd9</summary>

- After merging main (`ac8fede8`) and deleting the note, on `661fbd90`:
closure build, the two picklist suites (53 passed), cli typecheck and
cli unit (406 files, 5807 tests) exit 0; the same 69 derived families
all exit 0, `--ran` 69/69, 0 NOT-MEASURED, 0 UNRUN.

- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 69 families derived for the 5 changed
paths. All 69 ran on `e32fe7b2`, every one exit 0; `--ran`
reconciliation: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN. (Earlier:
`check-adr-0087-registration` was red on `fcf3a4b9` until `8f820082`
added the changeset's `not-required (no-migration-prescription)` marker;
63/63 green on `8f820082`, 69/69 on `688a9602`.)
- `pnpm --filter @objectstack/lint build`, `test` (197 files, 8365
tests) and `typecheck`: exit 0.
- `pnpm --filter @objectstack/cli build` and `typecheck` (tsc plus
`check:test-typecheck`): exit 0.
- `pnpm --filter @objectstack/cli exec vitest run --project unit`: 406
files, 5807 tests passed. The integration tier is declared to CI (no
spawn entry or integration file touched).
- eslint `--no-inline-config` over the changed source files: exit 0
(neither package has a `lint` script).
- Full turbo build of `packages/**` (on `fcf3a4b9`): exit 0.
- Full dogfood battery on `e32fe7b2`: exit 0; 441 files passed, 3
skipped; 3662 tests passed, 42 skipped; no failures.
</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo

---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmQYiGGwc3V7yGZ4iTo)_

<!-- signed: domain:server#4 · os-steve ·
session_01ADHkmP8JJo4zF2sjTBRwnX -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs/adr: ADR-0048 addendum (§3.4 and §5 lint text) and ADR-0129 cross-reference for ADR-0150 (ADR-0150 child A)

3 participants