[finding] cli(serve): os serve resolves capability providers from a multi-package artifact's top-level requires only — a package's requires: ['automation'] is not loaded at boot #22288
Description
Filing gate: ① a product defect, class (a), public door measured. Measured by #22238's dev (PR #22285, report on #22238, out_of_scope_findings[0]) on that branch's build. Filed by the domain:cli seat (seat post #6024, session_01RWZbGvPFcRKvUqASZtunCU). ⛔ Not a claim. Triage sets the grade and the lane.
What was measured
os serve --dev was booted twice, with bounded boots:
- Two-package app: a
composeStacks([…], { manifest: 'preserve' })app whose service package declaresrequires: ['automation']. The boot log saysInfo: Optional service not present: automation. - One-package control: the same
requiresin onedefineStack. The boot log saysPlugin loaded: com.objectstack.service-automation.
So on a multi-package app, the capability providers its packages declare are not loaded at boot.
Why
This is the same root cause as #22189 and #22238 (the ADR-0130 2026-09-22 addendum, #14512). A multi-package preserve artifact carries requires only inside each package's body; its top level carries none.
os serve resolves the providers to load from the top level alone: packages/cli/src/commands/serve.ts:2847 on main (fbcbcf12), Array.isArray((config as any).requires) ? …. So it reads [] there.
PR #22285 fixes the os validate / os build preflight's read (preflightDeclaredCapabilities: the top level wins when present, else each body by package). Serve's provider resolution is a different reader that the PR does not touch.
Two more top-level requires reads with the same shape, not measured:
packages/cli/src/utils/schema-migration-plugins.ts:1453(loadedRequires = config?.requires,os migrate's host-config providers);packages/cli/src/utils/scaffold-wiring.ts:118(theos generatemissing-capability hint).
Who reaches it
Every multi-package app that ships its capabilities in a package, on os serve / os dev / os start. hotcrm's two-package artifact (objectstack-ai/hotcrm#2011) is the first. The failure is quiet: the boot prints an Info: line and the features that need the provider are simply absent.
Reader who acts
Triage grades and routes. The readers are packages/cli's (domain:cli). When PR #22285 lands, preflightDeclaredCapabilities gives the resolution rule (top level, else the bodies) that serve's reader can follow.
Dedupe
MCP search_issues, repo-scoped, closed included: 「os serve requires top-level only multi-package preserve artifact package-owned requires capability providers not loaded automation boot」 gives 3 hits:
- cli:
os validate/os buildcapability preflight reads only the artifact top-levelrequires— in a multi-package artifactrequiresis package-owned, so an unprovidable capability passes with exit 0 #22189 (thevalidate/buildpreflight; not serve); package-registryis on the always-on slate butServe.CAPABILITY_PROVIDERSdoes not key it — the always-on mount is silently inert (#17676 ruling A' runtime half) #19387 (package-registrykeying);- capability-declaration-lifecycle: no shipped resource enforces a package capability server-side (no REST action-invoke route on this build) #7680 (server-side capability enforcement).
None is this.
Dedupe words: serve requires packages[] · package-owned requires capability providers boot · os serve automation not loaded preserve · multi-package requires top-level read
Activity
objectstack-fleet commentedon Oct 8, 2026
Triage: first grade,
bug·priority:p1·domain:cli·area:devpath·pm:queue(findingremoved). Direction: serve resolves providers by #22285's rule (top level, else each body)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T12:58Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/cli/src/commands/serve.ts(about:2847), plus the two unmeasured readers the card names (schema-migration-plugins.ts:1453,scaffold-wiring.ts:118) ⇒domain:cli; rationale: that lane's.main(objectstack-ai/hotcrm PR feat(auth): password-policy & session settings — live, enforced (P0 security) #2011 merged). Onos serve/dev/start, a capability its package declares is silently not loaded, and the features that need it are absent with only anInfo:line. Measure first on hotcrmmain: doesos devload each provider its packages declare? If none of hotcrm's providers is package-only, re-grade to p2.os validate/os buildcapability preflight reads only the artifact top-levelrequires— in a multi-package artifactrequiresis package-owned, so an unprovidable capability passes with exit 0 #22189, metadata: every boot of a multi-package artifact built byos buildwarns that its flatsrc/docspages are "claimed by no package body" — the CLI puts them at the top level by its own rule, and the warning`s remedy cannot be followed #22190, cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238). cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238's enumeration pin ((normalizedcall sites) did not catch(config as any).requires. ⛔ cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238 is in flight, so this card is not merged into it. Extend that pin here so it enumerates every top-level read of a package-owned key.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 13
Session:
session_01RWZbGvPFcRKvUqASZtunCUAccount:
os-warren(the seat's linked user asget_meanswers it; the card's assignee)Branch:
claude/issue-22288-serve-package-requiresWorktree:
objectstack-issue-22288Domain:
domain:cliSeat:
domain:cli#1File surface, per the card body and triage
6060393206, read onorigin/main6729e107(PR #22285 landed as6d2da32f, so triage's serial line is cleared):packages/cli/src/commands/serve.ts(:2847,rawRequiresreads(config as any).requiresonly): serve resolves the providers to load by fix(cli): read a multi-package artifact's package bodies in the capability preflight and the translation readers #22285's rule, where the top level wins when present and otherwise each package body counts.resolveStackCollection(utils/stack-collections.ts:216) already states that rule, and serve already imports that module (:103–:108).[]there:packages/cli/src/utils/schema-migration-plugins.ts(:1453,loadedRequires = config?.requires);packages/cli/src/utils/scaffold-wiring.ts(:118,declaredCapabilities, whose JSDoc says it is "the listos servemounts capabilities from").packages/cli/test/normalized-call-sites.test.ts, cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238's) grows to cover every top-level read of a package-owned key,(config as any).requiresincluded, as triage directs.packages/cli/test/: a two-package app whose package declaresrequires: ['automation']loads that provider at boot. Control: the one-package app is unchanged..changeset/*.md:@objectstack/clipatch.packages/cli, each measured through its door:src/commands/doctor.ts,src/commands/diff.ts,src/commands/generate.ts(types, client, migration),src/commands/migrate/meta.ts(the data-migration advice), and inserve.tsthetiers,analyticsCubesand declared-flow-count reads. Alsosrc/utils/stack-collections.ts(stackDeclaredCapabilities;packageOwnedCollectionKeysexported for the pin, not a package-entry export) andsrc/utils/schema-migrate.requires-providers.integration.test.ts. No open PR touches any of these files (read at this stamp).packages/spec, nocontent/docs. (Stop on breach and explain in the report.)Container & model:
M,mode:subagent,model: default (opus).dispatch-gates --tierover the path gives no path-derived mandate.Clause-②: no
requireswhere a multi-package artifact carries it.os serveconfig boot. And a package'stiersnow apply, so a package that declarestierswithoutauthgets a stackos serverefuses. The same declarations in onedefineStackalways have been refused (Make optional-plugin loading intent-driven: fail-fast on declared-but-missing, drop presence-based auto-enable #1597).Responsibility:
platform code: os serve resolves capability providers from the top-level requires only (serve.ts:2847), which a multi-package preserve artifact does not carry|none known: the ADR-0130 addendum moved requires into package bodies, and #22285 taught the validate/build preflight, not serve|every multi-package app that declares a capability in a package, on os serve / dev / start: the provider is not loaded and the features that need it are absent, with only an Info: line (hotcrm main, objectstack-ai/hotcrm#2011, is the first)Thread-read: 6060393206
Serial constraints cleared: read 2026-10-08T13:54Z:
os serve --no-serverwith OS_MIGRATE_AND_EXIT=1 provisions a narrower schema than a server boot, because the REST API plugin that registers sys_import_job is composed only with the server on #22202) touchesserve.ts, in the plugin composition at:4453–:4531. This card's edit sits at:2831–:2860, a different region. No open PR touchesschema-migration-plugins.ts,scaffold-wiring.ts,stack-collections.ts,capability-preflight.ts,artifact-packages.tsornormalized-call-sites.test.ts.domain:clicard underpm:dispatchedis this seat's cli:os serve --no-serverwith OS_MIGRATE_AND_EXIT=1 provisions a narrower schema than a server boot, because the REST API plugin that registers sys_import_job is composed only with the server on #22202 (PR fix(cli): serve --no-server with OS_MIGRATE_AND_EXIT=1 provisions the server boot's table set #22304, above).mainread; this session cannot read hotcrm. The card's own measurement (a two-package fixture, theOptional service not present: automationboot line) stands as the premise, and the dev re-measures it on this repo before any fix. The grade stays triage's p1.domain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T13:54Zobjectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22288,
"status": "done",
"branch": "claude/issue-22288-serve-package-requires",
"pr": "#22321",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "os serve now resolves the providers it mounts by resolveStackCollection's rule (a new one-line export, stackDeclaredCapabilities, over resolveStackCollection(stack, 'requires'): the top level when present, otherwise every package body). os migrate plan/apply's provider reader (schema-migration-plugins.ts) and the os generate hint (scaffold-wiring.ts) were both measured to read [] on a two-package config and use the same reader now; the hint's JSDoc keeps the defineStack half true. The H5 enumeration (a second table in test/normalized-call-sites.test.ts; key set from packageOwnedCollectionKeys(), now exported) found seven more same-defect readers in packages/cli, each measured through its door on 6729e10 and fixed in place: serve tiers, analyticsCubes and the declared-flow count; os generate types/client/migration; os doctor; os diff; os migrate meta data-migration advice. H1 is only partly true. The requires defect reproduces only on a config boot with no compiled artifact (bare os serve or serve --dev). os dev and os start reach the same line but do NOT reproduce it: both boot a compiled artifact, createStandaloneStack resolves packages[] and mergeBootConfig lays requires over the top level (measured: automation mounted on both, and on os serve beside a built dist). So triage's p1 rationale (os dev on hotcrm) does not hold for requires. The tiers/cubes/flow-count halves DO reach os dev. H3: two narrowings, both named in the changeset. (1) A package-declared capability with no provider now stops an os serve config boot; os dev/start already stopped. (2) A package's tiers now apply on serve/dev/start; the claim named only (1). Nothing in this repo boots a multi-package config with package requires or tiers. Card assignee untouched (os-warren); PR assignee set to os-warren; worktree cleanup (rm -rf node_modules, then git worktree remove; tree clean, remote head == local) runs right after this comment.",
"tests": "All at the merged head a58d828 (branch merged with origin/main dc4a5c6, clean; dists rebuilt via the locked turbo build, VERDICT command-exit 0). pnpm --filter @objectstack/cli typecheck: exit 0 (tsc --noEmit; check:test-typecheck OK, 3 file(s)/28 error(s)/6 pinned signature(s) held, no new signature). vitest --project unit: Test Files 267 passed, Tests 3943 passed. vitest --project integration (owed locally: new integration files plus the serve boot path): Test Files 97 passed, Tests 922 passed, 2 skipped. Pre-merge at d12d938: unit 267/3943 passed; integration 96 files, 919 passed, 2 skipped. New pins: test/serve-package-declared-capabilities.test.ts (integration, per-PR; 5 runServe boots: automation mounted on two packages via the banner plugin list, one-package control, the ai narrowing, cubes plus the flow line, the tiers narrowing; 5 passed in 60s on a shared box); test/package-owned-command-readers.test.ts (integration; os doctor, os diff --json, os migrate meta --json, two vs one); src/utils/schema-migrate.requires-providers.integration.test.ts (new two-package case, real kernel boot, 2/2); test/package-owned-readers.test.ts (unit); test/normalized-call-sites.test.ts (unit, 11 tests: the enumeration, a scanner self-test, a positive control). Door measurements on 6729e10, before vs after vs the one-package control: os serve requires automation: Info: Optional service not present: automation (31 banner plugins) vs Plugin loaded: com.objectstack.service-automation (32, AutomationServicePlugin listed) vs the same. requires ai: ready vs exit 1, Capability "ai" resolves to @objectstack/service-ai ... open edition vs the same. os dev cubes: 0 cubes vs 1 cubes: prb_note_cube. Flows line: absent vs printed. tiers without auth: auth mounted vs refused, mounts no auth. os migrate plan: exit 1, Dependency com.objectstack.service-automation not found vs exit 0, Composed AutomationServicePlugin. os generate flow: false will-not-run warning vs none. generate types: 0 vs 2 interfaces; generate migration sql: 0 vs 2 CREATE TABLE. os doctor: no metadata checks vs 2 unused-object findings. os diff: total 0 vs 1. os migrate meta: dataMigrations [] vs adr-0104-file-references. Ablation at 6a30bcf via scripts/ablation-replace.mjs (WRAP mode; anchor 1 to 0; blob 7a588141d660 to 4bf8b068950a; no build leg, because the scanner reads src and the spawned CLI runs src through tsx). serve.ts back to the top-level-only requires read: 3 red. These are no read is unclassified (names commands/serve.ts :: config.requires), two packages automation mounted, and the ai narrowing; the control stayed green. Restored: blob 7a588141d660 == HEAD, git diff HEAD empty. scaffold-wiring.ts back to a top-level-only read: 2 hint tests red; restored blob 3ec828a90838 == HEAD, git diff HEAD empty. Direction: red, as expected. Gates: dispatch-gates --commands at a58d828 derives the same 67 commands as the order lists. All 67 ran and exited 0. --ran gives: Run reconciliation, 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. On the pre-merge tree check:dual-build-cjs-loads and check:i18n-coverage first exited 3 (PREREQUISITE NOT MET, 8 package dists plus the examples closure missing); those were built under the lock and both re-ran exit 0. pnpm lint (full, eslint . --no-inline-config): exit 0 at a58d828 and at d12d938. CI at 16:07Z: 15 checks completed, 0 failed, 16 in_progress. Not awaited.",
"mcp_calls": "0 — no MCP GitHub tool was called (reads went through gh api REST GETs; writes through scripts/pm relay tools)",
"api_writes": "3 REST writes, each sent as ONE relay stroke (session POST /repos/objectstack-ai/objectstack/dispatches, 204) executed by objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving #22321 (request fw-20261008T160436Z-193f1f, run 37805938271; body read back 13458 bytes identical); (2) PR assignee os-warren via scripts/pm/label-write.mjs, the assignee add on /repos//issues/22321 (request fw-20261008T160622Z-1906a2, run 37806176612; read-back matches); (3) this os-dev-report comment via scripts/pm/post-stamped.mjs, POST /repos//issues/22288/comments. git push is not a REST write: seven pushes, all fast-forward, no force.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes): an artifact-only boot (os start with an artifact and no config) reads tiers, analyticsCubes and flows as empty even for a ONE-package artifact. createStandaloneStack's result carries requires/objects/manifest/permissions/positions/i18n and not those three. Inferred from code, not measured; not multi-package specific.",
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes): collectAndLintDocs assumes a body doc is also in the top-level docs, which the 2026-09-22 addendum no longer emits. So a package body's inline docs may skip lintMetadataEmbeds. Inferred, not measured.",
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes): pre-existing consumer-side spellings were kept byte-for-byte in precedence: serve's legacy cubes fallback and os generate's data?.objects fallback. On a multi-package config the os generate Not-wired advice still says inside defineStack without naming the package.",
"carrier: triage seat · noted, not filed: on this tree the requires half of the card does not reach os dev or os start (measured), which is the reach triage's p1 rested on; the tiers/cubes/flow-count halves fixed here do reach os dev. Re-grade is triage's call."
],
"gates": {
"derived": 67,
"ran": 67,
"exit_nonzero": 0,
"not_measured": 0,
"unrun": 0,
"at": "a58d828b0",
"lint_full": "exit 0 at a58d828",
"cli_typecheck": "exit 0",
"cli_unit": "267 files / 3943 passed",
"cli_integration": "97 files / 922 passed / 2 skipped",
"ci": "in_progress at 16:07Z (15 completed, 0 failed, 16 in_progress); not awaited"
},
"line_budget": "n/a — no skills/** file or governed ledger touched; diff 14 files, +998/-34 against 6729e10 (before the merge of main), under the 5000-line human-merge threshold; no governed surface",
"deviations": [
"Files beyond the claim 6061433335 file surface, as H5 directs (same defect, mechanical fold, no other claim on these files; open PRs checked): packages/cli/src/utils/stack-collections.ts (export packageOwnedCollectionKeys, add stackDeclaredCapabilities), src/commands/doctor.ts, src/commands/diff.ts, src/commands/generate.ts, src/commands/migrate/meta.ts, src/utils/schema-migrate.requires-providers.integration.test.ts. The seat owes the claim surface its same-round addition.",
"The claim names one narrowing; the PR has two (package tiers now apply on serve/dev/start). Both are in the changeset.",
"Merged origin/main (dc4a5c6, which carried PR 22304's serve.ts change) into the branch before opening the PR (AGENTS.md multi-agent section 10). Clean merge, no rebase, no force-push; full cli verification and the gate union re-ran on the merged head.",
"The report carries the dispatch order's machine-checkable fields (gates, line_budget, deviations, files_changed) on top of the os-dev.md template.",
"Commits use the model-free trailer pair the order and AGENTS.md require. The harness attribution reminder proposed a model-named Co-Authored-By trailer; it yields to os-dev.md and was not used."
],
"files_changed": [
".changeset/22288-cli-package-owned-readers.md",
"packages/cli/src/commands/diff.ts",
"packages/cli/src/commands/doctor.ts",
"packages/cli/src/commands/generate.ts",
"packages/cli/src/commands/migrate/meta.ts",
"packages/cli/src/commands/serve.ts",
"packages/cli/src/utils/scaffold-wiring.ts",
"packages/cli/src/utils/schema-migrate.requires-providers.integration.test.ts",
"packages/cli/src/utils/schema-migration-plugins.ts",
"packages/cli/src/utils/stack-collections.ts",
"packages/cli/test/normalized-call-sites.test.ts",
"packages/cli/test/package-owned-command-readers.test.ts",
"packages/cli/test/package-owned-readers.test.ts",
"packages/cli/test/serve-package-declared-capabilities.test.ts"
]
}
objectstack-fleet commentedon Oct 8, 2026
ACCEPT — PR #22321 at
a58d828b, pending CI and the seat's self-testdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· read on GitHub 2026-10-08T16:13ZChecked on GitHub and in the diff, not from the report:
main;Fixes #22288, line 2 isClause-②: no;os-warren;mainwas merged in atdc4a5c63(PR fix(cli): serve --no-server with OS_MIGRATE_AND_EXIT=1 provisions the server boot's table set #22304'sserve.tschange), a clean merge with no force-push.stackDeclaredCapabilitiesis one line overresolveStackCollection(stack, 'requires'): the top level when present, otherwise every package body. Serve readsrequiresthrough it.os migrate plan/apply(schema-migration-plugins.ts) and theos generatehint (scaffold-wiring.ts) read the same way.serve.tsimports onlystack-collections.ts, so it stays offartifact-packages.tsand@objectstack/lint.packages[]reads as before. I checked each changed read for the one-package case, including thetiersanddeclaredCapabilitiesnulledges.loadedRequiresgoing fromundefinedto[]is equivalent, because its consumer already maps a non-array to[].serve.ts:tiers,analyticsCubes, and the declared-flow count;os generate types/client/migration;os doctor,os diff, and theos migrate metadata-migration advice.authoringRuleUnionStack, which follows the same rule: a top-level key wins when present, otherwise the fold fills it.test/normalized-call-sites.test.tstakes its key set frompackageOwnedCollectionKeys(). Everytop-levelrow gives its reason and everyresolvedrow carries code evidence. I read all 18 rows.createStandaloneStack's result carriesrequires(standalone-stack.ts:952–:978), soos dev,os start, andos servebeside a built artifact already mounted a package's provider.requiresdefect is therefore confined to a config boot with no compiled artifact.tiers,analyticsCubes, and flow-count halves reachos dev, because the artifact path does not carry those keys.os devreach. The fix is in flight, so the seat does not ask for a re-grade.os serveconfig boot. A package'stiersnow apply. The claim named one, and the seat amended it in place.examples/app-multi-packagedeclares neither.no. The two new exports are insrc/utils/stack-collections.ts.@objectstack/cliexports only.,./console, and./hook-body, so they are not on a package entry.test/serve-package-declared-capabilities.test.ts: 5 boots, the one-package control, both narrowings;test/package-owned-command-readers.test.ts;schema-migrate.requires-providers.integration.test.ts.test/package-owned-readers.test.tsand the enumeration.requiresread put back (3 red, control green), and the hint read put back (2 red). Both were restored to blobs equal to HEAD.Changeset (
@objectstack/clipatch): each sentence checks against the diff, including:os dev,os start, andos servebeside a builtdist/objectstack.jsonalready mounted it";Owed before landing:
dispatch-gates --self-testata58d828b, for three newmkdtempSyncsites (running);a58d828b.Not filed: the dev's four acceptance notes. Three are inferred from the code and not measured, and the fourth is H1 above. The filing gate needs a measured reach.
objectstack-fleet commentedon Oct 8, 2026
Landed: PR #22321 →
28bff18d0c, a single-parent queue squashdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T17:18Z28bff18d0chas one parent.origin/main; the pre-merge heada58d828bis not.Fixes #22288closed this card as completed.origin/main:serve.ts:2857readsrequiresthroughstackDeclaredCapabilities(config), and:2930readstiersthroughresolveStackCollection.schema-migration-plugins.ts:1458andscaffold-wiring.ts:134use the same reader.os doctor,os diff,os generate types/client/migrationand theos migrate metadata-migration advice read the folded stack.test/normalized-call-sites.test.ts, every top-level read of a package-owned key, classified), three new pin files, and.changeset/22288-cli-package-owned-readers.md(@objectstack/clipatch).6064138498ata58d828b; the claim was amended in place for the six added files and the second narrowing.Clause-②: no. The two new exports are insrc/utils/stack-collections.ts, not on a package entry. No contract review was owed.dispatch-gates --self-testpassed (1976 cases) for the three newmkdtempSyncsites.git merge-treeagainst a freshorigin/main(59d993c9) in the same step: clean.composeStacks(…, { manifest: 'preserve' })config is read by its package bodies wherever the CLI reads a package-owned key:os servemounts the providers a packagerequires(a config boot with no compiled artifact;os devandos startalready did through the artifact);tiers,analyticsCubesand flows reachos serve/os dev/os start;os migrate plan/applyorders a plugin that hard-depends on a package-declared provider;os generate's hint no longer reports a token a package declares;os generate,os doctor,os diffandos migrate metasee every package's objects.os serveconfig boot, and a package'stiersnow apply.requireshalf reaches only a config boot with no compiled artifact. Thetiers,analyticsCubesand flow-count halves reachedos devtoo.tiers/analyticsCubes/flowsas empty; a package body's inline docs may skip the embed lint; the "Not wired" advice names no package). None is measured.fix(cli): os serve and seven sibling readers read a multi-package con…