Skip to content

feat(spec)!: retire PackageDependency.optional and AppManifest.dependencies, the two unread dependency spellings (ADR-0141 D8) - #26401

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-tzosbt
Oct 27, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-tzosbt

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

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 spec: retire the two unread dependency spellings, PackageDependencySchema (with optional) and AppManifestSchema.dependencies, now that ADR-0141 D2 judges manifest.dependencies alone #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: fix(i18n)!: the console translation bundle leaves out Studio's metadataForms (75 KB of 221 KB on app-crm) #26159 and feat(spec,cli)!: retire the ≤16.x retirement kits; the strict unknown-key refusal names the protocol upgrade guide #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

Derived gates (118 families, all exit 0)
  • 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.
Dogfood battery (the `(narrowing)` arm)

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):

This PR touches nothing under packages/rest/ or packages/qa/ (git diff --stat 6390fe52c 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.


Generated by Claude Code

…encies, the two unread dependency spellings

ADR-0141 D8 items 2 and 3: both keys are tombstoned with retiredKey(),
registered in RETIRED_KEYS_BY_MAJOR[18] with one D3 semantic entry
(dependency-unread-spellings-retired) and a step-18 rationale fragment.
No D2 conversion: neither shape is a stack collection member.

Claude-Session: https://claude.ai/code/session_01BkRZmjDUobsYmkGEjwjH9U
Co-authored-by: Claude <noreply@anthropic.com>
…tes for the two dependency spellings

Claude-Session: https://claude.ai/code/session_01BkRZmjDUobsYmkGEjwjH9U
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 3 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/spec/authorable-defaults/marketplace.json, packages/spec/authorable-defaults/system.json, packages/spec/authorable-surface/marketplace.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

⛔ 1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via PackageDependencySchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/spec/authorable-defaults/marketplace.json, packages/spec/authorable-defaults/system.json, packages/spec/authorable-surface/marketplace.json, …) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 55 of 207 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 146 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b33dd25b2e7144b43eae3deb54026c03c6d1689f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from dba7dc98556105f4992b9dd70665b08702548651 — the merge of head c65c5645cbeb0a7137e73bedd962003e40bc71e3 into base b33dd25b2e7144b43eae3deb54026c03c6d1689f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin dba7dc98556105f4992b9dd70665b08702548651 && git checkout dba7dc98556105f4992b9dd70665b08702548651
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b33dd25b2e7144b43eae3deb54026c03c6d1689f c65c5645cbeb0a7137e73bedd962003e40bc71e3 && git checkout -B drift-repro b33dd25b2e7144b43eae3deb54026c03c6d1689f && git merge --no-ff c65c5645cbeb0a7137e73bedd962003e40bc71e3

node scripts/docs-audit/affected-docs.mjs --json b33dd25b2e7144b43eae3deb54026c03c6d1689f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b33dd25b2e7144b43eae3deb54026c03c6d1689f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-27T02:36Z

Head c65c5645cb, report 6432027506 (on #25792), claim amendment 6430963835. The contract review owed by packages/spec/src/** and Clause-②: no (narrowing) is on record: PASS at CONTRACT_REVIEW_TIER on this head (6432185956 on #25792).

item reading
Shape Draft, base main, Fixes #25792 (item 6, the card's last open item), minor changeset for @objectstack/spec with a BREAKING banner. Clause-②: no (narrowing), Metadata-protocol: narrowed. Governed (1 of 19 paths: docs/adr/**): landing tier H.
Fix ADR-0141 D8 items 2 and 3, decided on a measured count: 0 readers and 0 writers outside packages/spec (repo incl. ui/, examples/, apps/) and in HotCRM 9464673. Both PackageDependencySchema.optional and AppManifestSchema.dependencies retire at once (startup-stage axis, no named external user) as retiredKey() tombstones that refuse any authored value with a prescription, not a silent strip. RETIRED_KEYS_BY_MAJOR[18] rows, D3 entry dependency-unread-spellings-retired, no D2 conversion (neither is a stack collection member). A dated D8 addendum records the counts.
Evidence 46 check runs succeeded and 9 were expected skips on this head, 0 failed; mergeable clean. Red first on main, an absence pin over five declared roots, and 118 derived gates exit 0. The two dogfood files red on the dev's local battery (authz-conformance census, authz-probe-blind-spot) are main's, anchored at p0 #26400; this PR touches no packages/rest or packages/qa.
Carried The cloud manifest_json reader is unmeasured from here: filed as cloud-lane card #26405 (does cloud persist optional, or parse AppManifest.dependencies; residue acceptance before the bump if so). #25794 told its item 2 is stale against the addendum (6432212137). #26000's 6b and the optional half of 6a are delivered here; its triage re-scopes the rest after landing.
Landing Tier H: ⛔ not armed by the seat. The PR stays draft for the maintainer's merge or an authorized APPROVED review; the seat lands it after that.

Generated by Claude Code

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 protocol:system size/l tests tooling

Projects

None yet

3 participants