Skip to content

docs(adr): ADR-0141 — an app may depend on another installed app: declared in manifest.dependencies, checked once before install, co-owning the namespace it shares - #25717

Merged
os-zhuang merged 7 commits into
mainfrom
claude/project-thread-uu2cmp
Oct 25, 2026
Merged

os-zhuang merged 7 commits into
mainfrom
claude/project-thread-uu2cmp

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread
Fixes #25403

Clause-②: no
Metadata-protocol: none

Docs-only, Tier H (docs/adr/**): the PR stays draft and the maintainer merges it. Records the two maintainer rulings on #25403 — B′ (6369704387) and the namespace letter A (6377678758) — as a new ADR, and makes the three neighbouring records say the same thing.

维护者速读(草稿)

改了什么
新增 ADR-0141,把「应用 B 可以声明依赖应用 A」写成契约:声明沿用 manifest.dependencies,引用沿用保存门的依赖闭包;唯一新增的关口是安装前检查(依赖已装、版本在范围内,同一发布物内送达的依赖只看在不在,否则拒绝并说明缺什么,填上预留的 dependency_missing;检查支持的版本范围文法由运行时卡写明,作者侧 os lint / os validate 先于安装报出读不懂的范围);声明了依赖且命名空间相同的包,在安装门上与被依赖方共有命名空间(单向,门禁从任一端读取;同一被依赖方下的两个模块互为同门,不必互相声明),前提是主应用发布物里共用该命名空间的其余包也直接声明主应用(注册表安装后不记录包来自哪个发布物);开机重注册的两条路径各自按 manifest.dependencies 排序后再注册,对象名唯一检查跨发布物同样生效。卸载保护、升级拦截、拓扑/连带安装、被引用方发布标记、市场版本协商、安装后通过 PATCH 改动 dependencies 的判定,逐项写为推迟项;D8 的快照记录到数组转换另立一张卡;权限不变。另外三处配套:ADR-0028 D4 改为「未声明依赖不得跨应用引用」;ADR-0019 只加一行交叉引用(原文不改);ADR-0130 加附录,D8 的推迟改为「到市场发布时结束」,并标注它关于 #14488 的那一行已过时。

为什么改
你在 #25403 批了 B′ 和命名空间 A。HotCRM 想把 revenue 等模块拆成可以单独安装的包(「而每个包可以独立安装」),今天做不到:安装时没有任何地方检查依赖是否已装,单独发布的模块还会因为共用 crm 命名空间被安装门拒绝。这份 ADR 定下契约,运行时卡在它合并后再切。

风险与代价(含回滚)
只改文档,不动代码和 schema。业务上的代价写进了 ADR:卸载或升级被依赖的应用暂时不拦(推迟项),管理员可能把依赖方晾在那里;单独发布的模块和主应用各有版本号,ADR-0130 在同一发布物内消除的版本矩阵会在跨发布物时回来,只靠声明的版本范围约束。合并后的运行时卡按 S 估(裁决原估检查本身为 XS–S),另有作者侧规则和快照转换两张小卡。回滚:撤销本 PR 的合并即可,没有任何运行时状态。

席位意见

你要做的
读 ADR 里「Open questions for the maintainer's merge decision」十二条(第一至四条与第十一、十二条定机制:同一发布物内送达的依赖算已装且只看在不在;只在三个安装动作时检查,开机重注册和安装后的 PATCH 改动都不检查;命名空间共有关系由依赖方单向声明,门禁不论哪一方后注册都按这一条声明判定,否则装了模块之后 HotCRM 自己就再也升不了级;只认直接声明,外加「同一被依赖方下的模块互为同门」这一条,否则第二个模块就不能独立安装;安装后 PATCH 改 dependencies 暂不判定,这道门只到得了手工编写的包,且整体改写可以删掉作为共有凭据的那一条;开机重注册的两条路径各自用同一个排序器按 manifest.dependencies 排序后再注册,而不是让同门规则在父包未注册时放行。五至八条定拒绝的形状:422(同一道门上也有 409 的先例,取 422 是与注册表门禁一族一致);版本或范围解析不了即拒绝,与协议握手的「警告并放行」相反,并要求运行时卡写明支持的文法,树里三个读取器没有一个兼有完整文法与精确到补丁号的粒度,扩展握手那一族;每条声明都是必需、optional 投影为 false;只判本包自己的声明。第九条:D8 的记录到数组转换另立一张卡,先读控制面怎么解析快照。第十条:对 #14488 裁决的读法,「发布物整体安装」未变,「不需要单独安装一个模块」所否定的需求如今由另一个发布物满足,即该裁决自己点名的拆出路径)。另有一条 HotCRM 侧前提尚未核实:sales 发布物里其余 crm 包须直接声明 sales,否则模块安装和 HotCRM 升级都会在命名空间门上被拒。同意就合并,合并即接受;有异议就在 PR 里点出哪一条。

What changed

File Change
docs/adr/0141-inter-app-dependency-declared-checked-before-install.md New record, Proposed. D1 declaration and reference (no new mechanism) · D2 the pre-install check (what it judges — an installed record for presence and range, a co-delivered package for presence only; 422 + a ledger-registered code + one dependency_missing issue per unmet entry; the three install acts it runs at, the three re-registration paths and the manifest PATCH it does not; fail-closed on an unparseable range, the opposite of the handshake beside it, paired with a stated range grammar — none of the tree's three readers has both the documented grammar and exact-version granularity; the handshake family in metadata-core is extended as a sibling function, the handshake's verdicts unchanged — and an author-side os lint / os validate rule) · D3 the six deferred items, each with its interim state and trigger (the PATCH item noting the door reaches only authored packages and can delete D5's proof) · D4 permissions unchanged, record-level semantics an open section of three questions (incl. the ADR-0086 D4 own-namespace discriminator that D5 dissolves across artifacts) · D5 dependency-declared namespace co-ownership, one-way, read from either end (the gate refuses any sitting owner outside the co-owner set, so HotCRM could not otherwise be upgraded once a module is installed — and the free upgrade guard that brings), with a sibling arm (two modules under one declared parent that holds the namespace), the precondition that the app's own companion packages declare it, and the requirement that each hydrate path sorts its batch through the one sorter (the seals and the never-recovered refusal otherwise; ledger entries as nodes; a cycle degrades to source order, never throwing out of the hydrate), D3 uniqueness across co-owners and siblings, ADR-0130 D8's deferral now ending at marketplace publishing · D6 what ADR-0028/0019/0130 now say · D7 the first consumer (a HotCRM module shipping alone as type: 'app', revenue first) with three HotCRM-side preconditions, not re-verified · D8 the three dependency shapes; the record-to-array snapshot conversion split off as its own card · a merge-decision list of twelve drafter choices (1–4, 11 and 12 the mechanism, 5–8 the refusal's shape, 9 the D8 conversion as its own card, 10 the reading of ruling 5645322259)
docs/adr/0028-metadata-naming-and-namespace-isolation.md D4 rewritten to "no cross-app reference without a declared dependency", with a note quoting the original sentence; the two dependent sentences (D6, leak-point 3) and TL;DR item 4 follow; status line gains an Amended entry. The rest of the record stays Deferred
docs/adr/0019-app-as-consumer-unit.md One additive Related header line; text unchanged (ADR-0130 D2's recording convention)
docs/adr/0130-release-artifact-as-co-ownership-boundary.md One status-header entry and an addendum at the end: D8's deferral end point, and the 2026-09-02 addendum's #14488 line marked stale (that card's state is not_planned, ruling 5645322259). No original text edited. The addendum names the sibling arm beside the one-way relation, and quotes, byte-exact, ruling 5645322259's premise sentence, reading it in two halves: an artifact installs whole (unchanged, D2 stands); the need its second half denied is now met by another artifact, the carve-out path the ruling itself named; its disposition stands

Measurements (at origin/main 47af3dde42)

  • Dispatch assumption "nothing at install reads manifest.dependencies" — holds for checking, fails for ordering by one reader: resolveArtifactPackageEntries (packages/core/src/artifact-packages.ts) feeds every declared id to resolvePluginOrder as an optional edge and skips an id outside the artifact silently. No install door checks presence or version; dependency_missing occurs only in app-install.zod.ts, its test and content/docs/references/system/app-install.mdx — the only three tracked occurrences (zero producers). Recorded in the ADR's Context and Verification notes; no decision moves.
  • Dispatch assumption "the namespace refusal is NamespaceConflictError in packages/objectql/src/registry.ts" — holds. Gate and throw are SchemaRegistry.installPackage / NamespaceConflictError; the co-owner set is ArtifactInstallScope.packageIds (artifact scope only); refuseCoOwnedObjectNameCollision reads the same set and skips non-co-owners by design, which is why D5's one added source makes both halves of the ADR-0130 D3 pair follow. The line numbers the HotCRM input and the ruling cite have moved; the ADR cites symbols.
  • Three dependency shapes, not two: the manifest record, the marketplace PackageDependencySchema array, and a third — AppManifestSchema.dependencies, bare ids with no range, in the same file as dependency_missing, no reader outside packages/spec. os package publish snapshots the record shape into manifest_json, whose declared schema is the array; the control-plane side lives in objectstack-ai/cloud and is not measured.
  • ADR number: 0136 and 0140 belong to proposals that never landed (PR docs(adr): ADR-0136 declared journeys as the priority anchor #18480 closed 2026-09-18, superseded by the North Star; PR docs(adr): ADR-0140 — v18 is the first LTS line, latest follows it, breaking changes batch into quarterly majors #25618 withdrawn 2026-10-24 by the maintainer's ruling in session); no landed record or open PR carries either number. This record is 0141.
  • In-repo declaring population: one manifest, examples/app-multi-package (orders → core, same artifact); each package declares its own version: '1.0.0', and composeStacks(…, { manifest: 'preserve' }) keeps each manifest as authored — why D2 counts a co-delivered dependency as present and judges it for presence only.
  • Range readers, three: rangeAdmitsMajor / alternativeAdmitsMajor (packages/metadata-core/src/protocol-handshake.ts) read */x/latest, ^, ~, N.x, ||, space- or comma-joined intersections, hyphen ranges and bare versions, but at major granularity (^6.2.0 admits 6.1.0); parseRangeFloor (same school, by its docstring) computes a floor only; SemanticVersionManager (packages/core/src/dependency-resolver.ts) compares full versions over a narrow grammar (6.x, intersections and || answer false or throw; ^6 throws in parse) and has no caller in packages/ outside its own file and test. No package under packages/ imports a semver library (root semver is a devDependency).
  • PATCH /api/v1/packages/:id (packages/runtime/src/domains/packages.ts) first runs requireWritablePackage(…, 'edit') — 422 WRITABLE_PACKAGE_REQUIRED for a package provided by code or an installed app (ADR-0070 D2) — then rewrites dependencies whole by reference to ManifestSchema.shape.dependencies, with no in-use check; it can delete an entry. The post-install edit D3 defers (merge question 11).
  • Hydrate order: the sys_packages hydrate (packages/services/service-package/src/index.ts) iterates packageService.list() and, on an installPackage throw, logs the package "was NOT hydrated into the registry" and leaves it absent for the process; the install-local kernel:ready rehydrate registers ledger entries in readdirSync order (local-manifest-source.ts#list). InstalledPackageSchema records no artifact; the manifest service's scope is not persisted. Hence D5's companion precondition and the per-path sort (merge question 12). resolvePluginOrder throws on a cycle through either edge kind and resolveArtifactPackageEntries keeps that throw for one artifact; D5 states the hydrate sort does not inherit it (cyclic members fall back to source order with a logged refusal).

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands (change set vs merge base 47af3dde4, 4 files, +851 / −16), all 20 run at head 02456273 (re-derived on that head), every one exit 0:
check-adr-links (+ --self-test), check-adr-symbol-anchors (+ --self-test; 2473 anchors across 143 records resolve), check-ci-filter-parity, check-closing-keyword-parity (+ --self-test), check-comment-mask-corpus, @objectstack/lint check:doc-formula-expressions, check:adr-anchors, check:closed-ceremony-text, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-prior-rulings, check:refd-timer-probe, check:watch-hint-literal.
dispatch-gates --ran: 20 derived, 20 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero — every line carried its exit code).
The record was re-drafted at 247fe2fa at CONTRACT_REVIEW_TIER with effort xhigh, on the owner's order that ADR text runs at that tier (card #25729). Contract review PASS at 247fe2fa (comment 6392433266); its five nits landed in fa733808. The independent review's eight accepted items (「全部照改」) landed in 2bf30f03. The independent full review of 2bf30f03 (PASS WITH FIXES, F1–F11) landed in 5c4071ef. The delta review of 5c4071ef (PASS WITH FIXES, N1–N4) landed in 02456273. The final delta check of 02456273 is PASS (record on this PR).
No changeset: docs/adr/** publishes nothing (skip-changeset).

Acceptance notes

  • hotcrm docs/architecture/module-split-plan.md, hotcrm#2270 and hotcrm#2271 are cited as the rulings cite them; that repository is not readable from this session.
  • Ruling 6369704387 says "revenue first per ADR-0130 §1.3(c)"; §1.3(c) names CPQ as the unsellable module. The ADR records revenue-first as HotCRM's own ordering; nothing moves.
  • D5's "read from either end" and sibling arm are the drafter's reading of the ruling's "one-way" (who declares and who gains stay one-way); they hold for HotCRM only if the app's companion packages (service, marketing) directly declare sales — the registry keeps no artifact record after install, so a companion is otherwise a foreign sitting owner that refuses the module's install and the app's next upgrade. Not verified in HotCRM: Open item 7, the HotCRM lane's reading. Boot order is decided, not read: each hydrate path sorts its batch through resolvePluginOrder (merge question 12) — ledger entries as nodes in the install-local path, a cycle degrading to source order rather than throwing out of the hydrate —; cross-path order and the kernel:ready seal/hydrate hook order are Open item 2.
  • D4's open section carries the ADR-0086 D4 discriminator question; nothing in-repo was measured to refuse a package's permission set naming another package's object — recorded as open, not decided.
  • The ADR's merge-decision list now names twelve drafter choices (the contract review's D(5)–(10) are items 5–10; 11 and 12 are new): 422 over the 409 precedents; fail-closed parse with a stated grammar, extending the handshake reader family; required entries / optional → false; own entries only; the D8 conversion as its own card; the two-halves reading of ruling 5645322259; the post-install PATCH deferred (authored packages only); each hydrate path sorting by manifest.dependencies.

Generated by Claude Code

…lared in manifest.dependencies, checked once before install, co-owning the namespace it shares

Records the two maintainer rulings on the inter-app dependency decision
(B′ 6369704387, namespace letter A 6377678758): declaration and reference
stay the existing manifest key and the save door's closure; a pre-install
check (installed, in range, else a clear refusal) fills the reserved
dependency_missing category; uninstall protection, upgrade interception,
topological/cascade install, the owner-side publish marker and marketplace
version negotiation are deferred by name; permissions are unchanged; a
declared dependency is one-way namespace co-ownership with the ADR-0130 D3
uniqueness check spanning it.

ADR-0028 D4 is rewritten to "no cross-app reference without a declared
dependency"; ADR-0019 gains an additive Related line; ADR-0130 gains an
addendum moving D8's deferral end to marketplace publishing and marking
its #14488 line stale.

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

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么
新增 ADR-0141(Proposed),把 #25403 的两次裁决写成契约:应用 B 用现有的 manifest.dependencies 声明依赖应用 A,引用沿用保存门的依赖闭包;唯一新增的关口是安装前检查(依赖已装、版本在范围内,否则一次性列出所有缺的依赖并拒绝安装,填上预留的 dependency_missing);声明了依赖且命名空间相同的包,在安装门上与被依赖方共有命名空间(单向),对象名唯一检查跨发布物照样生效。卸载保护、升级拦截、拓扑/连带安装、被引用方发布标记、市场版本协商逐项写为推迟项,各带触发条件;权限不变,记录级语义留作开放节。配套三处:ADR-0028 D4 改为「未声明依赖不得跨应用引用」;ADR-0019 只加一行交叉引用;ADR-0130 加附录(D8 的推迟改为到市场发布时结束,#14488 那一行标为已过时)。

为什么改
你裁了 B′ 和命名空间 A。HotCRM 要把 revenue 等模块拆成可以单独安装的包,今天做不到:安装时没有任何地方检查依赖是否已装,单独发布的模块还会因为共用 crm 命名空间被安装门拒绝。ADR 合并后才切运行时卡。

风险与代价(含回滚)
只改文档,不动代码和 schema。业务代价已写进 ADR:卸载或升级被依赖的应用暂时不拦,管理员可能把依赖方晾着;单独发布的模块和主应用各有版本号,只靠声明的版本范围约束。回滚 = 撤销合并,没有运行时状态。

席位意见
建议合并。我对照 diff 读完了全文,裁决的八项都在,没有超出裁决的新机制。起草人在裁决没说到的地方做了四个判断(ADR 末尾「Open questions for the maintainer's merge decision」),我四个都同意:① 同一发布物内交付的依赖算已装,否则现有多包发布物会拒绝自己;② 只在安装动作时检查、开机重注册不检查,符合 AGENTS.md 的启动期注册规则;③ 命名空间共有关系不论谁先注册都认,否则开机恢复顺序可能把 HotCRM 本身拒掉;④ 只认直接声明,范围更窄、以后放宽不用回退。起草时还实测纠正了派发令的一个假设:安装时其实有一处读 dependencies,但只用来排序、从不检查,结论不变。

你要做的
读 ADR 末尾那四条判断;同意就批准或直接合并本 PR(批准账户为 os-zhuang / hotlong,两位都已被请审)。

席位核对

Seat domain:spec#4 (#18917) · session_01K2Tat8JEt4AhLASV4V4QWE · reviewed at head d9b22062884e · 2026-10-24T23:37Z · verdict ACCEPT (Tier H: stays draft, the maintainer merges)

  1. Shape. Draft, base main, body opens with the attribution block then Fixes #25403; no closing keyword sits next to any other open issue number (按包组合授权:module 把自己对象的 permission 授权贡献进 app 拥有的角色(类比 navigationContributions)—— ADR-0130 分拆第二阶段,不挡发版 #14488 is closed; hotcrm cards are cited without verbs). Labels documentation, skip-changeset: docs/adr only, which publishes nothing. check-governed-merges.mjs --pr 25717: Tier H, docs/adr/** ×4, 497 changed lines (≤ 3000).
  2. Premises measured by the seat at origin/main 47af3dde42. resolveArtifactPackageEntries is exported from packages/core/src/artifact-packages.ts and its header says an out-of-artifact id "is simply not an edge"; dependency_missing occurs only in app-install.zod.ts, its test and the generated app-install.mdx; throw new NamespaceConflictError(...) sits in registry.ts with refuseCoOwnedObjectNameCollision called after it; resolveWritePackageScope is the save door's closure in protocol.ts; ADR numbers 0136 (docs(adr): ADR-0136 declared journeys as the priority anchor #18480) and 0140 (docs(adr): ADR-0140 — v18 is the first LTS line, latest follows it, breaking changes batch into quarterly majors #25618) are closed unlanded proposals, so 0141 is right.
  3. Gates. Dev's dispatch-gates --commands 20/20 exit 0 at d9b22062, --ran 20 derived / 20 run. Head-pinned check-runs at this record: 45 runs on d9b22062884e (= PR head), 32 success and 13 skipped, none failed or pending; Lint & Repo Gates and TypeScript Type Check both success.
  4. Accepted deviations. (a) The dispatch's assumption "nothing at install reads manifest.dependencies" is corrected by one reader (ordering only); recorded in the ADR's Context and Verification notes, no decision moves. (b) Three dependency shapes, not two (AppManifestSchema.dependencies added to D8). (c) The ADR keeps three dated line numbers for registry.ts (5974, 5989–6005, 6003) beside its symbol anchors; they are stated as readings at 47af3dde42, so drift does not make them false. (d) Four drafter judgements are listed for the merge decision instead of being decided silently; the seat agrees with all four (quick read above).
  5. Out of scope. No out-of-scope findings. The PR's Acceptance notes (hotcrm cited unverified; §1.3(c) names CPQ, not revenue) move no decision and need no card.
  6. Landing. Tier H: the PR stays draft, no auto-merge, no queue. It lands by the maintainer's merge, or by an authorized APPROVED review from GOVERNED_APPROVERS (os-zhuang, hotlong, both requested) after which this seat lands it. needs-user-decision goes on the PR with this record. After acceptance the runtime card is cut per the ADR's Execution plan.

… D2 placement and D5 relation restated, neighbour records re-amended

WIP: full re-drafting pass over the first draft. Every factual claim re-read at
the branch base; the maintainer's five verbatim lines carried whole; D2 names
the three install acts and the three re-registration paths; D5 defines the
co-owner relation once and states what reads it; D4 gains the ADR-0086 D4
discriminator question; ADR-0130's addendum records that #14488's premise
sentence moved while its disposition stands.

Claude-Session: https://claude.ai/code/session_01HwUcNzGjPhV6xtYkJ469qK
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l and removed size/m labels Oct 25, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 247fe2faa90bec0ed5fb2fd4a6014c17aaa8276f
Local-runs: none
Scope: docs/adr/0019-app-as-consumer-unit.md, docs/adr/0028-metadata-naming-and-namespace-isolation.md, docs/adr/0130-release-artifact-as-co-ownership-boundary.md, docs/adr/0141-inter-app-dependency-declared-checked-before-install.md + packages/spec/src/kernel/manifest.zod.ts, packages/spec/src/system/app-install.zod.ts, packages/spec/src/marketplace/package-version.zod.ts, packages/spec/src/kernel/package-registry.zod.ts, packages/spec/src/stack.zod.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/objectql/src/engine.ts, packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-package-install.ts, packages/metadata-core/src/protocol-handshake.ts, packages/metadata-core/src/artifact-forward-conversion.ts, packages/core/src/artifact-packages.ts, packages/core/src/plugin-order.ts, packages/core/src/dependency-resolver.ts, packages/runtime/src/domains/packages.ts, packages/runtime/src/app-plugin.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/services/service-package/src/index.ts, packages/lint/src/rule-explanations.ts, packages/cli/src/commands/package/publish.ts, examples/app-multi-package/src/packages/orders/index.ts, content/docs/references/system/app-install.mdx, docs/adr/0029, docs/adr/0048, docs/adr/0086, docs/adr/0087, docs/adr/0112, docs/adr/0116, docs/adr/0129

Inputs read: card #25403 body and all 7 comments (rulings 6369704387 and 6377678758 bind; 6370024454 is the HotCRM seat's input; 6390714959, 6390895435, 6390979898, 6392268004 are the dispatch claim and the dev's reports, treated as claims); PR #25717 body, file list (4 files, +612/−16) and the net diff origin/main...origin/claude/project-thread-uu2cmp; the 44 check-runs on the head. Code read at origin/main f3fea0a03df3; the branch base is 47af3dde42, and of every file the ADR cites only packages/lint/src/rule-explanations.ts moved between the two (+23 lines, a new security-modify-all-delete-withheld explanation; the cited LIST_VIEW_OBJECT_OUTSIDE_CLOSURE_EXPLANATION text is byte-identical). The ruling 5645322259 on #14488 was read because ADR-0141 D6 and the ADR-0130 addendum quote it.

① Derived judgments

A. Claims about today's code — each checked at origin/main.

ADR-0141 claim Anchor Verdict
ManifestSchema.dependencies is z.record(z.string(), z.string()).optional(), documented "Map of package IDs to version requirements" with an npm-style example packages/spec/src/kernel/manifest.zod.ts lines 711–715 (@example { "@steedos/plugin-auth": "^2.0.0" }) right
PATCH /api/v1/packages/:id edits dependencies, validates by reference to ManifestSchema.shape.dependencies, "⛔ No in-use check is made" packages/runtime/src/domains/packages.ts lines 2300–2320; durable half protocol.ts line 29681 right
resolveWritePackageScope computes the TRANSITIVE closure; an unresolvable declared id stays in the closure and the walk stops packages/metadata-protocol/src/protocol.ts lines 7377–7418 right
Save door refuses 422 with "declare a dependency on the package that defines the object"; os validate/os build/os lint "judge neither: a stack there is one app, and an unresolved binding may be a dependency package's object the stack does not carry" packages/lint/src/rule-explanations.ts LIST_VIEW_OBJECT_OUTSIDE_CLOSURE_EXPLANATION right, verbatim
defineStack admits a cross-package lookup, refuses hooks[].object and an app's own navigation to a foreign object — "navigation crosses only through contributions, and the split must follow hook ownership" packages/spec/src/stack.zod.ts lines 2170–2190 (DefineStackOptions.artifactObjectNames doc); ADR-0130 §1.5 (lines 155–160); no reference_to validation exists in stack.zod.ts, so lookups and master-detail are admitted right
At install manifest.dependencies has one reader, which orders and never checks: resolveArtifactPackageEntries feeds declared ids to resolvePluginOrder as optionalDependencies; an id outside the artifact "is simply not an edge here" packages/core/src/artifact-packages.ts lines 111–136 (header), 334–336 (optionalDependencies: Object.keys(manifest.dependencies)), 344 (resolvePluginOrder) right, verbatim
dependency_missing is an enum value of AppCompatibilityCheckSchema with zero producers packages/spec/src/system/app-install.zod.ts line 88; git grep over origin/main hits only the schema, app-install.test.ts and content/docs/references/system/app-install.mdx right on substance; see nit ⑥ (no tracked "generated JSON schema" carries it)
AppManifestSchema.dependencies is an array of bare ids, default [], no reader outside packages/spec app-install.zod.ts line 60; git grep AppManifestSchema outside spec: none right
PackageDependencySchema is { packageId, versionRange, optional = false }; PackageManifestSchema.dependencies is an array of it, default []; no reader outside spec; its header tells declaration from ResolvedPackageDependency packages/spec/src/marketplace/package-version.zod.ts lines 44–63, 109; readers outside spec: only CHANGELOG lines right
os package publish snapshots the compiled artifact into sys_package_version.manifest_json (header step 2) packages/cli/src/commands/package/publish.ts lines 11–12 right
SemanticVersionManager / DependencyResolver serve kernel plugins packages/core/src/dependency-resolver.ts lines 16, 181–185 ("Plugin Dependency Resolver") right
parseRangeFloor exists as a partial semver reader packages/metadata-core/src/artifact-forward-conversion.ts line 336 right
SchemaRegistry.installPackage builds coOwners from scope.packageIds minus self, refuses a non-shareable namespace owned by a non-co-owner with NamespaceConflictError (NAMESPACE_CONFLICT, status = 422), downgraded only under OS_METADATA_COLLISION=warn; same-package reinstall exempt (owner !== manifest.id) packages/objectql/src/registry.ts lines 5936–6005 (gate at 5990, throw 6003), 1886–1889, 2624, 1609 (isShareableNamespace) right
refuseCoOwnedObjectNameCollision returns early on an empty set, skips a sitting owner that is not a co-owner ("a cross-artifact claim, which the namespace gate above owns and refuses"), throws ArtifactObjectNameConflictError (DUPLICATE_ARTIFACT_OBJECT_NAME, 422); both halves read the one set registry.ts lines 6013, 6150–6172, 2263–2266 right, verbatim
ArtifactInstallScope.packageIds is built by the manifest service and handed through registerApp registry.ts line 2202; packages/objectql/src/plugin.ts line 775; engine.ts line 9250 registerApp(body, scope?, source) right
Picklist and bound-action seals: "A refused install registers nothing" packages/objectql/src/plugin.ts lines 784, 801 right, verbatim
Three install acts, each already running assertProtocolCompat ahead of registration: the protocol primitive (POST /api/v1/packages when the protocol service resolves; duplicatePackage and the base-package path in-process), the bare-registry fallback, POST /api/v1/marketplace/install-local protocol-package-install.ts lines 89, 110–116, 218; packages.ts lines 1180–1183, 1426–1442 (fallback runs assertProtocolCompat(manifest) at 1439); marketplace-install-local-plugin.ts line 1529, then manifestService.register(converted, { source: 'marketplace' }) at 950/1761. importUnmanagedPackage (protocol.ts, the ADR-0131 D6 unmanaged install reached from install-local) calls this.installPackage({ manifest: plan.baseManifest }) in-process, so it is the primitive's caller, not a fourth door right
Re-registration paths: code packages via the manifest service at init/start, the sys_packages rehydrate, install-local's kernel:ready rehydrate packages/runtime/src/app-plugin.ts line 553; packages/services/service-package/src/index.ts lines 713 (checkProtocolCompat), 751 (registry.installPackage(…, 'authored'), no scope); marketplace-install-local-plugin.ts lines 20, 120 right
ADR-0029's extender refusal is the uninstall refusal that stands registry.ts assertPackageUninstallable (throws PackageUninstallExtendedError, RESOURCE_CONFLICT/409) called by unregisterObjectsByPackage line 4920, reached from uninstallPackage line 6192 right; see nit ⑦ ("the only uninstall refusal there is" is over-broad)
InstalledPackageSchema.enabled packages/spec/src/kernel/package-registry.zod.ts line 130 right
In-repo declaring population is one manifest, orders → core in the same artifact examples/app-multi-package/src/packages/orders/index.ts line 65; the only other dependencies: { in packages/** is cli/src/commands/create.ts line 411, an npm package.json scaffold right
ADR numbering: 0136 (PR #18480) and 0140 (PR #25618) never landed; no open PR carries a new record both PRs closed, merged: false; docs/adr/ at origin/main ends at 0139; of 20 open PRs only #25716 names an ADR (amends 0126/0138) right
The tree outside docs/adr/ is identical at the head git diff --stat origin/main...head lists the four ADR files only right

B. Claims about neighbouring records — each checked at origin/main.

  • ADR-0019 §Out of scope bullet "Shared/published data model + inter-App dependencies (cross-app data sharing without a "suite")" — line 137, quoted exactly. ADR-0130 D2's recording convention ("the follow-up is a one-line Related entry on 0019 pointing here — additive, superseding nothing") — lines 203–209, as cited.
  • ADR-0130 D6 ("becomes its own artifact — at which point it needs the owner field D8 defers, and D8's deferral ends"), D8 ("necessary the day a module ships as its own artifact and must assert across artifacts 'I have the same publisher as HotCRM' … at the publisherOrgId"), D3 (pair as a machine constraint; gate test asserts code and status), §1.3(c) (CPQ, not revenue), the 2026-09-02 addendum's "filed, not decided" 按包组合授权:module 把自己对象的 permission 授权贡献进 app 拥有的角色(类比 navigationContributions)—— ADR-0130 分拆第二阶段,不挡发版 #14488 line and its "that discriminator is gone" input — all read as quoted.
  • ADR-0048 §3.2 (install gate), §3.5 (rename-on-install a non-goal), 2026-08-08 addendum Proposed, D2 publisherOrgId, A.5 third-party-publishing precondition — all as cited. ADR-0086 D4 "each set only grants objects.OWN-NAMESPACE_* keys" and "A package never writes into a shared/foreign record" — lines 191, 196. ADR-0087 D1, ADR-0112 D3 (ledger), ADR-0116, ADR-0129 — titles and decisions as cited.
  • 按包组合授权:module 把自己对象的 permission 授权贡献进 app 拥有的角色(类比 navigationContributions)—— ADR-0130 分拆第二阶段,不挡发版 #14488: closed not_planned 2026-09-12T10:27Z; ruling 5645322259 says a module carved out as its own artifact ships its own permission sets under ADR-0086 D4 and the card is not reopened for that — as the ADR-0130 addendum records. See nit ⑤ on the quotation's bytes.

C. Decisions against the two rulings.

Ruling 6369704387 (B′) — all four items present and unmoved: D1 (declaration = existing ManifestSchema.dependencies; reference = the save door's closure; no owner-side flag, no second mechanism); D2 (the pre-install check as the only new gate; installed + in range, else a clear refusal filling dependency_missing); D3 (all five deferred items by name: uninstall protection, upgrade interception, topological/cascade install, owner-side publish marker, marketplace version negotiation — each with interim state and trigger; none built); D4 (permissions unchanged, record-level semantics an open section decided with the first consumer). The ruling's workload list is executed: ADR-0028 D4 rewritten to "no cross-app reference without a declared dependency"; ADR-0019 cross-reference with text unchanged; the two dependency shapes aligned (D8); first consumer = a HotCRM module shipping alone, module-split-plan 2026-09-14 shape, revenue first (D7); composite products keep shipping as one ADR-0130 artifact until the check lands. The five maintainer lines are quoted whole and untranslated; the ADR correctly notes the ruling's "§1.3(c)" cites a passage that names CPQ, and moves nothing on it.

Ruling 6377678758 (namespace, letter A) — D5 states exactly it: dependency-declared co-ownership; one-way (X declares nothing, gains nothing); the ADR-0130 D3 pair spans those co-owners across artifacts because both halves read one set; no new manifest field; the in-tenant trust is the administrator's install act; ADR-0130 D8's deferral reworded to end at marketplace publishing, aligned to ADR-0048's 2026-08-08 addendum (still Proposed). Letters B (owner field now) and C (rename) recorded as not chosen; the director's variant D recorded as not chosen. Card 裁后执行 item 5 ("运行时卡把这一处安装门改动与安装前检查一起做") is Execution plan item 1.

Nothing the rulings deferred or rejected is decided here: every D3 item is a non-build with a trigger; the owner/publisher field stays deferred; renaming stays refused; permissions do not move.

D. Where the drafter decided and the rulings were silent. The ADR lists four as merge questions, all four verified as real gaps the rulings left: (1) a dependency delivered by the same artifact counts as present — necessary, because resolveArtifactPackageEntries already treats such an id as an in-artifact edge and examples/app-multi-package (orders → core) would otherwise be refused; (2) install acts only, never boot re-registration — necessary, because the sys_packages rehydrate and install-local's kernel:ready rehydrate register in boot order, not install order (AGENTS.md "Startup registry reads"); (3) D5 read from either end — necessary for the same reason, since the namespace gate runs on every installPackage call including rehydrate and a dependent registered first would otherwise refuse the depended-on app; (4) direct declarations only. The following are ALSO drafter decisions, stated in the text but NOT in the merge-question list: (5) the refusal's HTTP status 422, fixed by analogy with NAMESPACE_CONFLICT; (6) fail-closed on an unparseable version or range — note this is the opposite policy of the sibling handshake at the same doors, where assertProtocolCompat admits an unrecognised range with a warning; (7) every record entry is required and the marketplace optional arm projects to false; (8) only P's own entries are judged; (9) the manifest record is the source of truth and the marketplace array a projection, with a record-to-array conversion added to the runtime card's work (D8 item 1) — work beyond the ruling's "XS–S pre-install check" plus "objectql S"; (10) the reading that ruling 5645322259's premise sentence "has since been moved" by the B′ ruling (ADR-0141 D6; ADR-0130 addendum) — a reconciliation of two maintainer rulings that neither ruling on #25403 makes. None contradicts a ruling; (10) and (9) are the ones the maintainer should see named before merging.

E. The three neighbour edits. ADR-0028: five hunks and no more — the status line gains one "Amended" entry; TL;DR item 4, D4 (heading, a blockquote preserving the original sentence, body), D6's author-time bullet and leak-point 3 are rewritten; a grep of the head's 0028 finds no leftover "sandboxed"/"forbidden cross-app" sentence; the record stays Deferred. ADR-0019: one additive Related header line after Consumers; zero deletions. ADR-0130: one additive status-header entry and one addendum at the end; zero deletions; the addendum says only what D5/D6 of ADR-0141 and the two rulings say, plus the #14488 point (see D(10)). All three say only what the rulings and ADR-0141 say.

F. Check-runs on the head (read, not run). All 44 completed, none in progress. Required contexts: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (and all 6 shards), Governed Surface Queue Guard success, Build Core skipped (paths filter; docs-only diff). Governance checks: The card this PR closes must claim this branch success, No other open PR may claim the same issue success, No other open PR may claim the same single-writer path success, Part-of PR must not also close its card success, Check PR Size success, Check Documentation Links success, Lint · PM dispatch-gates success. Check Changeset skipped (label). No failure anywhere.

② Semver level

docs/adr/** only; nothing published from any released package; no changeset in the diff and none owed. The skip-changeset label is on the PR and Check Changeset is skipped by it. The PR body carries Clause-②: no and Metadata-protocol: none, matching the dispatch claim 6390714959 and matching the diff (no authorable key, value, type or meaning changes). Level: none — correct.

③ Boundary flags

The four drafter judgements the ADR lists for the merge decision:

  1. Co-delivered dependency counts as present — agree. Reason: the check judges the whole artifact before any mutation (installPackage refuses ahead of every write), so a same-artifact dependency is not yet "installed"; without this arm every ADR-0130 artifact whose modules declare their app, examples/app-multi-package included, would be refused.
  2. Install acts only; never boot re-registration — agree. Reason: service-package/src/index.ts line 751 and install-local's kernel:ready rehydrate register in boot order; a code package at start would read a marketplace dependency that arrives at kernel:ready as absent — the recorded-verdict defect AGENTS.md names. The gap it leaves (an environment whose dependency was removed outside an install act boots silently) is the uninstall-protection item the ruling itself deferred.
  3. D5 read from either end — agree, with the observation that the namespace gate already runs on every installPackage call (rehydrate included), so D5's co-owner source is evaluated at re-registration whether or not D2 is; the ADR's Open item 2 asks the runtime card to read today's rehydrate order first, which is the right place.
  4. D5 direct declarations only — agree. Reason: "who shares a namespace" and "what may be referenced" are different questions; the save door's transitive walk answers the second; narrowing the first is the reversible direction.

Dev flags in the reports on #25403:

  • 6390895435 / ADR Verification notes — "nothing at install reads manifest.dependencies" fails for ordering by one reader: agree, verified (artifact-packages.ts lines 334–344).
  • NamespaceConflictError line numbers moved (gate 5989–6005, throw 6003, co-owner set 5974): agree, verified (if at 5990, throw at 6003, coOwners at 5974).
  • Snapshot carries the record shape where PackageManifestSchema declares an array; control-plane consumption unmeasured (objectstack-ai/cloud): agree it is recorded as D8 item 1 and correctly marked not measured; flag that it enlarges the runtime card beyond the ruling's sizing — see D(9).
  • AppManifestSchema.dependencies as a third shape with no reader: agree, verified; D8 item 3 decides nothing, as it should.
  • "revenue first per ADR-0130 §1.3(c)" vs §1.3(c) naming CPQ: agree, verified; recorded without moving a decision.
  • hotcrm module-split-plan.md, hotcrm#2270/chore: version packages #2271 not readable: agree, marked "not re-verified" in the ADR wherever cited.
  • 6392268004 — the first pass's door discriminator was false because the sys_packages rehydrate runs the handshake too: agree, verified (checkProtocolCompat at line 713); the redraft names the three install acts and three re-registration paths instead, which is correct.
  • 6392268004 pr_body_edits_for_the_seat (6 edits): disagree with the current state — none is applied. The PR body still says gates ran at head d9b22062, Measurements bullet 2 still cites line numbers the ADR removed, the 0130 row omits the 5645322259 premise note, the D2/D4 cells and Acceptance-notes bullet 3 describe the first draft. The body is an input the maintainer reads for a Tier H merge; the seat should apply the six edits (body only; no new commit needed).
  • 6392268004 STALE TREE warning (origin/main ahead): agree no merge was owed (AGENTS.md §10: no conflict, no main-caused red); verified the one cited file that moved, rule-explanations.ts, left the cited text unchanged.

Nothing is escalated: no ruling is contradicted, nothing deferred is built, and the governed edits stay inside what the rulings ordered.

Nits for the seat (none moves the verdict): ⑤ the 「目前的要求是发布物是完整安装的,没有设计单独安装其中一个模块的需求」 quotation (ADR-0141 D6; ADR-0130 addendum) drops an interior space and the full stop present in 5645322259 (「…其中 一个模块的需求。」) — AGENTS.md asks for 原文; ⑥ "the generated JSON schema" is not a tracked occurrence of dependency_missing at origin/main (three files carry it: the Zod source, its test, the reference page); ⑦ "it is the only uninstall refusal there is" (D3 preamble) is over-broad — the install-local DELETE door also refuses under ADR-0131 D12's wall and the capability check; say "the only refusal that reads another package's relation to it"; ⑧ ADR-0028 D4's rewritten body says "ADR-0141 D2 refuses to install …" in the present tense while D2 is unbuilt — "decides that the install is refused" keeps Prime Directive #10's declared-vs-enforced line clean; ⑨ the merge-question list should name D(5)–D(10) above, or state that D2's refusal shape (422, fail-closed parse, required entries) and D8's projection direction are the drafter's.

Implemented-by: claude/project-thread-uu2cmp
Reviewed-by: session_01K2Tat8JEt4AhLASV4V4QWE

VERDICT: PASS

…cy_missing population, uninstall-refusal wording, D2 tense in ADR-0028, merge questions 5–10

Contract review PASS at 247fe2f named five nits; all five land here.
The quotation from ruling 5645322259 is copied from the comment body
(interior space and full stop restored) in ADR-0141 D6 and ADR-0130's
addendum; the dependency_missing population names the three tracked files
only; D3's preamble says what the ADR-0029 refusal is the only instance of;
ADR-0028 D4 says D2 decides, not refuses, since D2 is unbuilt; the
merge-decision list carries the drafter's six further choices (422,
fail-closed parse vs the handshake's warn-and-admit, required entries,
own entries only, the D8 conversion's sizing, the 5645322259 reading).

Claude-Session: https://claude.ai/code/session_01HwUcNzGjPhV6xtYkJ469qK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fa7338085423ab04a9fefea4a4fac98e7ed0b79e
Local-runs: none
Scope: docs/adr/0019-app-as-consumer-unit.md, docs/adr/0028-metadata-naming-and-namespace-isolation.md, docs/adr/0130-release-artifact-as-co-ownership-boundary.md, docs/adr/0141-inter-app-dependency-declared-checked-before-install.md + packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/metadata-core/src/protocol-handshake.ts, packages/spec/src/system/app-install.zod.ts, content/docs/references/system/app-install.mdx, packages/objectql/src/registry.ts, and the full cited-code set of the 247fe2fa record (comment 6392433266), which this head leaves untouched

Delta review on top of the 247fe2fa record (comment 6392433266, PASS). One new commit, fa733808 ("ADR-0141 patch round 2"): 3 files, +33/−8, seven hunks — ADR-0028 one hunk, ADR-0130 one hunk, ADR-0141 five hunks. git diff 247fe2fa fa733808 touches nothing else; ADR-0019 is byte-identical to the reviewed head; the tree outside docs/adr/ is identical to the base 47af3dde42; the net diff against main is +637/−16 over the same four files, matching the PR body. Everything the earlier record judged outside these seven hunks stands unchanged and is not re-argued here. Inputs: card #25403 (no new comments since round one; the 7 read then), PR #25717 body as refreshed at 2026-10-25T02:37Z, the delta and the net diff, the 51 check-runs on fa733808. Code read at origin/main f3fea0a03df3.

① Derived judgments

Each changed hunk, judged.

  1. ADR-0141 Context §1, dependency_missing population — now "across the tracked tree it occurs in that schema, its test and the generated reference page (content/docs/references/system/app-install.mdx) — zero producers". Verified: git grep dependency_missing origin/main outside docs/adr/ hits exactly packages/spec/src/system/app-install.zod.ts line 88, app-install.test.ts lines 87/95, content/docs/references/system/app-install.mdx lines 47/55. Right; closes nit ⑥.

  2. ADR-0141 D2 arm 2 (in range) — adds "deliberately the opposite of the protocol handshake beside it, which admits an unrecognised engines.protocol range with a warning (merge question 6 says why)". Verified: packages/metadata-core/src/protocol-handshake.ts checkProtocolCompat returns status: 'unparsed-range', and assertProtocolCompat on that status calls warn(...) and returns ("parser-coverage gap; never a false rejection"). Right; the contrast is a real one and is now stated where the policy is set.

  3. ADR-0141 D3 preamble — "the only uninstall refusal there is" becomes "the only refusal that reads another package's relation to the one being removed; the install-local DELETE door's own refusals (ADR-0131 D12's wall, a request it cannot serve) read the deployment's posture and the request, not another package". Verified at marketplace-install-local-plugin.ts lines 2145–2165: requireInstallCapability, INVALID_REQUEST 400, RESOURCE_NOT_FOUND 404, refuseWalledCatalogPackage (ADR-0131 D12), MARKETPLACE_STORAGE_FAILED 500 — none reads another package; the relation-based refusal is assertPackageUninstallable (registry.ts, via unregisterObjectsByPackage). Right; closes nit ⑦.

  4. ADR-0141 D6, ruling 5645322259 quotation — now 「目前的要求是发布物是完整安装的,没有设计单独安装其中 一个模块的需求。」. Byte-compared against the comment body fetched from GitHub: identical (the interior character is U+0020, the full stop present). Right; closes nit ⑤ for 0141.

  5. ADR-0141 merge-question preamble — adds "Items 1–4 shape the mechanism; 5–8 shape D2's refusal; 9 is a sizing consequence; 10 is a reading of an earlier ruling." Consistent with the list that follows.

  6. ADR-0141 merge questions 5–10 (new) — the six drafter decisions the earlier record named as D(5)–D(10), each now stated with its reason: (5) 422 by analogy with NAMESPACE_CONFLICT (NamespaceConflictError.status = 422, verified round one); (6) fail-closed parse vs the handshake's warn-and-admit, with the reason the policies differ (grandfathering a fleet vs one author's entry about one named package; "the in-repo population is one manifest" — verified round one, examples/app-multi-package); (7) every record entry required, optional projects to false, no new record spelling (consistent with the record's own "no schema, no code" status line); (8) only P's own entries judged; (9) D8's record-to-array conversion enlarges the runtime card beyond the rulings' sizing — "XS–S for the check, S for the objectql source" matches ruling 6369704387 ("one runtime card for the pre-install check (XS–S)") and ruling 6377678758 ("Workload: objectql S … riding with the B′ runtime pre-install card"), and offers the maintainer the option of a separate card; (10) the reading that ruling 5645322259's premise was moved by B′, stated as a reconciliation neither ruling on [Decision] 一个应用能否声明依赖另一个应用并引用其对象(ADR-0019 推迟的应用间依赖) #25403 makes, disposition untouched. Nothing new is decided by these hunks: each names a decision already present in D2/D8/D6 of the reviewed head. Internal references still resolve ("merge question 6" at D2; "Open items 2/3/4" unchanged); no stale "four questions" wording remains. Closes nit ⑨.

  7. ADR-0028 D4 (rewritten body), last sentence — "ADR-0141 D2 decides that installing an app whose declared dependency is absent or out of range is refused — a decision its runtime card implements; nothing enforces it until that card lands." Says only what ADR-0141 D2 and its Status line say; keeps the declared-vs-enforced line clean. Closes nit ⑧. The rest of 0028's five hunks are unchanged from the reviewed head (status entry, TL;DR item 4, D4 heading and blockquote, D6 author-time bullet, leak-point 3).

  8. ADR-0130 addendum, the same quotation — byte-identical to the source comment (verified as in hunk 4). The addendum remains purely additive: zero deletions anywhere in 0130; D1–D8, §1–§8 and both earlier addenda untouched.

Rulings. No hunk moves a decision. Ruling 6369704387 (B′) items 1–4 and its workload list, and ruling 6377678758 (letter A) with the D8 rewording, stand exactly as the 247fe2fa record found them: nothing chosen is missing, nothing deferred or rejected is decided, and the places where the drafter decided in the rulings' silence are now all ten named in the ADR's own merge-question list. ADR-0019: one additive Related line, text unchanged. ADR-0130: additive only. ADR-0028: the status entry, D4 and the three restating sentences, nothing else.

Check-runs on fa733808 (read, not run). 51 runs, all completed, none in progress, no failure. Required contexts: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (all 6 shards success), Governed Surface Queue Guard success, Build Core skipped (paths filter; docs-only diff). Governance: The card this PR closes must claim this branch success, No other open PR may claim the same issue success, No other open PR may claim the same single-writer path success, Part-of PR must not also close its card success, Check PR Size success (a second, skipped run of the same name also exists), Check Documentation Links success, Lint · PM dispatch-gates success. Check Changeset skipped (label).

② Semver level

Unchanged from the reviewed head: docs/adr/** only; nothing published; no changeset in the diff and none owed. skip-changeset label present, Check Changeset skipped by it. PR body carries Clause-②: no and Metadata-protocol: none, matching the dispatch claim 6390714959 and the diff. Level: none — correct.

③ Boundary flags

The ten drafter judgements the ADR now lists for the merge decision — items 1–4 as judged in comment 6392433266 (agree, all four); items 5–10:
5. 422 — agree: the sibling refusals at the same gate (NamespaceConflictError, ArtifactObjectNameConflictError) carry status = 422.
6. Fail-closed parse — agree: the reason given (an unread bound admits every version, the one outcome the check exists to refuse) is sound, and the text now says plainly that it diverges from assertProtocolCompat's policy and why.
7. Required entries; optional projects to false — agree: adding an optional spelling to the record would be a spec change this record forbids itself.
8. Own entries only — agree: a dependency's own dependencies were judged at its install; the alternative re-runs D2 for packages already admitted.
9. D8 conversion sizing — agree that it is named; it is the one item that enlarges the ruling's workload, and the text now offers the maintainer the cut (own card) instead of burying it in the runtime card.
10. Reading of 5645322259 — agree it is named as a reading, with the earlier disposition untouched; the quotation it rests on is now byte-exact.

Dev claims on this round (coordinator relay; the dev posted no new card comment) — each checked: byte-exact quote in 0141 D6 and the 0130 addendum: verified; dependency_missing scoped to three tracked files: verified; D3 preamble rescoped: verified; ADR-0028 D4 says D2 decides the refusal: verified; merge questions 5–10 added: verified; PR body updated with the six edits from report 6392268004: verified — the 0130 row carries the 5645322259 premise note; the 0141 row's D2 and D4 cells describe the current text and the ten-item list; Measurements bullet 2 cites symbols, no line numbers; the Gates paragraph names head fa733808; Acceptance-notes bullet 3 reads "read from either end" and new bullets cover the ADR-0086 D4 question and the ten items; 你要做的 names the ten items with the third judgement reworded (「命名空间共有关系由依赖方单向声明,门禁不论哪一方后注册都按这一条声明判定」). Body facts cross-checked: +637/−16 (matches git diff --stat), "its five nits landed in fa733808" (the five ADR-side nits ⑤–⑨; the sixth defect was the body itself, now fixed), "Contract review PASS at 247fe2fa (comment 6392433266)" (correct id). The body's gate claims (20 derived, 20 run, 2446 anchors) are claims; the check-runs above are the verdicts.

Nothing escalated. No defect found in this delta.

Implemented-by: claude/project-thread-uu2cmp
Reviewed-by: session_01K2Tat8JEt4AhLASV4V4QWE

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么
新增 ADR-0141(Proposed),把 #25403 的两次裁决写成契约:应用 B 用现有的 manifest.dependencies 声明依赖应用 A,引用沿用保存门的依赖闭包;唯一新增的关口是安装前检查(依赖已装、版本在范围内,否则一次性列出缺的依赖并拒绝安装,填上预留的 dependency_missing);声明了依赖且命名空间相同的包,在安装门上与被依赖方共有命名空间(单向)。卸载保护、升级拦截、拓扑/连带安装、被引用方发布标记、市场版本协商逐项写为推迟项;权限不变,记录级语义留作开放节。配套:ADR-0028 D4 改为「未声明依赖不得跨应用引用」;ADR-0019 加一行交叉引用;ADR-0130 加附录。本版按你的要求由 Fable(xhigh)整篇重写,并经同档契约复核两轮。

为什么改
你裁了 B′ 和命名空间 A。HotCRM 要把 revenue 等模块拆成可以单独安装的包,今天做不到:安装时不检查依赖,单独发布的模块还会因为共用 crm 命名空间被拒。ADR 合并后才切运行时卡。

风险与代价(含回滚)
只改文档,不动代码和 schema。卸载或升级被依赖的应用暂时不拦;单独发布的模块和主应用各有版本号,只靠声明的版本范围约束。回滚 = 撤销合并。

席位意见
建议合并。ADR 末尾列了十条起草人在裁决没说到的地方做的判断,契约复核逐条同意。其中两条最值得你看:第 9 条,把依赖从清单格式转成市场快照格式的工作,会让运行时卡比裁决估的 XS–S 大一点(ADR 提供拆成单独卡的选项);第 10 条,ADR 认为 B′ 改变了 #14488 裁决的前提(「发布物完整安装」),但那张卡的处置不变。

你要做的
读 ADR 末尾的十条判断;同意就批准或直接合并本 PR(批准账户为 os-zhuang / hotlong)。

席位核对

Seat domain:spec#4 (#18917) · session_01K2Tat8JEt4AhLASV4V4QWE · head fa7338085423 · 2026-10-25T02:44Z · verdict ACCEPT (Tier H: stays draft, the maintainer merges). This record supersedes the seat record 6390968082, which accepted the first draft at d9b22062.

  1. Shape. Draft, base main, attribution block then Fixes #25403; labels documentation, skip-changeset, needs-user-decision; 4 files under docs/adr/**, +637/−16 against main, under the 3000-line threshold; Tier H by check-governed-merges.mjs.
  2. Tier. Re-drafted on the project owner's order in the card thread (田騏, 2026-10-25T01:40Z: 「adr 这种任务都需要 fable extra,重新处理并立skills 卡片」) at CONTRACT_REVIEW_TIER, effort xhigh; the tiering card is pm-dispatch model tiering: a card whose surface touches docs/adr/** is dispatched at CONTRACT_REVIEW_TIER with effort xhigh, no one-line exit #25729. This supersedes the claim's Container & model line (6390714959).
  3. Contract review of record. docs/adr/** is governed rules text, so the PR owes an isolated CONTRACT_REVIEW_TIER review. PASS at 247fe2fa (6392433266, six non-blocking defects), all six fixed in fa733808, PASS on the delta at fa733808 (6392588282). Seat lapse, recorded here: the first ACCEPT (6390968082) was written without that review. It is corrected by these two records.
  4. Gates. Head-pinned check-runs on fa7338085423: 51 completed, none failed; Lint & Repo Gates, TypeScript Type Check, Test Core and Governed Surface Queue Guard success; Build Core skipped (docs-only paths filter). Dev dispatch-gates --commands 20/20 exit 0, --ran derived zero.
  5. Accepted deviations and out of scope. None beyond the ten listed merge questions; no out-of-scope findings to file.
  6. Landing. The PR stays draft, with no auto-merge and no queue. It lands by the maintainer's merge, or by an authorized APPROVED review from GOVERNED_APPROVERS (os-zhuang, hotlong), after which this seat lands it. After acceptance, the runtime card is cut per the ADR's Execution plan.

Eight changes from the independent review the maintainer accepted in full:

1. D2 second arm: a dependency the same artifact delivers is judged for
   presence only (packages carry their own manifest.version; the artifact
   is one tested unit); no warning at install, the in-artifact range
   mismatch is os lint's. TL;DR 3, merge question 1.
2. D5 read-from-either-end: the stronger reason — installPackage refuses
   any sitting owner outside the co-owner set, so HotCRM could never be
   upgraded once a module sits in crm; the free upgrade guard recorded.
3. D5 sibling arm: a sitting owner that declares the same parent (holding
   the namespace) is a co-owner, read from either end, judged by the D3
   uniqueness check too; keeps the second module independently installable.
4. D2 fail-closed paired with a stated range grammar (the tree's two
   readers accept less than npm's) and an author-side os lint/os validate
   rule; merge question 6, Execution plan card 2.
5. D8 item 1 (record-to-array snapshot conversion) is its own card, first
   step the control-plane reader; snapshots keep the record shape meanwhile.
6. Ruling 5645322259 read as unchanged by B′ (artifact installs whole; a
   standalone module is another artifact; B′ takes the carve-out the ruling
   named) in D6, Verification notes, merge question 10, 0130's addendum.
7. PATCH /api/v1/packages/:id: the post-install dependencies edit is
   deferred by name in D3 with interim state and trigger; merge question 11.
8. D7: the carved-out module is published as type: 'app' (ADR-0019 D2,
   isConsumerInstallable); HotCRM side not re-verified; ADR-0019 D1/D4
   interaction noted in one sentence; Open item 6.

Claude-Session: https://claude.ai/code/session_01HwUcNzGjPhV6xtYkJ469qK
Co-authored-by: Claude <noreply@anthropic.com>
F1  D5/D7: the depended-on app's companions must declare it — the registry
    keeps no artifact record after install (InstalledPackageSchema, scope
    not persisted), so a standalone module meets the app's other packages
    as foreign sitting owners and the app's next upgrade meets the module;
    stated as a precondition, Open item 7 for the HotCRM lane.
F2  D5: hydrate order is a requirement — the sys_packages hydrate and the
    install-local ledger register in list/readdir order and a refused row
    stays absent for the process; decided: both paths sort their batch by
    manifest.dependencies through resolvePluginOrder (ADR-0116's sorter);
    merge question 12; Open item 2 narrowed to cross-path order.
F3  D2: three range readers, not two — rangeAdmitsMajor reads the full
    grammar at major granularity, parseRangeFloor the floor only,
    SemanticVersionManager full versions over a narrow grammar with no
    caller; the handshake family in metadata-core is the reader to extend.
F4  0130 addendum (and header pointer) name the sibling arm.
F5  DUPLICATE_ARTIFACT_OBJECT_NAME keeps its code; message worded per
    co-ownership source.
F6  PATCH door reaches only authored packages (WRITABLE_PACKAGE_REQUIRED,
    ADR-0070 D2) and can delete the entry that is D5's proof; D3 row and
    merge question 11.
F7  Ruling 5645322259 read in two halves: artifact-installs-whole unchanged;
    the need the second half denied is met by another artifact.
F8  Amends/D6 name 0130's header pointer line beside the addendum.
F9  D7: HotCRM-side hooks and permission-set preconditions, not re-verified.
F10 Merge question 5 weighs the 409 precedents and keeps 422.
F11 kernel:ready seals folded into the hydrate-order requirement.

Claude-Session: https://claude.ai/code/session_01HwUcNzGjPhV6xtYkJ469qK
Co-authored-by: Claude <noreply@anthropic.com>
N1 D5 hydrate sort: a cycle in a batch degrades its cyclic members to
   source order with a logged refusal and never throws out of the hydrate;
   resolveArtifactPackageEntries' artifact-level cycle throw is not
   inherited (both hydrate paths stay per-entry tolerant). Merge question
   12 and Execution plan card 1 say the same.
N2 Install-local sort nodes are ledger entries: a declared id resolves to
   the entry carrying that package; ids inside one entry are not edges.
N3 D2 reader: the exact-version reader is a sibling function in the
   handshake's module; the handshake's verdicts and ADR-0087 D1's
   never-false-rejection contract are unchanged.
N4 Runtime card restated as S (the ruling's XS–S cited as the ruling's
   estimate for the check alone) in the Execution plan, merge question 9
   and Consequences.

Claude-Session: https://claude.ai/code/session_01HwUcNzGjPhV6xtYkJ469qK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Independent full review (owner's order, 2026-10-25)

Head reviewed last: 02456273c9a1202c6ddf36e5e3daec634d833869 · Verdict: PASS

The project owner ordered, on 2026-10-25, that spec, skills and ADR changes owe a complete review at CONTRACT_REVIEW_TIER beyond the contract review (「spec skills adr 都需要fable 完整的复核」, card #25765). This PR's review ran at that tier, effort xhigh, in agents separate from the drafter. They were fed the record, the rulings and the cited code, not the drafter's conclusions. All runs were read-only. Scope: the four docs/adr/** files, reading the code they cite.

Round Head Verdict Findings Landed in
1, full fa733808 changes advised 3 must-fix (co-delivered dependency judged for range; HotCRM upgrade refused without read-from-either-end; sibling modules refusing each other), 3 rewrites, 2 gaps (post-install PATCH; the standalone module's type); the owner chose 「全部照改」 2bf30f03
2, full 2bf30f03 PASS WITH FIXES F1 must-fix (the app's companion crm packages must declare it; the registry keeps no artifact record), F2 to F7 should-fix (hydrate order, three range readers, 0130 addendum sibling arm, cross-artifact error message, PATCH reach and deletion, the 5645322259 reading), F8 to F11 notes 5c4071ef
3, delta 5c4071ef PASS WITH FIXES N1 must-fix (a cycle in a hydrate batch must not throw out of the hydrate), N2 to N4 notes (ledger entries as nodes, handshake verdicts untouched, runtime card sized S) 02456273
4, delta 02456273 PASS one note for the runtime card: resolvePluginOrder names one cycle member per throw, so the card iterates the one sorter until the batch orders —

Every code claim the record makes was checked at origin/main 47af3dde42. The hotcrm and cloud repositories are not readable from the reviewing session, so the HotCRM-side preconditions stay marked not re-verified in the record (D7, Open item 7). The record's twelve merge questions are what the maintainer accepts by merging.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 02456273c9a1202c6ddf36e5e3daec634d833869
Local-runs: none
Scope: docs/adr/0019-app-as-consumer-unit.md, docs/adr/0028-metadata-naming-and-namespace-isolation.md, docs/adr/0130-release-artifact-as-co-ownership-boundary.md, docs/adr/0141-inter-app-dependency-declared-checked-before-install.md + packages/metadata-core/src/protocol-handshake.ts, packages/metadata-core/src/artifact-forward-conversion.ts, packages/core/src/dependency-resolver.ts, packages/core/src/index.ts, packages/core/src/plugin-order.ts, packages/core/src/artifact-packages.ts, packages/runtime/src/domains/packages.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/objectql/src/engine.ts, packages/metadata-protocol/src/protocol.ts, packages/services/service-package/src/index.ts, packages/cloud-connection/src/marketplace-install-local-plugin.ts, packages/cloud-connection/src/local-manifest-source.ts, packages/spec/src/kernel/package-registry.zod.ts, packages/spec/src/kernel/plugin.zod.ts, packages/spec/src/stack.zod.ts, packages/cli/src/utils/protocol-version-gap.ts, packages/cli/src/commands/validate.ts, examples/app-multi-package/src/packages/core/index.ts, examples/app-multi-package/src/packages/orders/index.ts, examples/app-multi-package/objectstack.config.ts, package.json, docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md, docs/adr/0048-cross-package-metadata-collision.md, docs/adr/0070-package-first-authoring.md, docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md, docs/adr/0087-metadata-protocol-upgrade-contract.md, docs/adr/0112-error-code-vocabulary-and-ledger.md, docs/adr/0116-plugin-ordering-declared-contract.md, docs/adr/0129-object-name-is-the-canonical-id.md, and the cited-code set of the 247fe2fa record (comment 6392433266) for everything this head leaves untouched

Third record on this PR, a delta review on top of comments 6392433266 (247fe2fa, PASS) and 6392588282 (fa733808, PASS). Three commits since fa733808 — 2bf30f03, 5c4071ef, 02456273 — driven by the independent full review whose summary is PR comment 6396577677 (read as a claim). Delta fa733808..02456273: 2 files, +323/−109 — ADR-0141 (+311/−102) and the ADR-0130 addendum (+12/−7, three hunks). ADR-0019 and ADR-0028 are byte-identical to fa733808; the tree outside docs/adr/ is identical to the base 47af3dde42; the net diff against main is +851/−16 over the same four files. Inputs: card #25403 body and all 8 comments (one new since round one, 6392594289, the seat's ACCEPT at fa733808 — not a ruling), PR #25717 body as refreshed at 2026-10-25T10:36Z, the delta and the net diff, the 53 check-runs on 02456273. Code read at origin/main f0cf338330, which has moved since the earlier records' reading: protocol.ts, engine.ts, registry.ts and packages.ts changed in regions away from the cited symbols, and none of the moves touches a cited behaviour — resolveWritePackageScope (now line 7387, transitive closure, "still IN the closure" comment intact), registerApp(body, scope?, source) (line 9261), the namespace gate and refuseCoOwnedObjectNameCollision (main's registry.ts hunks sit at lines 47, 153 and 5160 only), the PATCH door's requireWritablePackage(…, 'edit') — so every anchor the earlier records verified still holds at this main.

① Derived judgments

A. Every changed hunk, judged against the code and the rulings.

ADR-0141 header. "Amends" now names the 0130 status-header pointer line "the form 0130's 2026-09-22 line set" — right, 0130's header carries such lines for its 2026-09-02 and 2026-09-22 addenda. "Consumers" adds packages/lint for the os lint / os validate range rule — consistent with D2 and Execution plan 2.

TL;DR 3–5. Co-delivered dependency "presence only: the artifact is one tested unit"; the range grammar "stated by the runtime card"; author tooling reports an unreadable range before install; item 4 adds the sibling arm and its two conditions; item 5 adds a sixth deferred item (a post-install edit of dependencies). Each is restated in D2/D3/D5 and in merge questions 1, 4, 6, 11, 12 — consistent.

Context §1 — SemanticVersionManager and rangeAdmitsMajor. "Written for kernel plugins … no caller in packages/ outside its own file and test": verified — git grep SemanticVersionManager origin/main -- packages outside dependency-resolver.ts and its test hits only packages/core/PHASE2_IMPLEMENTATION.md (a document); the barrel packages/core/src/index.ts line 212 re-exports the module wholesale (export * from './dependency-resolver.js') without a caller. "rangeAdmitsMajor reads engines.protocol at major granularity": verified — every arm of alternativeAdmitsMajor / comparatorAdmitsMajor reduces to leadingMajor (^N → runtimeMajor === maj; ~N the same; comparators compare majors). Right.

Context §1 — in-repo population. core/index.ts line 19 and orders/index.ts line 58 both declare version: '1.0.0'; examples/app-multi-package/objectstack.config.ts line 68 is composeStacks([ordersStack, coreStack], { manifest: 'preserve' }); packages/spec/src/stack.zod.ts documents 'preserve' as folding each stack's metadata onto its own manifest (lines 1593, 1804). Right.

D2 arm 2 — presence only for a co-delivered dependency; range for an installed record. The reasoning rests on the facts above (each package carries its own manifest.version; composeStacks keeps them) and on ADR-0130 D6 (one artifact, one tested unit) — both verified. The in-artifact mismatch is routed to os lint (Execution plan 2). A drafter decision, named as merge question 1. Right and consistent.

D2 — "The range reader, and the grammar it must state". Verified claim by claim at origin/main: (a) no package under packages/ imports semver; the root package.json lists "semver": "^7.8.5" under devDependencies (line 230 inside the block opened at 224) — right; (b) rangeAdmitsMajor / alternativeAdmitsMajor (protocol-handshake.ts) read *, x, latest, ^, ~, the N.x / N.* wildcard, || unions (rangeAlternatives), space- or comma-joined comparator intersections, hyphen ranges and bare versions, at major granularity — right, and "^6.2.0 against 6.1.0 answers true" follows from runtimeMajor === maj; (c) parseRangeFloor's docstring says "the same 'leading token' school as the handshake's rangeAdmitsMajor" and returns a floor only — right, verbatim; (d) SemanticVersionManager.parse requires three numeric parts (/^(\d+)\.(\d+)\.(\d+)…$/, throws "Invalid semantic version"), so ^6 throws; satisfies handles */latest, an exact [\d.]+, one leading caret, tilde or single relational comparator (greater-or-equal, greater, less-or-equal, less), and a - b; 6.x matches no arm and ^1 || ^2 reaches parse('1 || ^2') — "answers false or throws" is right; (e) packages/cli/src/utils/protocol-version-gap.ts#checkProtocolVersionGap exists and validate.ts line 777 calls it — right. The decision to extend the handshake family in packages/metadata-core as a sibling function with the handshake's verdicts untouched, plus an author-side rule, is the drafter's; it is named in merge question 6 and the never-false-rejection contract of ADR-0087 D1 is kept explicitly. Right.

D2 — re-registration paragraph. Adds "The order a boot registers in is D5's requirement, because the namespace gate does run there." Right: the sys_packages hydrate calls registry.installPackage(…, 'authored') (service-package/src/index.ts line 751) and the gate sits inside installPackage.

D2 — "Nor the manifest edit". Verified at origin/main: the PATCH handler calls requireWritablePackage(deps, qlService, id, 'edit') before anything else; requireWritablePackage answers 422 WRITABLE_PACKAGE_REQUIRED with the message "(provided by code or an installed app)" and cites ADR-0070 D2 (isWritablePackage); the parsed dependencies row "REPLACES the stored one whole — removing an entry … ⛔ No in-use check is made here". Right; the deferral is merge question 11.

D3 table, sixth row. Interim state and trigger follow from the PATCH facts above; "a deleted entry removes D5's proof of co-ownership: the next related registration … is refused at the namespace gate naming the other package" is correct under D5's relation. Nothing the rulings chose is removed; the five ruled deferrals stand. Right.

D5 — sibling arm. Definition is precise (S and P each directly declare one installed Q whose namespace is N). The justification is correct against the gate: installPackage refuses when getNamespaceOwners(ns).find(…) finds any owner that is neither manifest.id nor in coOwners — verified at registry.ts — so under letter A's literal relation the second module of one app (marketing beside revenue, both declaring sales) would be refused. This is the largest widening of ruling 6377678758's literal relation ("a package that declares dependencies on an installed package X … is treated as X's co-owner"); it adds no field, no closure walk, and serves the B′ ruling's stated aim 「每个包可以独立安装」. Named as merge question 4. Right.

D5 — read from either end, with the upgrade argument. The new reason is correct and stronger than round one's: once revenue sits in crm, a HotCRM upgrade or reinstall arrives second and, read from P's side only, HotCRM itself meets NamespaceConflictError. The "free upgrade guard" (an upgrade of X newly defining a crm_* name the module owns is refused by refuseCoOwnedObjectNameCollision) follows mechanically from the pair reading one set. It is not the deferred "upgrade interception" (which is about a dependant's range) and the text says so. Right.

D5 — error message per co-ownership source. ArtifactObjectNameConflictError's message today reads "Object name conflict inside one release artifact … delivered by the same artifact" — verified round one; keeping the code DUPLICATE_ARTIFACT_OBJECT_NAME and rewording per source is runtime-card detail, recorded. Right.

D5 — companions must declare the app. Verified: InstalledPackageSchema carries manifest, source, status, enabled and no artifact field (git grep -i artifact in package-registry.zod.ts hits only a header comment); packages/objectql/src/plugin.ts line 768 says "nothing persisted — the claim cannot outlive" the artifact. So the registry cannot infer a companion, and both failure cases (module install refused naming service; HotCRM upgrade refused naming revenue) are real under the gate. The precondition is marked not verified in HotCRM (Open item 7) and surfaced in the PR body. Right.

D5 — hydrate order is a requirement (merge question 12). Verified: service-package/src/index.ts line 701 iterates packageService.list() and on an installPackage throw logs "was NOT hydrated into the registry" (line 755), boot continuing ("one stale package must not brick the environment", line 710–711, verbatim); the install-local rehydrate iterates entries from readAll() in order with no sort, and local-manifest-source.ts#list walks readdirSync(this.dir) (line 219); the two kernel:ready seals (picklistVocabularySealed, boundActionTargetsSealed) require an arriving artifact to bind to "an object the registry holds or one the artifact itself declares" (plugin.ts lines 798–801); resolvePluginOrder throws on cycles "through either edge kind" (plugin-order.ts line 63) naming one member (Circular dependency detected: ${pluginName}, line 75), and resolveArtifactPackageEntries keeps that throw for one artifact. The decision — both hydrate paths sort their batch through resolvePluginOrder with declared ids as optional edges, ledger entries as nodes, cyclic members falling back to source order with a logged refusal, never throwing out of the hydrate — is new boot-time mechanism beyond "one check + one co-owner source"; it is derived from letter A (the gate must hold at re-registration) and is the alternative to admitting a sibling without its parent, which the text rightly refuses. It is not the deferred "topological / cascade install" (an install-time act across artifacts); the D3 row stands. Named as merge question 12. Right.

D6 — ADR-0130 bullet and the #14488 reading. Now a two-halves reading: "an artifact installs whole" unchanged (ADR-0130 D2 — verified: "The consumer still installs one thing … the App you installed brought its modules"); the need the second half denied is met by another artifact, the carve-out path ruling 5645322259 itself names ("If a module is ever carved out as its own artifact, it ships its own sets" — verified in the comment body). Softer and more accurate than round two's "has since moved". Quotation still byte-identical. Right; merge question 10.

D7 — type: 'app' and three preconditions. isConsumerInstallable exists at packages/spec/src/kernel/plugin.zod.ts line 116; ADR-0019 D2 says only app is consumer-installable and ADR-0130 D2 says the other packages in an artifact are type: module / plugin — so a module carved out as its own consumer-installable artifact changes its type: a derived consequence of two standing records, not a new decision, correctly left to Open item 6 and the HotCRM lane. The hook precondition restates ADR-0130 §1.5 / stack.zod.ts (verified round one); the permission-set precondition restates ruling 5645322259 and ADR-0086 D4 (verified). Right.

D8. The conversion moves to its own card whose first step reads the control plane's parser of manifest_json (not readable here); snapshots keep the record shape meanwhile, and "D2 reads the manifest record, never the snapshot" — consistent with D2. Merge question 9. Right.

Consequences, Open items 2/6/7, Verification notes, Execution plan (five items; runtime card S, author-side XS, snapshot S), References. Consistent with the decisions above; References add local-manifest-source.ts, composeStacks, checkProtocolVersionGap, isConsumerInstallable, core/index.ts — all verified to exist. One internal sizing nit, see ⑬.

Merge questions 1–12. Items 1, 2, 3, 4, 5, 6, 9, 10 rewritten; 11 and 12 new. Item 5's precedents verified: NAMESPACE_CONFLICT and DUPLICATE_ARTIFACT_OBJECT_NAME carry status = 422; INVALID_ARTIFACT_PACKAGES / INVALID_ARTIFACT_PACKAGE_ENTRY are thrown as ADR-0112 envelopes with status: 422 (artifact-packages.ts lines 232–234, 256, 285, 308); install-local answers 409 RESOURCE_CONFLICT for a namespace a writable base holds (header line 57, code at 1642) and MANIFEST_CONFLICT at 1659; PackageUninstallExtendedError is RESOURCE_CONFLICT/409. Item 6's "6.x reads as nothing to SemanticVersionManager" — right (no arm matches). Item 9 cites the rulings' sizing correctly (ruling 6369704387: "one runtime card for the pre-install check (XS–S)"; ruling 6377678758: "objectql S"). Item 11's "one new gate" reading of B′ is faithful (ruling item 2: "Pre-install check, and only that gate"). Internal references resolve (D2 → "merge question 6"; D5 → "merge question 12"; Open items 2/6/7).

ADR-0130, three hunks. Status-header pointer line gains "(and its declared siblings' — ADR-0141 D5)"; the D8 paragraph of the addendum adds "plus siblings under one declared parent that holds the namespace, as ADR-0141 D5 defines"; the #14488 paragraph is rewritten to the two-halves reading with the quotation byte-identical. All three say only what ADR-0141 D5/D6 say; zero deletions of original text anywhere in 0130; D1–D8, §1–§8 and both earlier addenda untouched. Right.

B. The rulings. Ruling 6369704387 (B′): items 1–4 all present and unmoved — declaration unchanged (D1), the pre-install check as the only new gate (D2; the hydrate sort and the author-side lint rule are not gates on an install, and the text does not call them one), all five ruled deferrals still deferred by name (D3 rows 1–5, with a sixth the drafter adds), permissions unchanged with the open section (D4); workload list executed as before (0028 D4 rewrite, 0019 cross-reference, D8 alignment, D7 first consumer, composite products as one artifact meanwhile). Ruling 6377678758 (A): D5 keeps dependency-declared co-ownership, one-way, no new field, the D3 pair across artifacts, the administrator's install act as the in-tenant trust, and D8's deferral ending at marketplace publishing; letters B and C still not chosen. Nothing chosen is missing; nothing deferred or rejected is decided — the owner/publisher field, uninstall protection, range-based upgrade interception, install-time topological/cascade install, the publish marker and marketplace negotiation all stay deferred.

C. New drafter decisions, each named. (1) co-delivered presence-only → merge question 1; (2) stated grammar, handshake-family reader, author-side rule → 6; (3) sibling arm → 4; (4) PATCH deferral as a sixth D3 item → 11; (5) per-path hydrate sort with cycle fallback → 12; (6) D8 conversion as its own card, runtime card S → 9 and Execution plan; (7) 422 against the 409 precedents → 5; (8) two-halves reading of 5645322259 → 10. Derived consequences stated but not listed as choices, correctly: the standalone module's type: 'app' (ADR-0019 D2 + ADR-0130 D2; Open item 6), the free upgrade guard (D5), the companion precondition (D5, D7, Open item 7, PR body), the per-source error message. Twelve named; none missing.

D. Check-runs on 02456273 (read, not run). 53 runs, all completed, none in progress, no failure: 37 success, 16 skipped (path filters on a docs-only diff, opt-in harnesses, and the label-driven changeset check). Required contexts: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (8 shards, all success), Governed Surface Queue Guard success, Build Core skipped (paths filter; docs-only diff). Governance: The card this PR closes must claim this branch, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Check PR Size, Check Documentation Links, Lint · PM dispatch-gates — all success (a second, skipped run of Check PR Size / Auto Label / Check Changeset also exists). Check Changeset skipped (label).

② Semver level

Unchanged: docs/adr/** only (4 files); nothing published; no changeset in the diff and none owed. skip-changeset label present; Check Changeset skipped by it. PR body carries Clause-②: no and Metadata-protocol: none, matching the dispatch claim 6390714959 and the diff. Level: none — correct.

③ Boundary flags

The twelve drafter judgements the ADR lists for the merge decision:

  1. Co-delivered counts as present, presence only — agree: the example's packages each carry version: '1.0.0' and composeStacks(…, { manifest: 'preserve' }) keeps them, so a range check inside one artifact would refuse a unit ADR-0130 D6 says is tested whole; os lint is the right place for the mismatch.
  2. Three install acts only; never boot, never the PATCH — agree; the costs are the deferred items.
  3. Read from either end — agree; the upgrade argument is decisive (HotCRM could not be upgraded once a module sits in crm), and the free upgrade guard is a consequence worth the Console text it asks for.
  4. Direct-only with the sibling arm — agree; this is the widest departure from letter A's literal wording and the text says why the literal reading breaks the second module. The maintainer should read this one.
  5. 422 — agree; the registry-gate family answers 422 and the 409 precedents are correctly characterised as conflicts that hold until a resource changes.
  6. Fail-closed with a stated grammar; extend the handshake family; author-side rule — agree; verified that no reader in the tree has both the documented grammar and exact-version granularity, so fail-closed without the pairing would refuse the ruling's own 「HotCRM 6.x」.
  7. Required entries; optional → false — agree.
  8. Own entries only — agree.
  9. Snapshot conversion as its own card; runtime card S — agree; the control-plane reader must be read first.
  10. Two-halves reading of 5645322259 — agree; it no longer claims the earlier ruling "moved", and the carve-out path is the ruling's own sentence.
  11. PATCH edit deferred, not a fourth door — agree; faithful to B′'s "only that gate", and the door reaches only authored packages.
  12. Each hydrate path sorts its batch — agree with the reasoning (the alternative admits a sibling without its parent; the hydrates are per-entry so the cycle must not throw); flagged as the one item that adds boot-time mechanism beyond the ruling's "declaration + one check + one co-owner source", so it is the item most worth the maintainer's explicit yes.

Independent review 6396577677 (a claim) — its table says F1–F11 and N1–N4 landed in 5c4071ef / 02456273 and the head is PASS; the delta's content matches that description, and its one runtime-card note ("resolvePluginOrder names one cycle member per throw") is right (plugin-order.ts line 75). Dev claims relayed by the coordinator — twelve merge questions, PR body updated — verified.

PR body vs head: 维护者速读, 你要做的 (twelve items with the HotCRM precondition), the four "What changed" rows, the Measurements bullets (range readers, PATCH, hydrate order, example versions) and the Acceptance notes all describe 02456273; additions/deletions 851/16 match git diff --stat. One stale number: the Gates paragraph still says "4 files, +637 / −16" for the change set at head 02456273, which is +851/−16 — see nit ⑫. The body's gate counts (20 derived, 20 run, 2473 anchors) are claims; the check-runs above are the verdicts.

Nothing escalated. Nits for the seat, none moving the verdict: ⑫ PR body, Gates paragraph: "+637 / −16" should read "+851 / −16" for head 02456273; ⑬ ADR-0141 Consequences says "one runtime card of size S and two smaller ones" while Execution plan item 3 sizes the snapshot-alignment card S (not smaller) — say "two further cards (XS, S)"; ⑭ ADR-0130 status-header line "across artifacts (and its declared siblings' — ADR-0141 D5)" reads awkwardly — "across artifacts, and so is a sibling under the same declared parent (ADR-0141 D5)".

Implemented-by: claude/project-thread-uu2cmp
Reviewed-by: session_01K2Tat8JEt4AhLASV4V4QWE

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么
新增 ADR-0141(Proposed),把 #25403 的两次裁决写成契约:应用用现有的 manifest.dependencies 声明依赖,引用沿用保存门的依赖闭包;唯一新增的关口是安装前检查(已装的依赖要在版本范围内;同一发布物里一起交付的依赖只查在不在),不满足就一次性列出缺的依赖并拒绝安装(422,dependency_missing);声明了依赖且命名空间相同的包,在安装门上与被依赖方共有命名空间。推迟项六条(卸载保护、升级拦截、拓扑/连带安装、被引用方发布标记、市场版本协商、安装后改 dependencies),权限不变。配套:ADR-0028 D4、ADR-0019 交叉引用、ADR-0130 附录。本版经过你要求的独立 Fable 完整复核四轮(PR 评论 6396577677)和 Fable 契约复核三轮,最后一轮在本 head 通过。

为什么改
你裁了 B′ 和命名空间 A,目标是「每个包可以独立安装」。今天安装时不检查依赖,单独发布的 HotCRM 模块还会因为共用 crm 命名空间被拒。

风险与代价(含回滚)
只改文档。运行时卡定为 S,另有两张小卡(作者侧版本范围检查 XS、市场快照格式对齐 S)。卸载、升级被依赖的应用暂时不拦。回滚 = 撤销合并。

席位意见
建议合并。ADR 末尾列了十二条起草人在裁决没说到的地方做的判断,两轮复核都逐条同意。请重点看三处:

  • 第 4 条「兄弟模块」:同一个应用的两个模块(例如 revenue 和 marketing 都依赖 sales)彼此也算共有命名空间。这超出了命名空间 A 的字面意思,但不这样第二个模块装不上。
  • 第 12 条「开机按依赖排序」:两条开机恢复路径要按依赖顺序注册,有环时退回原顺序、不中断开机。这是裁决之外新增的开机机制。
  • HotCRM 的前提:HotCRM 应用里其他共用 crm 的包必须声明依赖这个应用,否则模块装不上、HotCRM 也升级不了。这一条需要 HotCRM 那边确认,本仓读不到 HotCRM 的代码。

你要做的
读 ADR 末尾的十二条判断,特别是第 4、12 条;同意就批准或直接合并本 PR(批准账户为 os-zhuang / hotlong)。

席位核对

Seat domain:spec#4 (#18917) · session_01K2Tat8JEt4AhLASV4V4QWE · head 02456273c9a1 · 2026-10-25T10:54Z · verdict ACCEPT (Tier H: stays draft, the maintainer merges). This record supersedes seat record 6392600749 (head fa733808).

  1. Shape. Draft, base main, attribution block then Fixes #25403; labels documentation, size/l, skip-changeset, needs-user-decision; 4 files under docs/adr/**, +851/−16, under the 3000-line threshold; Tier H. The PR body's Gates line was corrected by the seat from +637 to +851 (relay issue_patch, read back identical).
  2. Independent full review (the owner's rule 「spec skills adr 都需要fable 完整的复核」, card pm-dispatch review: a PR touching published spec, skills or docs/adr/** owes an independent full review at CONTRACT_REVIEW_TIER before the seat's ACCEPT, not only the contract review #25765): four rounds at CONTRACT_REVIEW_TIER, effort xhigh, separate from the drafter. The last one is PASS on this head, and the summary is 6396577677.
  3. Contract review of record: PASS at 247fe2fa (6392433266), at fa733808 (6392588282) and on this head (6396737746), all three at CONTRACT_REVIEW_TIER. All twelve drafter decisions are named among the merge questions. Nothing ruled is missing, and nothing deferred is decided.
  4. Gates. Head-pinned check-runs on 02456273c9a1: 53 completed, 37 success, 16 skipped, none failed; Lint & Repo Gates, TypeScript Type Check, Test Core and Governed Surface Queue Guard success. Dev dispatch-gates --commands 20/20 exit 0.
  5. Accepted as is (not worth another review round): two wording nits from 6396737746. Consequences says "two smaller" cards where Execution plan item 3 is sized S, and the ADR-0130 status-header line "(and its declared siblings' — ADR-0141 D5)" reads awkwardly. Both ride the next edit of either file. No out-of-scope findings to file.
  6. Landing. The PR stays draft, with no auto-merge and no queue. It lands by the maintainer's merge, or by an authorized APPROVED review from GOVERNED_APPROVERS, after which this seat lands it. After acceptance the seat cuts the cards in the ADR's Execution plan.

@os-zhuang
os-zhuang marked this pull request as ready for review October 25, 2026 11:33
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit afe6ab9 Oct 25, 2026
61 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-uu2cmp branch October 25, 2026 11:55
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/l 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.

[Decision] 一个应用能否声明依赖另一个应用并引用其对象(ADR-0019 推迟的应用间依赖)

3 participants