Repository navigation
Commit be78977
feat(install): a declared dependency co-owns its parent's namespace, and both boot hydrates sort by it (ADR-0141 D5) (#26054)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XJ5XLtgWVaBQg2nkK9T9j9j)_
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.installPackage`
builds one co-owner map, `coOwnersOf`, and both halves of the ADR-0130
D3 pair read it: the namespace gate and
`refuseCoOwnedObjectNameCollision`. 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.
`ArtifactObjectNameConflictError` keeps `DUPLICATE_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 carries `coOwnership`. `NAMESPACE_CONFLICT` now names
the declaration as the fix.
**Item 5: hydrate order** (`packages/core/src/hydration-order.ts`, new).
`orderHydrationBatch` sorts a batch with `resolvePluginOrder`, using
declared ids as optional edges. This is the reading
`resolveArtifactPackageEntries` already 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:
- the `sys_packages` hydrate in
`packages/services/service-package/src/index.ts`, where one row is one
node;
- the install-local `kernel:ready` rehydrate in
`packages/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 of `manifest.dependencies` that the registry
and both hydrates share.
**Review notes from #25986:**
- An undecided union whose installed version is readable (`<5 || latest`
against `7.0.0`) now reports `found: 'unreadable-range'` instead of
naming the version.
- Install-local no longer counts the artifact wrapper's own id as
delivered. `DeclaredDependencyCheck.deliveredIds`, which had no other
caller, is removed.
**Docs:**
- `content/docs/deployment/app-dependencies.mdx` now 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.
- The `plugin-spec.mdx` consumer callout now names five consumers.
## Declarations
`Clause-②: yes (narrowing)`. The widening: install acceptance (packages
previously refused `NAMESPACE_CONFLICT` now install) and new
`@objectstack/core` exports (`orderHydrationBatch`,
`declaredDependencyIds`, `HydrationNode`, `HydrationOrder`). The
narrowing: install-local refuses a module that declares only its
artifact wrapper's id, and the `deliveredIds` option 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 is `patch`,
with a BREAKING banner and an ADR-0087 `not-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.
| Suite | Before the fix | After the fix |
|:---|:---|:---|
| `packages/objectql/src/registry-artifact-co-ownership.test.ts`, new
describe extending the ADR-0130 D3 gate test, real load path through
`manifest.register()`; the sixth case is a negative control | 5 failed,
10 passed | 15 passed |
|
`packages/services/service-package/src/hydration-dependency-order.test.ts`,
real `SchemaRegistry` | 2 failed | 2 passed |
|
`packages/cloud-connection/src/marketplace-install-local-rehydrate-order.test.ts`
| 2 failed | 2 passed |
| `packages/core/src/hydration-order.test.ts` | new sorter | 6 passed |
| the review-note cases in `protocol-handshake-dependencies.test.ts` and
`marketplace-install-local-dependency-missing.test.ts` | 1 failed each |
passed |
The new objectql cases cover:
- the cross-artifact pair: the dependent is admitted, and a dependent
defining `crm_account` is refused;
- the sibling pair: the sibling is admitted, and a sibling defining the
other sibling's name is refused;
- the parent arriving second;
- the upgrade case: the parent is admitted, and a parent newly defining
the module's name is refused naming the module;
- an undeclared sharer, which is still refused;
- siblings whose parent is absent or holds another namespace, which are
refused.
Every refusal asserts `code` and `status`.
Package suites on this branch after a full build (119/119 tasks), all
typecheck exit 0:
| Package | Test files | Tests |
|:---|:---|:---|
| core | 120 | 2774 |
| objectql | 470 | 8748 |
| service-package | 10 | 127 |
| cloud-connection | 58 | 732 |
| metadata-core | 20 | 818 |
Ablations (`scripts/ablation-replace.mjs`, each restore checked against
HEAD):
| Mutation | Failures |
|:---|:---|
| parent arm removed | 3 |
| reverse arm disabled | 2 (either end, upgrade) |
| sibling arm removed | 1 |
| sibling arm reading any shared declared id instead of an installed
parent holding N | 1 |
| `sys_packages` hydrate in list order | 1 |
| install-local rehydrate in listed order | 2 |
| cycle edges kept | 1 |
| found-label fix reverted | 1 |
Gates: `node scripts/pm/dispatch-gates.mjs --ran` accounts for all 106
derived families, all exit 0. One found a real miss:
`check-registry-log-declared` flagged the service-package suite that now
builds a `SchemaRegistry`, and `vitest.config.ts` now declares
`OS_REGISTRY_LOG`. Scoped `eslint` on the touched files exits 0, and
`check:nul-bytes` exits 0.
Full dogfood on `968c670094`: 2522 passed, 18 skipped, 1 failed (VERDICT
command-exit 1). The head `10c01a4181` adds only the service-package
`vitest.config.ts` env line. The failure is
`authz-probe-blind-spot.test.ts`, the
`packages/runtime/src/domains/i18n.ts` row (`handleI18nRequest` expected
3, got 4). That is main's red since #25956, reported on #25986 too; this
PR touches neither file.
## Acceptance notes
- With #25986, this completes card items 1, 2, 4, 5 and 7. Item 3 has no
`ui/**` edit, and item 6 is #26000.
- HotCRM precondition (ADR-0141 D5, Open items 7): at hotcrm `d91c7b0`,
every `crm` companion (service, marketing, revenue) declares
`app.objectstack.hotcrm`, so the sibling arm admits each of them beside
the app and beside each other.
- First reading 2, from the code and not instrumented on a booted stack:
ObjectQLPlugin's `kernel:ready` seals and the ledger rehydrate both run
on `kernel: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 in
`sys_packages` whose parent hydrates later from the ledger.
- `Fixes #25792` is for the seat to judge. This body says `Part of`
because item 6 is filed separately.
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01NndsH9Qm7k6AS2i2fWbjNA
_Generated by [Claude
Code](https://claude.ai/code/session_01NndsH9Qm7k6AS2i2fWbjNA)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b4199c9 commit be78977
17 files changed
Lines changed: 885 additions & 41 deletions
File tree
- .changeset
- content/docs
- deployment
- protocol/kernel
- packages
- cloud-connection/src
- core/src
- metadata-core/src
- objectql/src
- services/service-package
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
45 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
46 | 49 | | |
47 | 50 | | |
48 | 51 | | |
| |||
125 | 128 | | |
126 | 129 | | |
127 | 130 | | |
128 | | - | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
129 | 136 | | |
130 | 137 | | |
131 | 138 | | |
| |||
134 | 141 | | |
135 | 142 | | |
136 | 143 | | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
143 | 173 | | |
144 | 174 | | |
145 | 175 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
469 | 469 | | |
470 | 470 | | |
471 | 471 | | |
472 | | - | |
473 | | - | |
474 | | - | |
475 | | - | |
476 | | - | |
477 | | - | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
478 | 482 | | |
479 | 483 | | |
480 | 484 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
196 | 196 | | |
197 | 197 | | |
198 | 198 | | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
199 | 211 | | |
200 | 212 | | |
201 | 213 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
153 | 158 | | |
154 | 159 | | |
155 | 160 | | |
| |||
913 | 918 | | |
914 | 919 | | |
915 | 920 | | |
916 | | - | |
| 921 | + | |
917 | 922 | | |
918 | 923 | | |
919 | 924 | | |
| |||
983 | 988 | | |
984 | 989 | | |
985 | 990 | | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
986 | 1024 | | |
987 | 1025 | | |
988 | 1026 | | |
| |||
1547 | 1585 | | |
1548 | 1586 | | |
1549 | 1587 | | |
1550 | | - | |
1551 | | - | |
| 1588 | + | |
| 1589 | + | |
| 1590 | + | |
1552 | 1591 | | |
1553 | 1592 | | |
1554 | 1593 | | |
| |||
1568 | 1607 | | |
1569 | 1608 | | |
1570 | 1609 | | |
1571 | | - | |
1572 | 1610 | | |
1573 | 1611 | | |
1574 | 1612 | | |
| |||
0 commit comments