Skip to content

Commit 436d284

Browse files
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
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
**BREAKING** — the two unread dependency spellings leave the authoring surface: `PackageDependency.optional` (an entry of the marketplace snapshot's `manifestJson.dependencies[]`) and `AppManifest.dependencies` (the bare-id list on the app install manifest) are `retiredKey()` tombstones (#25792, ADR-0141 D8 items 2 and 3).
6+
7+
A `major`-class change, recorded as `minor` under the launch-window convention. ADR-0141 D8 left both fates to "a measured reader count"; the count was zero for both, so both retire at once, with no retirement stage (ADR-0049 enforce-or-remove).
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: narrowed
11+
12+
**Why.** A dependency is declared in one place, the package's `manifest.dependencies` record (`{ [packageId]: versionRange }`), and the ADR-0141 D2 pre-install check reads that record alone and treats every entry as required. Two more spellings stayed declared beside it and nothing read either. `optional: true` promised "missing dep is a warning, not an error", but the record has no optional arm, so no install path could honour it. `AppManifest.dependencies` restated the record without its range, and no install path consulted it. An author, often an AI, who wrote either got nothing enforced and no error. Measured at `origin/main` `6390fe52ce`: zero readers and zero writers of either key outside `packages/spec` (`packages/`, `ui/`, `examples/`, `apps/`), and none in HotCRM at `9464673`, whose three modules declare their dependency in the record.
13+
14+
### FROM → TO
15+
16+
| removed | from | what to write instead |
17+
| --- | --- | --- |
18+
| `optional` on a `PackageDependency` entry (`PackageManifestSchema.dependencies[]`) | `@objectstack/spec/marketplace` | delete the key. An entry carries `packageId` and `versionRange` only. Declare a dependency in `manifest.dependencies` only when the package cannot install without it. |
19+
| `dependencies` on `AppManifest` | `@objectstack/spec/system` | delete the key. Declare each dependency with its range in the package's `manifest.dependencies`, for example `{ 'com.acme.core': '^1.0.0' }`. |
20+
21+
**The one-line fix: delete the key.** A TypeScript author fails `tsc` first (each key is typed as the retired mark), and a parse of `PackageDependencySchema`, `PackageManifestSchema` or `AppManifestSchema` refuses the key with its prescription, the old default (`false`, `[]`) included. The parsed output no longer carries either key, so `PackageDependencyParsed` loses `optional` and `AppManifestParsed` loses `dependencies`.
22+
23+
⚠️ Runtime behaviour is deliberately **unchanged**: nothing ever read either key, so every install answers exactly as before. The removal retracts a false claim, not a capability. **No deprecation window** (startup-stage posture: retirements take effect immediately).
24+
25+
⚠️ **The out-of-repo consumer population beyond HotCRM is NOT MEASURED.** `@objectstack/spec` is published, and the control plane's reading of `sys_package_version.manifest_json` is not readable from this repository.
26+
27+
The ADR-0087 D3 semantic entry `dependency-unread-spellings-retired` carries the judgement, and both keys are registered in `RETIRED_KEYS_BY_MAJOR[18]`. There is no D2 conversion: neither shape is a stack collection member, so a conversion would have no source to rewrite.
28+
29+
<!-- adr-0087: registered dependency-unread-spellings-retired -->

‎content/docs/references/marketplace/package-version.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Package dependency declaration
6464
| :--- | :--- | :--- | :--- |
6565
| **packageId** | `string` | ✅ | Manifest ID of the dependency |
6666
| **versionRange** | `string` | ✅ | Semver version range (e.g. ^1.0.0) |
67-
| **optional** | `boolean` | optional (default: `false`) | Whether this dependency is optional |
67+
| **optional** | `never` | optional | [REMOVED] `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. |
6868

6969

7070
---
@@ -83,7 +83,7 @@ Package manifest snapshot embedded in a package version
8383
| **description** | `string` | optional | Short description |
8484
| **scope** | `Enum<'platform' \| 'environment'>` | optional (default: `"environment"`) | Package scope |
8585
| **minPlatformVersion** | `string` | optional | Minimum required platform version (semver) |
86-
| **dependencies** | `{ packageId: string; versionRange: string; optional?: boolean }[]` | optional (default: `[]`) | Package dependencies |
86+
| **dependencies** | `{ packageId: string; versionRange: string }[]` | optional (default: `[]`) | Package dependencies |
8787
| **metadataTypes** | `string[]` | optional (default: `[]`) | Metadata types provided by this package |
8888
| **migrations** | `string[]` | optional (default: `[]`) | Migration script identifiers (ordered) |
8989
| **configurationSchema** | `Record<string, any>` | optional | JSON Schema for per-installation configuration properties |
@@ -97,7 +97,7 @@ Package dependency declaration
9797
| :--- | :--- | :--- | :--- |
9898
| **packageId** | `string` | ✅ | Manifest ID of the dependency |
9999
| **versionRange** | `string` | ✅ | Semver version range (e.g. ^1.0.0) |
100-
| **optional** | `boolean` | optional (default: `false`) | Whether this dependency is optional |
100+
| **optional** | `never` | optional | [REMOVED] `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. |
101101

102102

103103
---

‎content/docs/references/system/app-install.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ App manifest for marketplace installation
111111
| **flows** | `string[]` | optional (default: `[]`) | Flow names provided |
112112
| **hasSeedData** | `boolean` | optional (default: `false`) | Whether app includes seed data |
113113
| **seedData** | `Record<string, any>[]` | optional (default: `[]`) | Seed data records |
114-
| **dependencies** | `string[]` | optional (default: `[]`) | Required app dependencies |
114+
| **dependencies** | `never` | optional | [REMOVED] `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. |
115115

116116

117117
---

‎docs/adr/0141-inter-app-dependency-declared-checked-before-install.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,17 @@ other two on the runtime card:
513513
The resolver-side `ResolvedPackageDependencySchema` (`@objectstack/spec/kernel`) is unaffected: it is a resolution outcome,
514514
not a declaration, and `PackageDependencySchema`'s own header already tells the two apart.
515515

516+
**Addendum (2026-10-27, #25792 item 6) — items 2 and 3 decided on the measured count: both retire.** Measured at
517+
`origin/main` `6390fe52ce`: `PackageDependencySchema.optional` has **0** readers and **0** writers outside `packages/spec`,
518+
and `AppManifestSchema.dependencies` has **0** readers and **0** writers there, with no parse of `AppManifestSchema` at all
519+
(this repository's `packages/`, `ui/`, `examples/` and `apps/`; HotCRM at `9464673`, whose three module manifests declare
520+
the record, the live control). Inside `packages/spec` the only authors were the two schemas and three of their own tests.
521+
Each key is a `retiredKey()` tombstone now (`marketplace/PackageDependency:optional` and `system/AppManifest:dependencies`
522+
in `RETIRED_KEYS_BY_MAJOR[18]`), with the D3 record `dependency-unread-spellings-retired` and no D2 conversion, because
523+
neither shape is a stack collection member. The projection above therefore carries `packageId` = the key and
524+
`versionRange` = the value, nothing else: item 1's conversion writes no `optional`, and `manifest.dependencies` is the
525+
one way to declare a dependency.
526+
516527
---
517528

518529
## Consequences

‎packages/spec/authorable-defaults/marketplace.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"marketplace/Package:isStarter = false",
1414
"marketplace/Package:publisher = \"private\"",
1515
"marketplace/Package:visibility = \"org\"",
16-
"marketplace/PackageDependency:optional = false",
1716
"marketplace/PackageManifest:dependencies = []",
1817
"marketplace/PackageManifest:metadataTypes = []",
1918
"marketplace/PackageManifest:migrations = []",

‎packages/spec/authorable-defaults/system.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"system/AppInstallResult:createdTables = []",
1010
"system/AppInstallResult:installedObjects = []",
1111
"system/AppInstallResult:seededRecords = 0",
12-
"system/AppManifest:dependencies = []",
1312
"system/AppManifest:flows = []",
1413
"system/AppManifest:hasSeedData = false",
1514
"system/AppManifest:objects = []",

‎packages/spec/authorable-surface/marketplace.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
"marketplace/Package:translations",
103103
"marketplace/Package:updatedAt",
104104
"marketplace/Package:visibility",
105-
"marketplace/PackageDependency:optional",
105+
"marketplace/PackageDependency:optional [RETIRED]",
106106
"marketplace/PackageDependency:packageId",
107107
"marketplace/PackageDependency:versionRange",
108108
"marketplace/PackageManifest:configurationSchema",

‎packages/spec/authorable-surface/system.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"system/AppInstallResult:seededRecords",
4444
"system/AppInstallResult:success",
4545
"system/AppInstallResult:version",
46-
"system/AppManifest:dependencies",
46+
"system/AppManifest:dependencies [RETIRED]",
4747
"system/AppManifest:description",
4848
"system/AppManifest:flows",
4949
"system/AppManifest:hasSeedData",

‎packages/spec/src/contracts/app-lifecycle-service.test.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ describe('App Lifecycle Service Contract', () => {
1414
views: ['contact_list', 'deal_board'],
1515
flows: ['new_deal_notification'],
1616
hasSeedData: true,
17-
dependencies: [],
1817
};
1918

2019
it('should allow a minimal IAppLifecycleService implementation with all required methods', () => {

‎packages/spec/src/kernel/package-dependency-dual-source.test.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
//
1717
// ./cloud — since #16325 ./marketplace — the DECLARATION form an author
1818
// writes into a package manifest:
19-
// `{ packageId, versionRange, optional }`, embedded in
19+
// `{ packageId, versionRange, optional }` (`optional` since retired,
20+
// ADR-0141 D8), embedded in
2021
// `PackageManifestSchema.dependencies[]` → `sys_package_version.manifest_json`;
2122
// ./kernel — the RESOLVER form the dependency resolver walks:
2223
// `{ name, versionConstraint, type, resolvedVersion }`, embedded in
@@ -144,10 +145,10 @@ describe('PackageDependency dual-source retirement (C7)', () => {
144145
packageId: 'com.objectstack.core',
145146
versionRange: '^1.0.0',
146147
});
148+
// `optional` is a retired tombstone (ADR-0141 D8): it materializes nothing.
147149
expect(declared).toEqual({
148150
packageId: 'com.objectstack.core',
149151
versionRange: '^1.0.0',
150-
optional: false, // the `.default(false)` still applies
151152
});
152153
});
153154

@@ -163,13 +164,14 @@ describe('PackageDependency dual-source retirement (C7)', () => {
163164
const kernelKeys = keysOf(ResolvedPackageDependencySchema as never);
164165
const cloudKeys = keysOf(PackageDependencySchema as never);
165166
expect([...kernelKeys].sort()).toEqual(['name', 'resolvedVersion', 'type', 'versionConstraint']);
167+
// `optional` stays in the shape as a retiredKey() tombstone (ADR-0141 D8).
166168
expect([...cloudKeys].sort()).toEqual(['optional', 'packageId', 'versionRange']);
167169
expect([...kernelKeys].filter((k) => cloudKeys.has(k)), 'key sets must stay disjoint').toEqual([]);
168170

169171
// Neither side is `.strict()`, so cross-pasting a document does not throw —
170172
// it silently strips. That is exactly the failure mode the shared name hid,
171173
// and it is why the pin above forbids a "helpful" re-export alias.
172-
const manifestDoc = { packageId: 'com.objectstack.core', versionRange: '^1.0.0', optional: true };
174+
const manifestDoc = { packageId: 'com.objectstack.core', versionRange: '^1.0.0' };
173175
const strippedByResolver = ResolvedPackageDependencySchema.safeParse(manifestDoc);
174176
expect(strippedByResolver.success, 'resolver form rejects a manifest doc only on required keys').toBe(false);
175177

0 commit comments

Comments
 (0)