Repository navigation
ADR-0141 runtime: pre-install dependency check (dependency_missing, 422) at the three install doors, plus dependency-declared namespace co-owners and the dependency-ordered rehydrate #25792
Description
Filing gate: ③ maintainer-directed — the runtime card ADR-0141 §Execution plan item 1 names, owed by landed work (PR #25717, merge afe6ab9ccc)
Ruling: #25403 comment 6369704387 (B′, maintainer 「同意」 2026-10-23T16:28:30Z): "Then one runtime card for the pre-install check (XS–S) with the console's refusal text, plus docs"; comment 6377678758 (letter A, 「同意 #8」 2026-10-24T00:40:55Z): "objectql S (one more source for coOwners, plus a test), riding with the B′ runtime pre-install card"
Business reason: the ruling's own words — 「如果这样,hotcrm 岂不是真实的可以分拆出很多模块」/「而每个包可以独立安装」. Today an app that depends on another installs anyway and fails later at its first cross-app save; and a HotCRM module shipped as its own artifact (revenue first, hotcrm#2271) is refused by the namespace gate next to HotCRM. Without this card, composite products keep shipping as one artifact.
Reader: the domain:services / objectql lane, one PR touching packages/metadata-core, packages/metadata-protocol, packages/runtime/src/domains/packages.ts, packages/cloud-connection, packages/objectql, packages/services/service-package, and the Console refusal text
Dedupe (open AND closed): "pre-install dependency check dependency_missing install refuse missing dependency" → 0; "ADR-0141 inter-app dependency runtime card" → 7, none a runtime card (#25403 is the decision card); "namespace co-owner declared dependency install gate object name uniqueness cross-artifact" → 1 (#14122, closed, the ADR-0130 in-artifact case)
Size: S (ADR-0141's estimate). Siblings: author-side range rule card (released with or before this one) and snapshot-alignment card, cut together from the same plan.
What to build (ADR-0141, docs/adr/0141-inter-app-dependency-declared-checked-before-install.md)
-
D2 — the pre-install check. One predicate, called at the three install acts and nowhere else:
packages/metadata-protocol/src/protocol-package-install.ts#installPackage;- the bare-registry fallback of
POST /api/v1/packages(packages/runtime/src/domains/packages.ts); POST /api/v1/marketplace/install-local(packages/cloud-connection/src/marketplace-install-local-plugin.ts), before the manifest service sees the artifact.
Each entry
(id, range)of the installed package's ownmanifest.dependenciesis satisfied whenidis installed (SchemaRegistry.getPackage) at amanifest.versioninsiderange, or is delivered by the same artifact (presence only). Otherwise the whole install is refused before anything registers:status422, acoderegistered in the ADR-0112 D3 ledger, and oneAppCompatibilityCheckissue per unmet entry (severity: 'error',category: 'dependency_missing', message naming the id, the range and what was found). Every unmet entry is reported in one answer. An unparsable range or version does not satisfy (fail closed). ⛔ Not at the re-registration paths (code packages atinit/start, thesys_packagesrehydrate, the install-localkernel:readyrehydrate), and ⛔ not atPATCH /api/v1/packages/:id. -
The range reader. One sibling function in
packages/metadata-core/src/protocol-handshake.ts, sharing the handshake's alternative/comparator parsing, at exact-version granularity. The card states the grammar it supports: at least^,~,>=, anxwildcard,||and the space intersection, so 「HotCRM 6.x」 reads.assertProtocolCompat's verdicts and ADR-0087 D1's never-false-rejection contract stay unchanged. ⛔ No fourth reader (rangeAdmitsMajor,parseRangeFloor,SemanticVersionManagerare the three that exist). -
The Console refusal text, in the author's terms (「请先安装 HotCRM 6.x」), from the refusal's facts.
-
D5 — dependency-declared co-owners. A second source for the co-owner set the namespace gate and
refuseCoOwnedObjectNameCollisionboth read (packages/objectql/src/registry.ts,NamespaceConflictError): a package that declares X and uses X's namespace co-owns it with X, and with X's other declared dependents that use it (sibling arm). One-way, read from either end, whichever registers first. Tests extend the ADR-0130 D3 gate test: a cross-artifact pair and a sibling pair (a dependent defining an object name X owns, and a sibling defining a name the other sibling owns, are refused; assertcodeandstatus, message worded per co-ownership source), and the upgrade case (X reinstalled beside its module is admitted; X newly defining a name the module owns is refused). -
D5 — rehydrate order. The
sys_packagesand install-local rehydrates sort their own batch throughresolvePluginOrder(packages/core/src/plugin-order.ts), ledger entries as nodes. A cycle degrades to source order with a logged refusal and never throws out of the hydrate. -
D8 items 2 and 3. A stated fate for
optional(no record spelling; a snapshot never produces it and D2 never reads it until decided), and forAppManifestSchema.dependencies(project from the record, or retire through the ADR-0087 conversion layer, decided on a measured reader count). -
Docs (§Execution plan item 4). The package-authoring guide states how to declare a dependency, the range grammar, what the install refusal means, that two modules of one app need not declare each other, and that
os validatecannot see a dependency's objects.
First readings (ADR-0141 §Open items 2, 3, 4, 6)
- 2: whether a dependent on one hydrate path can have its parent on a later one, and whether the
kernel:readyseals inpackages/objectql/src/plugin.tsrun before or after the ledger rehydrate on the same hook. - 3: a declared id that names no installable package, if HotCRM's manifests carry one. Report it; D2 does not exempt it.
- 4: what
enabled: falseon X should mean for a dependent, measured against the save door's closure and the data door today. - 6: that the artifact
os buildwrites for a standalone module carriestype: 'app', and what each install door does today with a top-level package of another type.
Out of scope
D3's deferred items (uninstall protection, upgrade interception, topological/cascade install, owner-side publish marker, marketplace version negotiation, judging a post-install dependencies edit). D8 item 1 (record-to-array snapshot conversion) is the snapshot-alignment card. HotCRM-side work (D4 readings, D7 preconditions) is the HotCRM lane, hotcrm#2271.
Activity
objectstack-fleet commentedon Oct 25, 2026
Triage: first grade —
enhancement·priority:p2·domain:server·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-25T13:52Z⛔ Not a claim, ⛔ not a dispatch.
dependency_missing(422) at the three install doors, plus the objectqlcoOwnerssource, XS–S. It adds a refusal, not a key, so p2.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 5 · card thread
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01NndsH9Qm7k6AS2i2fWbjNAAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-78l2jg(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree:
objectstack-issue-25792(a linked worktree in the card thread's container)Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage 6398501495; #25403 B′ (6369704387) and letter A (6377678758); scope = the card's items 1, 2, 4, 5, 7; item 3 rides objectui's card; item 6 goes to
open_questionsFile surface: the card's reader list (
packages/metadata-core/src/protocol-handshake.ts,packages/metadata-protocol/src/protocol-package-install.ts,packages/runtime/src/domains/packages.ts,packages/cloud-connection/src/marketplace-install-local-plugin.ts,packages/objectql/src/registry.ts,packages/services/service-package/**,packages/core/src/plugin-order.tscallers), the package-authoring docs, their tests, one changeset; inpackages/spec/**only the new code's row inpackages/spec/src/api/error-code-ledger.zod.ts(contract review by the seat). ⛔ Noui/**, ⛔ no spec key change (stop on breach; explain in the report)Container & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: no (narrowing: an install whose declared dependency is unmet is refused at the three install acts; measured and declared by the dev)
Metadata-protocol: none
Responsibility: n/a — ADR-0141 runtime card owed by landed work (PR #25717)
Thread-read: 6398501495
Premise: holds (read 2026-10-26T06:09Z at
origin/main9ca36b163e: card open, unassigned,pm:queue, no other seat's claim)Serial constraints cleared: no open PR names #25792; #25744 (this seat) also edits
packages/objectql/src/registry.ts(registerObject, a different function from the co-owner gate); whichever lands second mergesmainobjectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25792, "status": "needs_decision", "branch": "claude/project-thread-78l2jg", "pr": "https://github.com/objectstack-ai/objectstack/pull/25986", "session": "session_01NndsH9Qm7k6AS2i2fWbjNA", "premise_still_valid": true, "summary": "PR #25986 (draft, head 12daab9542, Part of #25792) delivers card items 1, 2 and 7. It adds the ADR-0141 D2 pre-install dependency check at exactly the three install acts: the protocol installPackage (which is also the composed arm of POST /api/v1/packages), the no-protocol fallback arm of POST /api/v1/packages, and POST /api/v1/marketplace/install-local (inline and cloud-snapshot branches). A package with an unmet entry gets 422 DEPENDENCY_MISSING, with one AppCompatibilityCheck issue and one unmet fact per entry. It also adds one fail-closed range reader beside the D1 handshake (rangeAdmitsVersion; shared comparator peel, D1 verdicts unchanged), the ledger row (the only packages/spec edit), and the docs. Size measured: the D2 part is about 1,500 changed lines (18 files, +1500/-42), over the ~900 split point, so items 4 and 5 (D5 co-owner source and rehydrate order) are the next PR, and #25792 stays open. Item 3 has no ui/** edit. Item 6 is in open_questions with no spec key change. HotCRM at d91c7b0 passes unchanged: service, marketing and revenue each declare { 'app.objectstack.hotcrm': '^6.0.0' } and are composed with the 6.0.0 app, so the app counts as delivered (presence only), and ^6.0.0 also admits 6.0.0 if a module ships standalone. Departures: (1) Clause-② measured `yes (narrowing)`, not the claim's `no (narrowing ...)`, because the diff adds a ledger code and new metadata-core exports. (2) Two files sit outside the claim's listed surface, both machine-required: scripts/adr-anchors/packages__metadata-core__src__protocol-handshake.ts.json (an ADR anchor for the edited file) and scripts/docs-audit/handwritten-docs.json (the roster entry check:docs-audit-scope demanded for the new docs page).", "tests": "Red first: protocol-package-install-dependencies.test.ts failed on main (2 failed) and passes after wiring. New suites: metadata-core protocol-handshake-dependencies.test.ts 17, metadata-protocol protocol-package-install-dependencies.test.ts 4, runtime packages-install-dependency-missing.test.ts 6 (composed and fallback arms), cloud-connection marketplace-install-local-dependency-missing.test.ts 7. On head 12daab9542, after a full build (os-verify-lock VERDICT command-exit 0, 119/119 tasks): spec Test Files 722 passed, Tests 21884 passed | 1 todo; metadata-core 20 files / 817 passed; metadata-protocol 266 passed | 3 skipped files, 21537 passed | 19 skipped; cloud-connection 57 files / 729 passed; runtime Test Files 448 passed, Tests 7188 passed | 19 skipped (VERDICT command-exit 0). Typecheck exit 0 for all five packages. Before the core rebuild, runtime typecheck failed with TS2305 (markAppAutomationElevation) and 42 tests failed in app-automation-identity-write-refusal.integration.test.ts; both came from a stale @objectstack/core dist after merging main, and both cleared after the rebuild. Ablations (scripts/ablation-replace.mjs, each restore checked against HEAD): fallback-arm call removed, 2 failed; install-local check disabled, 4 failed; presence-only arm deleted, 1 failed; shared comparator peel deleted, 9 failed (5 in the new suite, 4 in the D1 handshake suite).", "gates": "Derived list from dispatch-gates.mjs --commands: 127 families, each run with its exit code captured before any pipe, then reconciled with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran gates-ran.txt`: exit 0, '127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN'. First pass: four non-zero. check-plugin-teardown-shape --self-test exit 3 (shallow clone; 0 after git fetch --unshallow). check:skill-examples and check:dual-build-cjs-loads exit 3 (no build; 0 after the full build). check:docs-audit-scope exit 1, a real miss: the new page was not in handwritten-docs.json. It was fixed with `check-audit-scope.mjs --write` and now exits 0. Also scoped eslint on the touched packages exits 0, and check:nul-bytes exits 0. The gates ran at b1ed3d7420. The final head 12daab9542 adds only a clean merge of origin/main, with no change to this PR's own files. CI check-runs are not cited yet because the PR was just opened; the seat holds its events.", "dogfood_battery": "12daab9542 · os-verify-lock: VERDICT command-exit 1 · held the lock 1022s · Test Files 1 failed | 295 passed | 3 skipped (299) · Tests 1 failed | 2522 passed | 18 skipped (2541). The one failure: test/authz-probe-blind-spot.test.ts > packages/runtime/src/domains/i18n.ts — every positive control is still present in THAT file: control \"handleI18nRequest\" moved, expected 3, got 4. It is red on main too: neither packages/runtime/src/domains/i18n.ts nor the census test differs from origin/main, and the count moved with #25956. It is not this PR's failure.", "mcp_calls": "0 — no MCP GitHub calls", "api_writes": "2 — relay strokes: (1) pr_create via fleet-write repository_dispatch (request fw-20261026T080426Z-b519b6, run 40289773646 success, read-back 6642/6642 bytes identical) creating #25986; (2) this os-dev-report comment on #25792 via post-stamped.mjs. Plus git push (not REST). Write budget 3/3: push, pr_create, report. One REST GET (a read, no write) read back the PR (draft true, assignee os-project-manager).", "open_questions": [ { "question": "Item 6a: what is the fate of PackageDependencySchema (marketplace/package-version.zod.ts: an array of { packageId, versionRange, optional }), the second dependency spelling, whose `optional` arm no install path honours? D2 reads only ManifestSchema.dependencies ({ id: range }) and fails closed. Measured at 12daab9542: zero readers of PackageDependencySchema, `versionRange` or `optional` outside packages/spec (packages/, ui/, examples/, apps/; the only hits are D2's own doc comments and a negative test that pins the array shape as unreadable-declaration).", "options": [ "A Retire PackageDependencySchema (with `optional`) now, as its own spec PR with retiredKey() tombstones and the spec-property-retirement kit.", "B Keep it and wire `optional` into D2 (a missing optional entry is a warning, not a refusal), so the marketplace version record becomes a second input to the check.", "C Leave it declared and unwired, and record it as a known implementation gap." ], "recommendation": "A. Business need: zero readers and no producer measured, and HotCRM uses only the record spelling, so it serves no real scenario. Long-term soundness: one dependency spelling (ManifestSchema.dependencies) that the runtime enforces, which is contract-first; B adds a second input and a lenient arm to a gate that ADR-0141 made fail-closed. Preventing AI mistakes: a declared `optional` that nothing enforces is exactly a capability an AI can declare that the runtime does not honour, and C keeps that trap; A removes it and turns any use into a loud parse refusal. Startup scope: retire immediately, with no staged window and no named external user, and no new gate. Cost: A is one spec retirement PR (about 1 day with the kit); B is a D2 extension plus docs (about 1-2 days) and widens the surface." }, { "question": "Item 6b: what is the fate of AppManifestSchema.dependencies (system/app-install.zod.ts: z.array(z.string()), 'other apps that must be installed first')? It is a third, versionless dependency spelling. Measured at 12daab9542: zero readers of AppManifestSchema outside packages/spec.", "options": [ "A Retire AppManifestSchema.dependencies now (tombstone it to point at manifest.dependencies), in the same spec PR as 6a.", "B Wire it into D2 as presence-only entries (any version).", "C Leave it declared and unwired." ], "recommendation": "A. Business need: no reader, no producer, no HotCRM use. Long-term soundness: the install check judges one declaration, and a versionless list duplicates manifest.dependencies with weaker semantics. Preventing AI mistakes: an AI writing it gets nothing enforced today (C), and B would let it skip ranges, which is lenient on the consumer side; A makes the record spelling the only one and refuses the other loudly. Startup scope: immediate retirement, no staged window, no new gate. Cost: a small addition to the 6a retirement PR." }, { "question": "Declaration line: should the PR keep `Clause-②: yes (narrowing)` (measured), or take the claim's `no (narrowing ...)`? Metadata-protocol is `none` either way. That line is borderline because a dependency range outside the grammar (e.g. `latest`) still parses but is now refused at install; precedents #25954 and #25964 read a run-time refusal as `none`.", "options": [ "A Keep `Clause-②: yes (narrowing)`: the diff adds public surface (DEPENDENCY_MISSING ledger code, new @objectstack/metadata-core exports) and narrows install acceptance.", "B Change it to `no (narrowing: ...)`, treating the new exports as internal plumbing. The seat would edit the PR body and the changeset." ], "recommendation": "A, because AGENTS.md counts a new ledger code and new public exports as a surface change, and the BREAKING banner, ADR-0087 marker and full dogfood run that the (narrowing) arm requires are already in place. If the seat rules B, the PR body edit goes through the seat (the dev writes the body once), and the changeset line is a one-line edit." } ], "out_of_scope_findings": [ "carrier: #25956 (merged) — main's dogfood battery is red on test/authz-probe-blind-spot.test.ts, row packages/runtime/src/domains/i18n.ts: control handleI18nRequest expected 3, got 4. Neither file differs between origin/main and 12daab9542. Noted, not filed. Dedupe words: authz-probe-blind-spot, handleI18nRequest, i18n census, positive control moved.", "carrier: the D5 PR (next PR on #25792) — First readings 2 and 6 were not measured here, because they belong with D5 and the HotCRM lane. Noted, not filed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed (D2)
PR #25986 merged at 2026-10-26T08:58Z by the merge queue as
3446d43d40onmain(Part of #25792; ACCEPT6411478575, contract review6411460417).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-25986-*'origin/mainby contentgit grep -c DEPENDENCY_MISSING origin/main -- packages/metadata-core/src/protocol-handshake.tsgit merge-base --is-ancestor 3446d43d40 origin/main422 DEPENDENCY_MISSING) at the three install acts, one fail-closed range reader, the ledger row and the docs.optional) and AppManifestSchema.dependencies, now that ADR-0141 D2 judges manifest.dependencies alone #26000 (domain:spec).Part of),pm:dispatchedstays.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25792, "status": "done", "branch": "claude/project-thread-78l2jg", "pr": "https://github.com/objectstack-ai/objectstack/pull/26054", "session": "session_01NndsH9Qm7k6AS2i2fWbjNA", "premise_still_valid": true, "summary": "PR #26054 is draft, head 10c01a4181, Part of #25792, 17 files, +885/-41. It delivers card items 4 and 5 (ADR-0141 D5) and the two #25986 review notes, on a branch reset to origin/main 3518664cb6. Item 4: SchemaRegistry.installPackage builds one co-owner map (coOwnersOf), read by both the namespace gate and refuseCoOwnedObjectNameCollision. Its sources are artifact (unchanged); dependency (P declares an installed X holding N, or a sitting owner of N declares P, so the relation reads from either end); and sibling (S and P both directly declare one installed package holding N). DUPLICATE_ARTIFACT_OBJECT_NAME is worded per source and carries coOwnership. NAMESPACE_CONFLICT names the declaration as the fix. Item 5: the new @objectstack/core orderHydrationBatch sorts through resolvePluginOrder with optional edges; cycle-internal edges are dropped, the members keep source order and are logged, and nothing throws. Both the sys_packages hydrate (row = node) and the install-local kernel:ready rehydrate (ledger entry = node; a declared id resolves to the entry carrying it) use it. Review notes: an undecided union with a readable installed version now reports unreadable-range, and install-local no longer counts the wrapper id as delivered (DeclaredDependencyCheck.deliveredIds removed; unreleased). Docs: app-dependencies.mdx and the plugin-spec callout. Pushing: the first push was refused by check:commit-card-trailers because the unpublished commits carried `Part of #25792`. I reworded them with a soft reset onto the merge base before the first push, so no published history was rewritten. The remote branch had been deleted after #25986 landed, so no force push was needed.", "tests": "Red first, on main: the registry-artifact-co-ownership.test.ts D5 describe had 5 failed / 10 passed (the sixth case is a negative control); hydration-dependency-order.test.ts (service-package, real SchemaRegistry) had 2 failed; marketplace-install-local-rehydrate-order.test.ts had 2 failed; the found-label case and the wrapper-id case had 1 failed each. All are green after wiring. Package suites after a full build (VERDICT command-exit 0, 119/119 tasks), all typecheck exit 0: core 120 files / 2774 tests; objectql 470 / 8748; service-package 10 / 127 (re-run on the final head 10c01a4181); cloud-connection 58 / 732; metadata-core 20 / 818. Ablations (scripts/ablation-replace.mjs, every restore blob == HEAD): parent arm removed, 3 failed; reverse arm disabled, 2 failed (either end, upgrade); sibling arm removed, 1 failed; sibling arm reading any shared declared id instead of an installed parent holding N, 1 failed; sys_packages hydrate in list order, 1 failed; install-local rehydrate in listed order, 2 failed; cycle edges kept, 1 failed; found-label fix reverted, 1 failed.", "gates": "dispatch-gates --commands derived 106 families, each run with its exit code captured before any pipe; `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran d5-gates-ran.txt` exits 0 with '106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN'. One family found a real miss on first run: check-registry-log-declared exited 1 because the service-package suite now builds a SchemaRegistry. It was fixed by adding OS_REGISTRY_LOG to the vitest.config.ts env (commit 10c01a4181) and now exits 0. Re-deriving on 10c01a4181 gives the same 106 commands. Scoped eslint on the 13 touched .ts files: exit 0. check:nul-bytes: exit 0. CI check-runs are not cited because the PR was just opened; the seat holds its events.", "dogfood_battery": "968c670094 (the final head 10c01a4181 differs only by the service-package vitest.config.ts env line) · os-verify-lock: VERDICT command-exit 1 · held the lock 1153s · Test Files 1 failed | 295 passed | 3 skipped (299) · Tests 1 failed | 2522 passed | 18 skipped (2541). The one failure is test/authz-probe-blind-spot.test.ts > packages/runtime/src/domains/i18n.ts: control handleI18nRequest expected 3, got 4. This is main's red since #25956, the same row reported on #25986, still present at origin/main 3518664cb6. This PR touches neither file.", "mcp_calls": "0 — no MCP GitHub calls", "api_writes": "2 — relay strokes: (1) pr_create via fleet-write repository_dispatch (request fw-20261026T101417Z-1cfc41, run 40305827913 success, read-back 8289/8289 bytes identical) creating #26054; (2) this os-dev-report comment on #25792 via post-stamped.mjs. Plus git push (not REST). Write budget 3/3: push, pr_create, report. Reads only: REST GETs of #25792, the review comment 6411460417 and the new PR (draft true, assignee os-project-manager).", "open_questions": [], "out_of_scope_findings": [ "carrier: #25956 (merged) — main's dogfood battery is still red on test/authz-probe-blind-spot.test.ts, row packages/runtime/src/domains/i18n.ts (handleI18nRequest expected 3, got 4) at origin/main 3518664cb6. Noted, not filed. Dedupe words: authz-probe-blind-spot, handleI18nRequest, i18n census, positive control moved.", "carrier: ADR-0141 Open items 2 — First reading 2, from the code and not instrumented. ObjectQLPlugin's kernel:ready seals and the install-local ledger rehydrate both run on kernel:ready in hook-registration order, and the install-local plugin declares no ordering edge against ObjectQLPlugin. Each hydrate is now sorted within itself. One case stays open by design: two siblings in sys_packages (hydrated at service-package start) whose parent is only in the install-local ledger (hydrated later at kernel:ready); the second sibling is refused NAMESPACE_CONFLICT. Noted, not filed.", "carrier: the HotCRM lane (hotcrm#2271) — First reading 6 (the type of the artifact `os build` writes for a standalone module) was not measured here. The D5 companion precondition holds at hotcrm d91c7b0: service, marketing and revenue each declare app.objectstack.hotcrm. Noted, not filed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
PR #26054 merged at 2026-10-26T11:37Z by the merge queue as
be7897799bonmain(Part of #25792; ACCEPT6413749310).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26054-*'origin/mainby contentgit cat-file -e origin/main:packages/core/src/hydration-order.tsgit merge-base --is-ancestor be7897799b origin/mainState: card stays open (
Part of),pm:dispatchedstays.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim amendment: PM loop round 5 · card thread · item 3 (the Console refusal text)
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01NndsH9Qm7k6AS2i2fWbjNA(same thread as D2 #25986 and D5 #26054)Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-78l2jg(the card thread's harness-pinned branch, restarted fromorigin/mainafter #26054 merged)Domain:
domain:serverSeat:
domain:server#1Ruling-ref: #25403 B′ (6369704387): "one runtime card for the pre-install check … with the console's refusal text". The original claim routed item 3 to objectui's card. objectui merged into this repo's
ui/on 2026-10-26, and no objectui card was filed (dedupe below), so item 3 now rides this card.Scope: item 3 only. The Console renders the
422 DEPENDENCY_MISSINGrefusal at its install surfaces in the author's terms (「请先安装 HotCRM 6.x」), built from the refusal's own facts (one line perdependency_missingissue: id, range, what was found), with i18n keys in every locale the Console ships.File surface:
ui/packages/app-shell/src/console/marketplace/**(marketplaceApi.ts,MarketplacePackagePage.tsx,MarketplacePage.tsx,MarketplaceRowActions.tsx),ui/packages/app-shell/src/views/metadata-admin/PackagesPage.tsx,ui/packages/i18n/src/locales/*.ts(new keys only), their tests, one changeset. ⛔ Nopackages/**server change, ⛔ nopackages/spec/**, ⛔ nodocs/adr/**(stop on breach; explain in the report)Container & model:
M,mode:thread, model and effort as the thread was openedClause-②: no (a client rendering of an existing refusal; no server behavior changes)
Metadata-protocol: none
Responsibility: n/a (ADR-0141 runtime card, owed by landed work PR #25717)
Dedupe: objectui issues "Console install refusal text missing dependency ADR-0141" → 0; objectstack open "Console install refusal text DEPENDENCY_MISSING 请先安装" → only this card
Premise: holds (read 2026-10-26T11:39Z at
origin/mainbe7897799b:ui/**holds noDEPENDENCY_MISSING/dependency_missingreader; the server side landed in #25986)Serial constraints cleared: no open PR touches the marketplace files or
PackagesPage.tsx; #25987, #26040, #26044, #26045, #26048 and #26055 add keys toui/packages/i18n/src/locales/*.ts(additive; whichever lands second mergesmain)Remains on the card after this slice: item 6 (#26000,
domain:spec)objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25792, "status": "done", "branch": "claude/project-thread-78l2jg", "pr": "https://github.com/objectstack-ai/objectstack/pull/26123", "session": "session_01NndsH9Qm7k6AS2i2fWbjNA", "premise_still_valid": true, "summary": "PR #26123 is draft, head f76c765089, Part of #25792, 21 files, +734/-10. It delivers card item 3 (claim amendment 6415110715) on a branch restarted from origin/main be7897799b, with origin/main merged in once (merge base f6c619329a). The Console now draws the 422 DEPENDENCY_MISSING refusal at the three install surfaces that go through install-local: the package page Install, the catalog page org install on a self-hosted runtime, and Setup -> Packages -> Import. Each shows a heading saying nothing was installed, then one line per details.unmet[] entry built from its own facts (dependency id, range, found, installedVersion), e.g. zh-CN '• 请先安装 app.objectstack.hotcrm 6.x。' and en '• Install com.acme.billing ^2.1.0 first. Version 1.4.0 is installed.'. New module console/marketplace/dependencyMissing.ts: readDependencyMissing is all-or-nothing, so an unknown found or an incomplete entry keeps the server message; dependencyMissingText; useDependencyMissingText. installLocal() now carries details on its error; PackagesPage apiJson carries code and details (no existing caller reads either). The org strip and the import note gained whitespace-pre-wrap. Keys marketplace.install.dependencyMissing.{heading,absent,outOfRange,unreadableRange,unreadableVersion,unreadableDeclaration} are in all ten locale packs. Setup -> Packages reads them through the hook because its engine.* table is en/zh only. The dependency is shown by package id, not a display name such as 'HotCRM': the refusal's facts carry no display name, and adding one would be a server change outside this claim. Two departures from the listed surface, neither in packages/**, packages/spec/** or docs/adr/**: (1) scripts/sdui-manifest.record.json. The i18n locale packs are registry sources, so check-sdui-manifest exited 1 until the recorded digest was regenerated with node scripts/gen-sdui-manifest-node.mjs; sdui.manifest.json is byte-identical. It was regenerated again to resolve the merge-main conflict on that line. Every other open PR that adds locale keys will hit the same line. (2) The changeset was first graded minor. CI Check Changeset refused minor under Metadata-protocol: none, so it is now patch for @object-ui/app-shell and @object-ui/i18n (commit f76c765089). Declarations stay as claimed: Clause-②: no, Metadata-protocol: none.", "tests": "4 new files, 12 tests. Red first, with the wiring reverted (the source patch removed and re-applied): 5 failed / 7 passed. The passing ones are the controls and the pure reader/text cases. The 5 that failed: installLocal carries details; the package page lines; the org-install lines; the import door in zh and in en. After wiring: 12/12. Ablations (scripts/ablation-replace.mjs, after commit, every restore blob == HEAD), all red: hook removed at the import door, 2 failed; hook removed at the org install, 1 failed; details dropped in installLocal, 2 failed; details dropped in apiJson, 2 failed; whitespace-pre-wrap dropped on the org strip, 1 failed. Wider runs under os-verify-lock: marketplace/ + views/metadata-admin/ + packages/i18n gave Test Files 727 passed, Tests 8276 passed / 14 skipped (VERDICT command-exit 0). After merging main: the 4 new files + packages/i18n gave 91 files, 1365 passed / 13 skipped. app-shell tsc --noEmit exit 0. The ui i18n checks all exit 0: check-i18n-call-site-keys (re-run after the merge), en-drift, dead-keys, designer-table-parity. The first full build exited 2 on a TS18049 null narrowing in dependencyMissing.ts; fixed before commit. One local note: ui/scripts/check-changeset-presence.mjs exits 1 because it still reads ui/.changeset, while @object-ui changesets now live in the root .changeset (as #26037 did). It is not a CI gate here, and CI Check Changeset reads the root.", "gates": "dispatch-gates --commands derived 34 families on the first commit and 46 after the sdui record commit. Each was run with its exit code captured before any pipe. First sweep: check-sdui-manifest exit 1 (a real miss, fixed as above), all others exit 0. After merging main, the full 46 were re-derived and re-run: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran i3-ran2.txt exits 0 with '46 derived, 46 run, 0 NOT-MEASURED, 0 UNRUN', all 46 exit 0. Scoped eslint over the 9 touched app-shell files and the 10 locale packs: red first (exit 1, 'waitFor' unused import in a new test), then exit 0 with 0 errors (only warnings already in those files). CI on head f76c765089: 61 check-runs, 54 success, 7 skipped, 0 failed. The one earlier red, Check Changeset on 2a7e4c4776 (the minor grade), is fixed by f76c765089.", "dogfood_battery": "Not run locally: the diff touches only ui/** client code, the locale packs, one changeset and the sdui record. CI 'Dogfood Regression Gate' is success on f76c765089.", "mcp_calls": "0 — no MCP GitHub calls", "api_writes": "2 — relay strokes: (1) pr_create via fleet-write repository_dispatch (request fw-20261026T125503Z-cbe662, run 40326238152 success, read-back 5299/5299 bytes identical) creating #26123, assignee os-project-manager. An earlier dispatch was refused locally with zero writes because pr_create takes no 'draft' key; the PR opened as draft anyway. (2) This os-dev-report comment on #25792 via post-stamped.mjs. Plus 2 git pushes (not REST). Reads only: REST GETs of the PR, its check-runs and the Check Changeset annotations.", "open_questions": [], "out_of_scope_findings": [ "carrier: #25831 (ui/.changeset aftercare) — ui/scripts/check-changeset-presence.mjs still reads ui/.changeset, so locally it reds any ui change whose changeset sits in the root .changeset. Noted, not filed. Dedupe words: changeset-presence, ui/.changeset, root changeset, changesetConfigPath.", "carrier: the sdui manifest record — every PR that edits a ui registry source (including the locale packs) moves scripts/sdui-manifest.record.json's single digest line. Concurrent PRs (#25987, #26040, #26044, #26045, #26048, #26055 add locale keys) will each conflict there and must regenerate after merging main. Noted, not filed.", "carrier: ADR-0141 D2 refusal facts — details.unmet[] carries no display name for the dependency, so the Console says 'app.objectstack.hotcrm 6.x' rather than 'HotCRM 6.x'. A display name would need a server change. Noted, not filed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
PR #26123 merged at 2026-10-26T18:21Z by the merge queue as
934810cc57onmain(Part of #25792; ACCEPT6417558550).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26123-*'origin/mainby contentgit cat-file -e origin/main:ui/packages/app-shell/src/console/marketplace/dependencyMissing.tsgit merge-base --is-ancestor 934810cc57 origin/mainState: card stays open (
Part of),pm:dispatchedstays.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim amendment: PM loop round 8 · card thread · item 6 (ADR-0141 D8 items 2 and 3)
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01BkRZmjDUobsYmkGEjwjH9U(a new thread; the item 1–5 and item 3 thread has wrapped)Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-tzosbt(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: #25403 B′ (6369704387) and ADR-0141 §D8 as merged (
afe6ab9ccc): the runtime card owes "a stated fate foroptional" and "a stated fate forAppManifestSchema.dependencies… decided with a measured reader count". A fate that needs a maintainer call is reportedneeds_decisionon the four axes, not picked.Scope: item 6 only. Items 1, 2, 4, 5 landed in #25986 and #26054, item 3 in #26123, and item 7 is on main in
content/docs/deployment/app-dependencies.mdx.File surface:
packages/spec/src/marketplace/package-version.zod.ts(PackageDependencySchema.optional),packages/spec/src/system/app-install.zod.ts(AppManifestSchema.dependencies), their tests, the ADR-0087 conversion layer where a retirement needs it, the regeneratedcontent/docs/references/**pages, a dated addendum line in ADR-0141 §D8, and a changeset. ⛔ NoResolvedPackageDependencySchema. ⛔ No snapshot conversion (D8 item 1, its own card).Container & model:
S,mode:thread, default model and effort (set at thread open)Clause-②: measured on the diff — a retirement is
no (narrowing)with the narrowing arm's evidence. The seat runs aCONTRACT_REVIEW_TIERreview before landing (packages/spec/src/**).Metadata-protocol: measured on the diff
Responsibility: two published dependency shapes have no stated meaning beside the record D2 reads, so an author or AI can declare a dependency the runtime never honours (ADR-0141 §D8)
Thread-read: 6424112245
Premise: holds (read 2026-10-27T00:51Z at
origin/main6390fe52ce: card open,pm:dispatched, assigneeos-project-manager; the seat's first reading found no reader ofAppManifestSchemaoutsidepackages/specand the generated reference docs)Serial constraints cleared: the dev re-measures open PRs on the spec files it touches and runs
git merge-treeagainst any overlap beforepr_create.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 25792,
"status": "done",
"branch": "claude/project-thread-tzosbt",
"pr": "#26401",
"session": "session_01BkRZmjDUobsYmkGEjwjH9U",
"premise_still_valid": true,
"summary": "PR #26401 (draft, head c65c564, Fixes #25792, the card's last item) decides ADR-0141 D8 items 2 and 3 on the measured reader count: both retire at once. PackageDependencySchema.optional and AppManifestSchema.dependencies are retiredKey() tombstones with prescriptions pointing at manifest.dependencies; both keys are in RETIRED_KEYS_BY_MAJOR[18]; one D3 semantic entry dependency-unread-spellings-retired plus its STEP18_RATIONALE fragment; no D2 conversion (neither shape is a stack collection member, the kernel/Manifest:capabilities precedent); acceptRetiredDefaultResidue not adopted (no producer emits either default). Measured at origin/main 6390fe5: 0 readers and 0 writers of either key outside packages/spec (packages/, ui/, examples/, apps/), no import of AppManifest outside packages/spec, and 0 in HotCRM at 9464673, whose three modules declare the record (live control). ADR-0141 D8 gains a dated addendum with the counts (Tier H, so the PR needs the maintainer's approval). Generated artifacts and the two reference pages are regenerated; three spec fixtures that authored a retired key now write the live shape. Changeset @objectstack/spec minor, BREAKING, FROM-TO table, marker registered dependency-unread-spellings-retired. Declarations as ordered: Clause-② no (narrowing), Metadata-protocol narrowed. One judgement for the contract review: no snapshot carried today changes (publish uploads the record shape), but the addendum fixes D8 item 1's future projection to packageId and versionRange only.",
"tests": "Red first: the new packages/spec/src/marketplace/dependency-spellings-retirement.test.ts on main 6390fe5 gave 12 failed | 2 passed (the passing two are the matcher anti-vacuity case and the no-conversion case); its tree-scoped absence pin named exactly five authoring sites, all in packages/spec (the two schemas and three tests). Green: that file plus the three edited tests, src/marketplace/ and scripts/step18-rationale-merge.test.ts gave 10 files / 124 tests passed; the 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 two @ts-expect-error tombstone lines compile under tsconfig.test.json). No separate ablation: the change is the tombstones, and the red-first run on main is the run without them.",
"gates": "dispatch-gates --commands derived 118 families at 6548121 and the same 118 at c65c564. Each ran with its exit code captured before any pipe. First pass: 107 exit 0; 10 exit 3 and 1 exit 1 (check:generated naming a stale api-surface, check:dts-closure) while a concurrent dogfood closure build rewrote packages/spec/dist and packages/lint/dist; all 11 re-ran on the settled build and exited 0. One real miss: check:cross-package-test-inputs exit 1, the new absence pin escapes its package but was not in vitest.repo-tests.json; fixed in c65c564, re-run exit 0. dispatch-gates --repo objectstack-ai/objectstack --ran (record with exit codes): exit 0, '118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN', 'a DERIVED zero'. Scoped eslint --no-inline-config over the 9 touched .ts files exit 0; check-nul-bytes exit 0; spec check:generated 14/14 up to date after --fix regenerated check:docs only. Open-PR overlap: #26159 and #26138 share packages/spec/authorable-surface/system.json; a driver-free merge-tree in a throwaway bare clone answers exit 0 with a tree id for both. CI check-runs not cited: the PR was just opened and the seat holds its events.",
"dogfood_battery": "c65c5645cb · os-verify-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 failures are main's: test/authz-conformance.test.ts (6 tests, 'packages/rest/src/rest-route-ledger.ts: docblock says 80 rows, the table holds 81') and test/authz-probe-blind-spot.test.ts (4 tests, control 'this.routeManager.register(' in rest-server.ts expected 62, got 63). The PR touches nothing under packages/rest or packages/qa; the last change to both rest files in the base is 6a2866a (#26361, merged 2026-10-26T23:58Z), and origin/main b33dd25 has no later change to them.",
"mcp_calls": "4 — mcp__github__issue_read x4 (get and get_comments on #25792 and on #26000); no write tool",
"api_writes": "2 — relay strokes: (1) pr_create via fleet-write repository_dispatch (request fw-20261027T021005Z-bb0ef5, run 40436852678 success, read-back 12198/12198 bytes identical, assignee os-project-manager) creating #26401; (2) this os-dev-report comment on #25792 via post-stamped.mjs. Plus 4 git pushes (not REST). Write budget 3 kinds: push, pr_create, report. Reads only otherwise: REST GETs of claim comment 6430963835, the open-PR list and files, and #26401.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: the full dogfood battery on main (CI's scheduled and push runs) · evidence: test/authz-conformance.test.ts 'rest-route-ledger.ts: docblock says 80 rows, the table holds 81' and test/authz-probe-blind-spot.test.ts 'this.routeManager.register( expected 62, got 63' at base 6390fe5, untouched by this PR · carrier: #26361 (merged; added the route) · noted, not filed; for the main-red convention · dedupe words: rest-route-ledger docblock 80 rows, authz-probe-blind-spot routeManager.register, approval lock read route",
"carrier: cloud — not measured: how the control plane parses sys_package_version.manifest_json. If it parses with PackageManifestSchema and stores array entries carrying optional, the new tombstone refuses them on the next spec bump. Per the cloud ruling, a cloud-lane card is the seat's to file · dedupe words: manifest_json parse, PackageManifestSchema optional, cloud snapshot dependencies",
"carrier: #25794 — D8 item 1's record-to-array conversion must now project packageId and versionRange only (no optional); the ADR-0141 D8 addendum says so · noted, not filed",
"carrier: #26000 — overlap: its 6b (AppManifestSchema.dependencies) and the optional part of 6a are delivered by #26401; the rest of 6a (retiring PackageDependencySchema whole) still collides with D8 as the spec seat's retriage comment 6430707402 says · for triage's re-scope · noted, not filed",
"carrier: none — AppManifestSchema as a whole and IAppLifecycleService have no reader or implementer outside packages/spec (dormant; outside item 6) · in the PR's Acceptance notes only"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Contract review
Served-tier:
CONTRACT_REVIEW_TIERHead-sha:
c65c5645cbeb0a7137e73bedd962003e40bc71e3Local-runs: none
Scope: packages/spec/src/marketplace/package-version.zod.ts, packages/spec/src/migrations/entries/retired-keys/18.marketplace__PackageDependency__optional.ts, packages/spec/src/migrations/entries/retired-keys/18.system__AppManifest__dependencies.ts, packages/spec/src/migrations/entries/semantic/18.dependency-unread-spellings-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/system/app-install.zod.ts, docs/adr/0141-inter-app-dependency-declared-checked-before-install.md + packages/spec/src/shared/retired-key.ts, packages/spec/src/migrations/entries/retired-keys/18.kernel__Manifest__capabilities.ts, packages/spec/src/marketplace/dependency-spellings-retirement.test.ts, packages/spec/src/kernel/package-dependency-dual-source.test.ts, packages/spec/src/system/app-install.test.ts, packages/spec/src/contracts/app-lifecycle-service.test.ts, packages/spec/authorable-surface/{marketplace,system}.json, packages/spec/authorable-defaults/{marketplace,system}.json, packages/spec/vitest.repo-tests.json, scripts/cross-package-test-inputs.mjs, packages/core/src/artifact-packages.ts, packages/cli/src/commands/package/publish.ts, packages/metadata-core/src/protocol-handshake.ts, .changeset/25792-retire-dependency-spellings.md, content/docs/references/marketplace/package-version.mdx, content/docs/references/system/app-install.mdx. On the narrowing face no path of the diff is a remainder.
Inputs read: card #25792 body and all 11 comments (claim 6409455733, amendments 6415110715 and 6430963835, dev reports 6411207761 / 6413551415 / 6416975195 / 6432027506, landed records); PR #26401 body, 19-file list, net diff
origin/main...pr-26401; #26000 body and retriage ask 6430707402; #25794 body; the check-runs on the head; HotCRM fetched to9464673.① Derived judgments
PackageDependencySchema.optional→retiredKey()tombstone — right. The schema is a non-strictz.objectunderlazySchema, so a bare deletion would have been a silent strip (the skill §2 rule); the tombstone makes the removal loud in both channels. Accept-set change: any authored value, the old defaultfalseincluded, is refused at['optional'](inside a snapshot at['dependencies', i, 'optional']) with the prescription; absence stays absence and the parsed entry no longer materializesoptional: false. Published-type change:PackageDependencyParsedlosesoptional; thez.inputtype carries the retired mark (tsc channel, compiled bytsconfig.test.jsoninclude: ["src/**/*"], so the@ts-expect-errorpins are not phantom checks).AppManifestSchema.dependencies→retiredKey()tombstone — right, same reasoning (non-strict object). Every value is refused,[]and the record spelling{ id: range }included (the test pins that the record spelling is not silently re-read at this key); absence stays absence,AppManifestParsedlosesdependencies(previously materialized[]). D8 item 3 offered projection-or-retirement "decided with a measured reader count"; zero readers makes projection a shape nobody reads, so retirement is the right arm.was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0141 D8)(the house spelling, 34 sibling sites spell18 (), a dash clause on why it was inert,Delete the key.plus the live mechanism (manifest.dependencies, the D2 check). Noos migrate metasentence — correct, because no conversion covers either surface; adding it would have been the false claim the class pin forbids.18.kernel__Manifest__capabilities.tssays exactly this and was read. The new test pinsALL_CONVERSIONSnames neither surface.acceptRetiredDefaultResiduenot adopted — right on the measured population, with one unmeasured arm (③.1). The residue stage exists for a materialized default in already-built artifacts. The only producer that could materializeoptional: falseis a parse ofPackageManifestSchemaover an array-shapeddependencies; at the head no file outsidepackages/specimportsPackageManifestSchema,PackageVersionSchemaorPackageDependencySchema(only prose: a comment inpackages/core/src/artifact-packages.ts, a comment inui/.../waitForServedApp.ts, CHANGELOGs),publish.tsuploadsdist/objectstack.jsonas it is, and the record shape is what every measured artifact carries (artifact-packages.ts#declaredDependencyIdstreats an array as declaring nothing). So no emitted population exists in this repository or HotCRM.AppManifestSchemais parsed nowhere outside spec, sodependencies: []was never emitted.packages/specacrosspackages/,ui/,examples/,apps/: 0 readers of either key, 0 imports ofAppManifest/AppManifestSchema, 0 writers ofoptionalbesideversionRange. HotCRM at9464673(fetched; the local checkout was ata4b3560, re-measured at both): 0 of either;src/{marketing,revenue,service}/index.tsdeclare{ 'app.objectstack.hotcrm': '^6.0.0' }in the record — the live control. Thedependencies: ['com.objectstack.plugin-auth']arrays inpackages/apps/{account,setup,studio}/src/index.tsaretype: 'plugin'manifest headers (the kernel plugin-validatorstring[]shape), notAppManifest, so they are not writers of the retired key.entries/README.md(major, category, def and key joined by__, e.g.18.marketplace__PackageDependency__optional.ts), values spelled exactly as theauthorable-surfacerows minus[RETIRED]; one D3 semantic entry per family with non-emptyreasonandacceptanceCriteria; aSTEP18_RATIONALEfragment with a uniqueorder: 110;registry.tsis generated and gitignored, so correctly absent from the diff. Changeset markerregistered dependency-unread-spellings-retirednames an id this diff adds (Check Changeset: success).authorable-surfacerows gain[RETIRED], the twoauthorable-defaultsrows leave, both reference pages regenerated;api-surface/,json-schema.manifest/andauthorable-surface.base.jsondo not move, which is the expected reading for a key-level tombstone (the def survives). The liveness ledger walks nomarketplace/PackageDependencyorsystem/AppManifestshape (no such ledger file), so no row to flip (Spec property liveness: success).package-dependency-dual-source.test.tskeeps['optional','packageId','versionRange']as the cloud-side key set with a note thatoptionalstays as a tombstone (a tombstone keeps the key in the walked shape); the two other fixtures drop the key. The absence pin is tree-scoped overpackages/,examples/,skills/,content/,scripts/, every root declared under@objectstack/specinscripts/cross-package-test-inputs.mjsand in turbo'stest:repoinputs, and the file is invitest.repo-tests.json.ui/andapps/are outside the pin's declared radius (the bound is stated in the pin); they are covered by the grep measurement above, not by the pin.packageId+versionRangeonly. The original D8 paragraph above it still reads "optional=false"; the addendum below governs. Note:docs/adr/**is Tier H, so the maintainer's approving review is owed on this PR independently of this record.② Semver level
@objectstack/specminorwith the BREAKING banner, the FROM → TO table and the one-line fix (Delete the key.) — matches what the diff publishes: two authorable keys tombstoned, two parsed types narrowed, no export removed.majoris refused in the launch window (check-changeset-no-major), sominor+ BREAKING is the correct spelling.Clause-②: no (narrowing)andMetadata-protocol: narrowedappear line-initial in both the PR body and the changeset body and match the kind: a key tombstoned isnarrowed, and the level is at leastminoron the moved package. The narrowing arm's evidence is present: red-first onmain6390fe52ce(12 failed / 2 passed), the tree-scoped absence pin, and the dogfood battery on the head (2 failed files, bothpackages/restledger counts anchored at #26400 and red onmainitself; this PR touches nothing underpackages/restorpackages/qa).③ Boundary flags
sys_package_version.manifest_jsonreader — unmeasured; escalate, not a condition of this PR. Ifobjectstack-ai/cloudparses snapshots withPackageManifestSchemafrom the published spec and persists the parsed output, stored rows would carryoptional: falseand the tombstone would refuse them on the next spec bump — the feat(spec): retired-defaulted-key tolerance — the retired default parses as inert residue and strips; non-default values keep the loud refusal (#12497 class rule) #12840 class, whose ruling was recorded onobjectstack-ai/cloud#1685. ADR-0141 D8 item 1: one record-to-array conversion so marketplace snapshots carrydependenciesinPackageDependencySchemashape — read cloud'smanifest_jsonreader first #25794's step 1 already owes a reading of that parser; the seat should widen that reading to "does the parser persist array entries carryingoptional", or file it as its own cloud-lane card per the cloud ruling, and, if yes, landacceptRetiredDefaultResidue(PackageDependencySchema, { optional: false })in a cloud-lane child before the bump reaches cloud. Not a condition here because every measured snapshot carries the record shape, so no array entry exists to have materialized the default.dependenciesinPackageDependencySchemashape — read cloud'smanifest_jsonreader first #25794's body is now stale on item 2 ("optional=false"): the addendum fixes the future projection topackageIdandversionRangeonly. The seat amends ADR-0141 D8 item 1: one record-to-array conversion so marketplace snapshots carrydependenciesinPackageDependencySchemashape — read cloud'smanifest_jsonreader first #25794's body before it dispatches; the ADR governs, the card follows.optional) and AppManifestSchema.dependencies, now that ADR-0141 D2 judges manifest.dependencies alone #26000 overlap — for triage's re-scope. 6b and theoptionalhalf of 6a are delivered by this PR; the rest of 6a (retiringPackageDependencySchemawhole) collides with D8 and ADR-0141 D8 item 1: one record-to-array conversion so marketplace snapshots carrydependenciesinPackageDependencySchemashape — read cloud'smanifest_jsonreader first #25794, as retriage ask 6430707402 says. After this lands, spec: retire the two unread dependency spellings, PackageDependencySchema (withoptional) and AppManifestSchema.dependencies, now that ADR-0141 D2 judges manifest.dependencies alone #26000 is either closed as superseded or re-scoped to what remains, by triage.publish.tsuploads the compiled artifact; D2 reads the manifest record only; no reader ofPackageManifestSchemaexists outside spec.open_questions: empty — nothing further to answer. Its out-of-scope findings 1–5 are covered by ②'s dogfood reading and flags 1–3 above; the dormantAppManifestSchema/IAppLifecycleService(no implementer) is noted, not changed, and outside item 6.main(dogfood: main's Dogfood Regression Gate (1/3) and (3/3) are red since #26361 — the authz census still pins rest-route-ledger.ts at 80 rows and rest-server.tsthis.routeManager.registerat 62; #26361 made them 81 and 63 #26400) namepackages/restledger counts only.Implemented-by:
claude/project-thread-tzosbtReviewed-by:
session_01R1q2xdtYgU6FKrZ7LgjPCCVERDICT: PASS
objectstack-fleet commentedon Oct 27, 2026
Landed
PR #26401 merged at 2026-10-27T03:16Z by the merge queue as
436d284069onmain(Fixes #25792; ACCEPT6432355909).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26401-*'origin/mainby contentgit cat-file -e origin/main:packages/spec/src/migrations/entries/semantic/18.dependency-unread-spellings-retired.tsgit merge-base --is-ancestor 436d284069 origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code