Repository navigation
feat(install): a declared dependency co-owns its parent's namespace, and both boot hydrates sort by it (ADR-0141 D5) - #26054
Conversation
…pace, and both hydrates sort by it (ADR-0141 D5) The install gate's co-owner set gains a second source beside the artifact: a package declaring an installed X that holds its namespace, a sitting owner declaring the incoming package, and a sibling under one declared parent. The object-name check reads the same set and words its refusal per source. The sys_packages hydrate and the install-local rehydrate order their batch by manifest.dependencies through resolvePluginOrder; a cycle keeps source order for its members and is logged. Docs and changeset included. Also carries the D2 review notes: an undecided union with a readable installed version reports unreadable-range, and install-local no longer counts the artifact wrapper's id as delivered. Claude-Session: https://claude.ai/code/session_01NndsH9Qm7k6AS2i2fWbjNA Co-authored-by: Claude <noreply@anthropic.com>
…e builds a SchemaRegistry Claude-Session: https://claude.ai/code/session_01NndsH9Qm7k6AS2i2fWbjNA Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 40 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin aeedc7f4bff2e540697e0a16fec0bd2a225bb90f && git checkout aeedc7f4bff2e540697e0a16fec0bd2a225bb90f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 431f065177de0cfc9d682b3b18692132390e0c57 10c01a418105fedab936a4b17a343fecf621fe04 && git checkout -B drift-repro 431f065177de0cfc9d682b3b18692132390e0c57 && git merge --no-ff 10c01a418105fedab936a4b17a343fecf621fe04
node scripts/docs-audit/affected-docs.mjs --json 431f065177de0cfc9d682b3b18692132390e0c57
|
Contract reviewServed-tier: ① Derived judgmentsCI state at review time (2026-10-26T10:21Z, 47 check-runs on the head). 25 concluded A. The co-owner map and its three sources — right.
The relation is one-way in who declares (X declares nothing) and symmetric in who is recognised, as the ADR states. Nothing is persisted; the map is rebuilt from the registry's current rows on every install, so a Wording per source — right for all three. B. Is the degrade acceptable under the card's rulings? Yes, it is what they prescribe, not a departure from them. Card item 5: "A cycle degrades to source order with a logged refusal and never throws out of the hydrate." ADR-0141 D5: "The cyclic members degrade to source order for that batch, with the cycle logged as a refusal naming them; the acyclic rest keeps its sorted order, and nothing throws out of the hydrate." Merge question 12: "The sorter's cycle throw is not inherited: cyclic members of a batch fall back to source order with a logged refusal, and the hydrate never throws — the per-entry tolerance both paths have today stands." Both callers log at C. The two #25986 review notes — right. (i) D. New public surface — right. E. The vitest env line — right and machine-required. F. Claims outside these inputs. The HotCRM precondition paragraph (every ② Semver levelLevels. The changeset introduces
The narrowing is of an unreleased surface — confirmed from the in-repo release state, so it is legal under the arm with no migration owed.
Carriers. The changeset carries the ③ Boundary flags
Dev flags and out-of-scope findings.
Card #25792 item ledger, with this slice plus #25986 (merged
So this slice plus #25986 closes items 1, 2, 4, 5 and 7. Item 6 is #26000's. Item 3 is the one item neither PR closes and #26000 does not carry: it lives outside this repository by the triage's routing. First readings 2 and 6 are stated as residual readings, and reading 4 is unreported. Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT ·
|
| item | reading |
|---|---|
| Shape | draft, base main, Part of #25792 (the card keeps items 3 and 6 open, see below), Clause-②: yes (narrowing) for the unreleased D2 install-local narrowing, Metadata-protocol: none; 17 files across core, objectql, metadata-core, cloud-connection and service-package; no packages/spec/** path. |
| Item 4 (D5 co-owners) | SchemaRegistry.installPackage computes one co-owner map (artifact, dependency read from either end, sibling) and both halves of the ADR-0130 D3 pair read it, so the relaxation and the refusal cannot disagree about who counts; the refusal is worded per source. |
| Item 5 (D5 order) | orderHydrationBatch in core orders both the sys_packages hydrate and the install-local rehydrate through resolvePluginOrder; a cycle degrades and is logged. |
| #25986 notes | the unreadable-range case on an undecided union is found; the wrapper id no longer counts as delivered (deliveredIds removed, unreleased). |
| Evidence | red first on the three new suites, 8 ablations red; 106/106 derived gates exit 0 (check-registry-log-declared found a real miss, fixed); five packages green after a full build. Auto-merge holds for the head's Test Core / Type Check / Build Core runs. |
| Card after this slice | items 1, 2, 4, 5, 7 closed (with #25986). Item 6 is #26000 (domain:spec). Item 3, the Console refusal text, was routed to objectui by triage 6398501495; objectui now lives in this repository under ui/, so the seat carries it on this card as the remaining server-side-to-UI slice. |
| Carried | the dogfood authz-probe-blind-spot red was main's own, fixed on main by #26002. |
| Landing | 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready + auto-merge through the relay. |
Generated by Claude Code
Requested by Leehom · project thread
Part of #25792
Clause-②: yes (narrowing)
Metadata-protocol: none
Summary
ADR-0141 D5, card items 4 and 5, plus the two review notes from #25986.
Item 4: a declared dependency is a second co-owner source (
packages/objectql/src/registry.ts).SchemaRegistry.installPackagebuilds one co-owner map,coOwnersOf, and both halves of the ADR-0130 D3 pair read it: the namespace gate andrefuseCoOwnedObjectNameCollision. For a non-shareable namespace N used by the incoming package P, the map holds:artifact: the artifact's other packages, exactly as before;dependency: an installed X that P declares and that holds N, or a sitting owner of N that declares P. This is the same relation read from the other end, so an upgrade or reinstall of the app beside its modules, or a boot that hydrated a module first, is admitted;sibling: a sitting owner S of N, when S and P each directly declare one installed package that holds N.Only direct declarations count. The shared parent's namespace is read from its installed record, so two packages that declare one common package co-own nothing unless that package is installed and holds N.
ArtifactObjectNameConflictErrorkeepsDUPLICATE_ARTIFACT_OBJECT_NAME/ 422. Its message is now worded per source (same artifact, declared dependency, sibling), so a cross-artifact refusal never claims one artifact, and it carriescoOwnership.NAMESPACE_CONFLICTnow names the declaration as the fix.Item 5: hydrate order (
packages/core/src/hydration-order.ts, new).orderHydrationBatchsorts a batch withresolvePluginOrder, using declared ids as optional edges. This is the readingresolveArtifactPackageEntriesalready gives the key; there is no second sorter. A node lists every package id it carries. A cycle's internal edges are dropped before sorting, found with Tarjan's algorithm, so its members keep source order while still following their acyclic dependencies. The cycles are returned for the caller to log, and nothing throws. Both hydrates use it:sys_packageshydrate inpackages/services/service-package/src/index.ts, where one row is one node;kernel:readyrehydrate inpackages/cloud-connection/src/marketplace-install-local-plugin.ts#inDependencyOrder, where one ledger entry is one node. A declared id resolves to the entry that carries that package, and an id declared between packages of one entry is no edge.declaredDependencyIds(packages/core/src/artifact-packages.ts) is the one record-only reading ofmanifest.dependenciesthat the registry and both hydrates share.Review notes from #25986:
<5 || latestagainst7.0.0) now reportsfound: 'unreadable-range'instead of naming the version.DeclaredDependencyCheck.deliveredIds, which had no other caller, is removed.Docs:
content/docs/deployment/app-dependencies.mdxnow says that two modules of one app need not declare each other. It also explains namespace co-ownership by declaration and its object-name rule, the companion precondition, and the boot order with its cycle log.plugin-spec.mdxconsumer callout now names five consumers.Declarations
Clause-②: yes (narrowing). The widening: install acceptance (packages previously refusedNAMESPACE_CONFLICTnow install) and new@objectstack/coreexports (orderHydrationBatch,declaredDependencyIds,HydrationNode,HydrationOrder). The narrowing: install-local refuses a module that declares only its artifact wrapper's id, and thedeliveredIdsoption is removed. Both of those belong to the D2 check, which #25986 landed and which is still unreleased (its changeset is pending on main). The changeset ispatch, with a BREAKING banner and an ADR-0087not-required (no-migration-prescription)marker.Metadata-protocol: none. No key, accepted value or metadata type changes in what an author can write.Tests
Red first: each new suite failed on main before its wiring.
packages/objectql/src/registry-artifact-co-ownership.test.ts, new describe extending the ADR-0130 D3 gate test, real load path throughmanifest.register(); the sixth case is a negative controlpackages/services/service-package/src/hydration-dependency-order.test.ts, realSchemaRegistrypackages/cloud-connection/src/marketplace-install-local-rehydrate-order.test.tspackages/core/src/hydration-order.test.tsprotocol-handshake-dependencies.test.tsandmarketplace-install-local-dependency-missing.test.tsThe new objectql cases cover:
crm_accountis refused;Every refusal asserts
codeandstatus.Package suites on this branch after a full build (119/119 tasks), all typecheck exit 0:
Ablations (
scripts/ablation-replace.mjs, each restore checked against HEAD):sys_packageshydrate in list orderGates:
node scripts/pm/dispatch-gates.mjs --ranaccounts for all 106 derived families, all exit 0. One found a real miss:check-registry-log-declaredflagged the service-package suite that now builds aSchemaRegistry, andvitest.config.tsnow declaresOS_REGISTRY_LOG. Scopedeslinton the touched files exits 0, andcheck:nul-bytesexits 0.Full dogfood on
968c670094: 2522 passed, 18 skipped, 1 failed (VERDICT command-exit 1). The head10c01a4181adds only the service-packagevitest.config.tsenv line. The failure isauthz-probe-blind-spot.test.ts, thepackages/runtime/src/domains/i18n.tsrow (handleI18nRequestexpected 3, got 4). That is main's red since #25956, reported on #25986 too; this PR touches neither file.Acceptance notes
ui/**edit, and item 6 is spec: retire the two unread dependency spellings, PackageDependencySchema (withoptional) and AppManifestSchema.dependencies, now that ADR-0141 D2 judges manifest.dependencies alone #26000.d91c7b0, everycrmcompanion (service, marketing, revenue) declaresapp.objectstack.hotcrm, so the sibling arm admits each of them beside the app and beside each other.kernel:readyseals and the ledger rehydrate both run onkernel:ready, in hook-registration order. The install-local plugin declares no ordering edge against ObjectQLPlugin. One cross-path case stays unordered by design (ADR-0141 Open items 2): two siblings insys_packageswhose parent hydrates later from the ledger.Fixes #25792is for the seat to judge. This body saysPart ofbecause item 6 is filed separately.🤖 Generated with Claude Code
https://claude.ai/code/session_01NndsH9Qm7k6AS2i2fWbjNA
Generated by Claude Code