Repository navigation
Commit 436d284
feat(spec)!: retire PackageDependency.optional and AppManifest.dependencies, the two unread dependency spellings (ADR-0141 D8) (#26401)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XVH2vvDXVdAZoD8eV8xc6m4)_
Fixes #25792
This is card item 6, the last open item (ADR-0141 D8 items 2 and 3).
Items 1, 2, 4, 5 and 7 landed in #25986 and #26054, and item 3 in
#26123.
Clause-②: no (narrowing)
Metadata-protocol: narrowed
## 维护者速读
- **改了什么**:依赖声明的两种「无人读取」拼写退役:市场快照依赖项上的
`optional`(`PackageDependency.optional`)和应用安装清单上的裸 id
列表(`AppManifest.dependencies`)。两者都改为 `retiredKey()`
墓碑,写了就报错并给出改法;ADR-0141 §D8 补一条带日期的实测结论。
- **为什么改**:ADR-0141 D8 把这两个键的去留交给「实测读者数」决定。实测两者在 `packages/spec` 之外都是 0
个读者、0 个写者(本仓 packages/ui/examples/apps 与 HotCRM)。安装前检查只读
`manifest.dependencies`,且每条都必需;留着这两个键,作者(尤其 AI)写了也不生效、也不报错。
- **风险与回滚**:运行时行为不变(本来就没人读)。风险只在仓外未测到的读者:`@objectstack/spec` 是已发布包,cloud
控制面如何解析 `manifest_json` 本仓读不到。回滚即 revert 本 PR。
- **先看**:①
`packages/spec/src/marketplace/package-version.zod.ts`(`optional`
墓碑与说明);②
`docs/adr/0141-inter-app-dependency-declared-checked-before-install.md`
§D8 新增的 Addendum 段;③
`packages/spec/src/marketplace/dependency-spellings-retirement.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/spec exec vitest run --project
repo src/marketplace/dependency-spellings-retirement.test.ts`,预期 14
个测试全过;在 `main` 上同一文件 12 个失败。
- **需要你做的**:本 PR 改了 `docs/adr/**`(Tier H),需要你批准后由席位落地。
## What changes
What an author sees change, in plain sentences:
- Writing `optional` on a dependency entry of a package version snapshot
(`PackageManifestSchema.dependencies[]`, the `PackageDependency` shape)
is now refused, whatever the value, including the old default `false`.
The refusal reads: "`PackageDependency.optional` was removed in
@objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0141 D8) — no
install path ever honoured it: the pre-install check reads the package's
`manifest.dependencies` record, which has no optional arm, and treats
every entry as required. Delete the key. Declare a dependency in
`manifest.dependencies` (`{ 'com.acme.core': '^1.0.0' }`) only when the
package cannot install without it."
- Writing `dependencies` on an app install manifest
(`AppManifestSchema`) is now refused the same way, including the old
default `[]`. The refusal reads: "`AppManifest.dependencies` was removed
in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0141 D8) —
nothing read it: it listed bare ids with no version range, and no
install path consulted it. Delete the key. Declare each dependency with
its range in the package's `manifest.dependencies` (`{ 'com.acme.core':
'^1.0.0' }`), which the pre-install check enforces."
- In TypeScript, both keys are typed as the retired mark, so writing
either fails `tsc` at the authoring site.
- A parsed entry or manifest no longer carries the key:
`PackageDependencyParsed` loses `optional`, and `AppManifestParsed`
loses `dependencies`.
- The manifest record `manifest.dependencies` is unchanged and stays the
one way to declare a dependency.
The retirement kit, per the `spec-property-retirement` playbook:
- Schemas: `retiredKey()` on `PackageDependencySchema.optional`
(`packages/spec/src/marketplace/package-version.zod.ts`) and
`AppManifestSchema.dependencies`
(`packages/spec/src/system/app-install.zod.ts`), with comments naming
ADR-0141 D8 and the measurement.
- ADR-0087: `RETIRED_KEYS_BY_MAJOR[18]` gains
`marketplace/PackageDependency:optional` and
`system/AppManifest:dependencies` (one file each under
`migrations/entries/retired-keys/`). One D3 semantic entry for the
family, `dependency-unread-spellings-retired`, and its
`STEP18_RATIONALE` fragment in `registry.ts.template`. **No D2
conversion**: neither shape is a stack collection member, so a
conversion would have no seam that runs (the
`kernel/Manifest:capabilities` precedent, recorded in its retired-key
entry).
- The residue stage `acceptRetiredDefaultResidue` is **not** adopted,
although both keys carried a default: no producer in this repository or
HotCRM parses either schema to emit one, so there is no emitted
population (reasoning written on both retired-key entries).
- Regenerated: `authorable-surface/{marketplace,system}.json` (each row
gains `[RETIRED]`), `authorable-defaults/{marketplace,system}.json` (the
two default rows leave), and the two reference pages under
`content/docs/references/`, through `check:generated --fix`.
`authorable-surface.base.json`, `api-surface/` and
`json-schema.manifest/` do not move (a key-level tombstone keeps the def
and the export).
- Fixtures: three spec tests authored a retired key
(`system/app-install.test.ts`,
`contracts/app-lifecycle-service.test.ts`,
`kernel/package-dependency-dual-source.test.ts`). Each now writes the
live shape, and the dual-source pin notes that `optional` stays in the
shape as a tombstone.
- ADR-0141 §D8 gains a dated addendum naming the counts and the fates
(Tier H).
- Changeset: `@objectstack/spec` `minor`, BREAKING banner, FROM → TO
table, ADR-0087 marker `registered dependency-unread-spellings-retired`.
## Tests
- New
`packages/spec/src/marketplace/dependency-spellings-retirement.test.ts`,
14 tests, in the `repo` project (`vitest.repo-tests.json`), because its
absence pin walks outside the package. It pins the refusal (`code`,
`path`, prescription text) for every value of each key, the snapshot
path `['dependencies', 1, 'optional']`, the controls (no key ⇒ parse
passes and grows no key), the `tsc` channel (`@ts-expect-error` on each
key, compiled by `tsconfig.test.json`), the registry rows and the D3
entry, that no conversion names either surface, and a tree-scoped
absence pin over the five declared roots with a shape matcher
(`optional` beside `versionRange`; `dependencies` beside
`minKernelVersion` or `hasSeedData`) and its own anti-vacuity cases.
- Red first: the new file on `main` `6390fe52ce` gave **12 failed | 2
passed**. The two that passed are the matcher's anti-vacuity case and
the no-conversion case. The absence pin's red named exactly five
authoring sites, all inside `packages/spec`: the two schemas and the
three tests fixed here.
- After the change: the new file plus the three edited tests,
`src/marketplace/` and `scripts/step18-rationale-merge.test.ts`: 10
files, 124 tests passed. The new file under `--project repo`: 14 passed.
- `pnpm --filter @objectstack/spec test`: Test Files 727 passed, Tests
21998 passed | 1 todo (os-verify-lock VERDICT command-exit 0).
- `pnpm --filter @objectstack/spec typecheck`: exit 0 (the test layer
compiles under `tsconfig.test.json`; `test-typecheck-debt.json`
unchanged).
- No separate ablation: the change is the tombstones themselves, and the
red-first run on `main` is the run without them.
## Acceptance notes
- **The counts behind the fates**, measured at `origin/main`
`6390fe52ce`. `optional` on a dependency entry: 0 readers and 0 writers
outside `packages/spec` (`packages/`, `ui/`, `examples/`, `apps/`).
`AppManifestSchema` / `AppManifest`: no import outside `packages/spec`
at all, so 0 readers of `dependencies`. The only non-spec mentions were
prose (ADR-0141, ADR-0027's table, a comment in
`packages/core/src/artifact-packages.ts`). HotCRM at `9464673`: 0 of
either; its three module manifests (`src/service`, `src/marketing`,
`src/revenue`) declare `{ 'app.objectstack.hotcrm': '^6.0.0' }` in the
record, the live control. `IAppLifecycleService`
(`packages/spec/src/contracts/app-lifecycle-service.ts`), the one
contract that takes an `AppManifest`, has no implementer in this
repository.
- **What a snapshot carries.** Today a published snapshot carries the
record shape, since `publish.ts` uploads the compiled artifact as it is
(ADR-0141 D8 item 1), so no stored or produced snapshot changes. The
addendum states the consequence for D8 item 1's future conversion (its
own card): the projection writes `packageId` and `versionRange` only,
never `optional`. The seat should read this against that card before
landing.
- **Not measured:** the cloud control plane's parse of
`sys_package_version.manifest_json` (`objectstack-ai/cloud`, not
readable from this session), and any other out-of-repo consumer of the
published `@objectstack/spec`.
- **Overlap with #26000.** That card (open, `pm:retriage`) proposed
retiring the whole of `PackageDependencySchema` plus
`AppManifestSchema.dependencies`. This PR retires only the two keys
ADR-0141 D8 owed to this card. The rest of `PackageDependencySchema`
stays, as D8's projection requires.
- `AppManifestSchema` as a whole (and `IAppLifecycleService`) has no
reader outside `packages/spec` either. That is outside item 6 and is
noted here, not changed.
- Open PRs touching the same files: #26159 and #26138 both edit
`packages/spec/authorable-surface/system.json` (a generated,
driver-merged shard). A driver-free `merge-tree` in a throwaway bare
clone answers exit 0 with a tree id for both pairs, so both merge clean.
## Verification
<details><summary>Derived gates (118 families, all exit 0)</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 118 families on this diff (18 paths,
+683/−18 at derivation; re-derived on `c65c5645cb`, same 118).
- Each was run with its exit code captured before any pipe. First pass:
107 exit 0; 10 exited 3 and 1 exited 1 because a concurrent closure
build was rewriting `packages/spec/dist` and `packages/lint/dist` under
them (each said "Nothing was measured" or named a stale dist). All 11
re-ran against the settled build and exited 0.
- One real miss: `check:cross-package-test-inputs` exited 1 because the
new absence pin escapes the package but was not in
`vitest.repo-tests.json`. Fixed in `c65c5645cb`; re-run exit 0.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--ran RECORD` (a record carrying each exit code): exit 0, "118 derived,
118 run, 0 NOT-MEASURED, 0 UNRUN", "a DERIVED zero — all 118 recorded an
exit code and none of them is 3".
- `pnpm exec eslint --no-inline-config` over the 9 touched `.ts` files:
exit 0. `node scripts/check-nul-bytes.mjs`: exit 0.
- `pnpm --filter @objectstack/spec check:generated`: 14 of 14 up to date
after `--fix` regenerated `check:docs` only.
</details>
<details><summary>Dogfood battery (the `(narrowing)` arm)</summary>
On head `c65c5645cb`, closure built first (`pnpm --filter
'@objectstack/dogfood^...' build`, VERDICT command-exit 0), then `pnpm
--filter @objectstack/dogfood test` under the lock: **VERDICT
command-exit 1**, held the lock 1147s. Test Files 2 failed | 297 passed
| 3 skipped (302); Tests 10 failed | 2521 passed | 18 skipped (2549).
Both failing files are main's expected reds, anchored at #26400 (the
shape #26399 uses):
- `test/authz-conformance.test.ts`, 6 tests in "#17111 — the matrix
docblock's ledger figures are pinned to the ledgers":
"packages/rest/src/rest-route-ledger.ts: docblock says 80 rows, the
table holds 81".
- `test/authz-probe-blind-spot.test.ts`, 4 tests on
`packages/rest/src/rest-route-ledger.ts` and
`packages/rest/src/rest-server.ts`: control
"this.routeManager.register(" moved in rest-server.ts, expected 62, got
63.
This PR touches nothing under `packages/rest/` or `packages/qa/` (`git
diff --stat 6390fe5 HEAD -- packages/rest packages/qa` is empty). The
last change to both rest files in the base is `6a2866af71` (#26361, the
approval lock read route, merged 2026-10-26T23:58Z), and `origin/main`
`b33dd25b2e` carries no later change to either file or either test.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01BkRZmjDUobsYmkGEjwjH9U)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5507225 commit 436d284
19 files changed
Lines changed: 684 additions & 18 deletions
File tree
- .changeset
- content/docs/references
- marketplace
- system
- docs/adr
- packages/spec
- authorable-defaults
- authorable-surface
- src
- contracts
- kernel
- marketplace
- migrations
- entries
- retired-keys
- semantic
- system
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
67 | | - | |
| 67 | + | |
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | | - | |
| 86 | + | |
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| |||
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
100 | | - | |
| 100 | + | |
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
114 | | - | |
| 114 | + | |
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
513 | 513 | | |
514 | 514 | | |
515 | 515 | | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
516 | 527 | | |
517 | 528 | | |
518 | 529 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | 16 | | |
18 | 17 | | |
19 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | 12 | | |
14 | 13 | | |
15 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
105 | | - | |
| 105 | + | |
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | 17 | | |
19 | 18 | | |
20 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
144 | 145 | | |
145 | 146 | | |
146 | 147 | | |
| 148 | + | |
147 | 149 | | |
148 | 150 | | |
149 | 151 | | |
150 | | - | |
151 | 152 | | |
152 | 153 | | |
153 | 154 | | |
| |||
163 | 164 | | |
164 | 165 | | |
165 | 166 | | |
| 167 | + | |
166 | 168 | | |
167 | 169 | | |
168 | 170 | | |
169 | 171 | | |
170 | 172 | | |
171 | 173 | | |
172 | | - | |
| 174 | + | |
173 | 175 | | |
174 | 176 | | |
175 | 177 | | |
| |||
0 commit comments