Repository navigation
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
Conversation
…§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
📓 Docs Drift Check
What this run could not see
Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
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. |
|
domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd Contract reviewServed-tier: 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 ① Derived judgments
② Semver level
③ Boundary flagsEvery dev flag (report 6733853724
④ Design judgmentEvery 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
ADR-0048. The §5 bullet was checked clause by clause against 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 ADR-0130. One dated note, under D1, no rewrite (ruling 6729820123's ⛔ respected). The rule it states (own items → declared closure plus
Findings
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: VERDICT: PASS Seat adoption note: read before adopting. F1 is verified ( |
…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
|
domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd Contract reviewServed-tier: This is a DELTA record on the fix commit 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: VERDICT: PASS |
|
domain:spec#2 · zhuangjianguo · session_01QsR2jUe9c6YdtNkrzUnMUd 维护者速读
席位核对 · ACCEPT · 2026-11-09T07:24Z · head
|
…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
…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>
Requested by Steedos · project thread
Fixes #31685
Clause-②: no
Metadata-protocol: none
维护者速读
base/system/sys不受同命名空间唯一规则约束、扩展目标和 everyone/guest 锚点按无包调用方解析、无归属的引擎函数算平台持有者。ADR-0048 §5 的 lint 条目改成现状;ADR-0129、ADR-0130 加带日期的交叉引用。skip-changeset)。回滚即撤销这一个提交。pnpm check:adr-index && node scripts/check-adr-links.mjs,两条都应退出 0。docs/adr/**)。What changes
Docs only: four ADRs and one code comment. No code, schema, lint or skills change.
everyoneandguest(an epic-seat reading recorded in its E3 review, 6732998040 on draft PR feat(core, plugin-security)!: permission sets resolve by (package, name), and a package position's sets resolve from its package (ADR-0150 E3) #31815) resolve as a caller with no package.base,systemandsysare not judged by child N's co-owner rule (epic red-line review of N, 6728571147 on PR feat(objectql)!: refuse a (type, name) held twice inside one namespace #31717); a qualified address there answers409with the candidates, and G1 pins it.sys(R2 review 6729484996 item 2, refined by re-review 6730444942 item 1).[done]authoring-lint bullet is rewritten in place, as d5106ae did for the same bullet. It now describes whatnaming/namespace-prefixreports since child P (feat(spec,cli,skills)!: behavioural metadata names stay short, and spec declares the namespace address grammar (ADR-0150 child P) #31765): the duplicate finding (DUPLICATE_KEYED_TYPES), and the bare-name finding for capabilities and positions only (PREFIX_JUDGED_COLLECTIONS). Phase 3 of ruling C is withdrawn (record 6721941146 on Bare-name phase 3: defineStack and the save door refuse a bare package-authored name; ADR-0129 and ADR-0048 amended; migration of stored and external references (ruling C on #30798) #30806).packages/cli/src/utils/picklist-references.ts: the module comment at line 42 gains a dated note pointing at ADR-0150 D3 and the ADR-0130 note. Comment only.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
origin/main732def5, it is not: the sentence cites the rule for "an author shipping twopage/homein one package". That duplicate finding still ships (DUPLICATE_KEYED_TYPESinpackages/cli/src/commands/lint.ts), and ADR-0150 D9 keeps it. So §3.4 gets a dated note saying the sentence holds and pointing at §5, and only §5's bullet is rewritten. The seat agreed to this reading.picklist-references.ts:42leans on D1's co-ownership claim, so that is the sentence N1 means. The seat agreed to this placement.tool, corrected per contract review F1 (record 6734267031):tooljoined phase 1's list (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), andBARE_NAME_ONE_HOLDER_TYPES(packages/core/src/package-scoped-resolution.ts) lists 13 entries withtoollast. ADR-0150 now counts 14 covered types (the constant's 13 plus permission sets), 11 uncovered types withouttool, and 14 B children. The same fix commit (0a18e76) drops the letter from the N1 citation (F2), dates the ADR-0130 note's install-judge reading to 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 (F3), and scopes the sealed-clash count to E5's measurement (F5).Verification
Readings
Measured on commit bfc1d99 (base
origin/main732def5); after mergingorigin/mainat ca23ce3, none of the five files changed, andcheck:adr-index,check-adr-linksandcheck-adr-anchorswere re-run there: exit 0 each.dispatch-gates: 56 gates derived for this diff, 56 run, all exit 0.--ranreconcile: 0 NOT-MEASURED, 0 UNRUN.turbo run build --filter=./packages/**, VERDICT 0 (81/81 tasks).check:adr-index,check-adr-linksandcheck-adr-anchorswere re-run on it: exit 0 each.Human Minimum
packages/specholds no runtime logic. (No schema touched.)pnpm lintpasses. (No formatter ran. The repo-widepnpm lintscan is CI's run; the only source edit is a comment.)main, one pull request.skip-changeset.)packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01QsR2jUkMujHycV1pMLHL8h
Generated by Claude Code