Repository navigation
fix(cli): read a multi-package artifact's package bodies in the capability preflight and the translation readers - #22285
Conversation
…ility preflight and the translation readers
A `composeStacks(..., { manifest: 'preserve' })` artifact carries its
collections, `translations` and `requires` only inside `packages[]`. Five
readers looked at the top level alone and so judged nothing there:
- the capability preflight (`os validate` / `os build`) now reads each
package body's `requires` when the top level carries none, naming the
package in every finding; a top-level `requires` still wins, unchanged;
- translation coverage (`os lint`, `os i18n check`), `os i18n extract` and
the undeclared-authoring-key walk (`os validate` / `os build`) read the
package-union stack the author-time rules already use.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…ized` call site - `normalized-call-sites.test.ts` names each command call site that hands `normalized` to an analysis and records which stack it reads; a new unclassified site, or a stale row, fails. - `package-union-readers.e2e.test.ts` runs the real commands over a two-package `preserve` app and its one-package control. - `capability-preflight.test.ts` pins the per-package attribution and the unchanged top-level reading. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
A `*.e2e.test.ts` name moves a file to the nightly tier, so the pins would not have gated the merge. Renamed into the per-PR integration tier, and cut to the nine spawns the pins need. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
📓 Docs Drift CheckThis PR changes 1 package(s): 18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e28b7600330dd336df2e2e3dccd5dc48abf40c6b && git checkout e28b7600330dd336df2e2e3dccd5dc48abf40c6b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fbcbcf124f2aa49259b837bc74cdbf4d08b46b3c 6a6e8c78ad82c89a289ce9b19fc83051fa991dec && git checkout -B drift-repro fbcbcf124f2aa49259b837bc74cdbf4d08b46b3c && git merge --no-ff 6a6e8c78ad82c89a289ce9b19fc83051fa991dec
node scripts/docs-audit/affected-docs.mjs --json fbcbcf124f2aa49259b837bc74cdbf4d08b46b3c
|
…narrow The closing sentence claimed no accepted input changes, while the paragraph above it records that `os i18n check` and `os i18n extract` now refuse a non-array `packages` they used to accept. It now names that exception. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
…fig's package-owned keys through its package bodies (objectstack-ai#22321) Fixes objectstack-ai#22288 Clause-②: no ## What this changes A `composeStacks([…], { manifest: 'preserve' })` config carries every package-owned key (`requires`, `tiers`, `analyticsCubes`, `flows`, `objects`, …) once, inside the body of the package that declared it. Its top level carries none of them. `os serve` read the capability providers to mount from the top-level `requires` only (`serve.ts:2847` on `6729e107`), so a capability a package declares was not mounted. The card named two sibling readers. The enumeration this PR adds found the same top-level read in seven more readers in `packages/cli`. All of them are fixed here. Every reader now uses one rule: `resolveStackCollection` (`utils/stack-collections.ts`, the rule objectstack-ai#22285 gave the build preflight), which takes the top-level value when the stack carries one and otherwise every package body's. Or it folds the stack with `authoringRuleUnionStack` where the stack is loaded. A stack with no `packages[]` reads exactly as before. `serve` imports only `stack-collections.ts` (core plus spec), so it stays off `artifact-packages.ts` and `@objectstack/lint`. | reader | file | change | |---|---|---| | `os serve` providers (`requires`) | `commands/serve.ts` | `stackDeclaredCapabilities(config)`, a new one-line export over `resolveStackCollection(…, 'requires')` | | `os serve` `tiers` | `commands/serve.ts` | `resolveStackCollection(config, 'tiers')` | | `os serve` analytics cubes | `commands/serve.ts` | the top level first (its array, then the legacy `cubes` spelling), then the bodies | | `os serve` declared-flow count (automation line) | `commands/serve.ts` | `resolveStackCollection(config, 'flows').length` | | `os migrate plan` / `apply` providers | `utils/schema-migration-plugins.ts` | `stackDeclaredCapabilities(config)` | | `os generate` missing-capability hint | `utils/scaffold-wiring.ts` | `declaredCapabilities` reads by the same rule; JSDoc rewritten (below) | | `os generate types` / `client` / `migration` | `commands/generate.ts` | the four readers fold the loaded config on entry | | `os doctor` | `commands/doctor.ts` | folds once where it normalizes the config | | `os diff` | `commands/diff.ts` | folds each side where it is loaded | | `os migrate meta` data-migration advice | `commands/migrate/meta.ts` | folds at the one call of `pendingDataMigrations` | ## Measured, through the doors Each door was run with bounded boots/runs of two-package fixtures and their one-package controls. BEFORE is `6729e107`, AFTER is this branch. The fixtures are the ones in the pins below. | door | two packages, before | two packages, after | one package (control, before = after) | |---|---|---|---| | `os serve` (config, no artifact), package `requires: ['automation']` | `Info: Optional service not present: automation`; banner 31 plugins, no `AutomationServicePlugin` | `Plugin loaded: com.objectstack.service-automation`; banner 32 plugins incl. `AutomationServicePlugin` | `Plugin loaded: com.objectstack.service-automation` | | `os serve`, package `requires: ['ai']` (no open-edition provider) | `✓ Server is ready` | exit 1 `✗ Capability "ai" resolves to @objectstack/service-ai, which is not available in the open edition …` | the same exit 1 line | | `os dev`, package `analyticsCubes` | `[Analytics] Service started with 0 cubes: (none)` | `… with 1 cubes: prb_note_cube` | `… with 1 cubes: prb_note_cube` | | `os dev`, package screen flow, no automation | no flow line | `⚠ Flows: 1 flow(s) declared but the automation engine is not enabled …` | the same line | | `os serve`, package `tiers` without `auth` | ignored: `com.objectstack.auth` mounted, ready | exit 1 `✗ This stack mounts no auth, …` | the same exit 1 | | `os migrate plan`, host plugin hard-depending on automation, package `requires: ['automation']` | exit 1 `✗ [Kernel] Dependency 'com.objectstack.service-automation' not found for plugin 'com.probe.connector'` | exit 0, `Composed AutomationServicePlugin for requires: ['automation'] …` | exit 0, same note | | `os generate flow done --object prb_ticket`, app package declares `['automation','triggers']` | `⚠ It will not run yet: objectstack.config.ts does not require 'automation', 'triggers'` and a `requires:` line to add | `✓ Reaches the stack`, no warning | no warning | | `os generate types --dry-run` | 0 record interfaces | 2 | 2 | | `os generate migration --format sql --dry-run` | 0 `CREATE TABLE` | 2 | 2 | | `os doctor` | no metadata check ran; `✅ Environment is healthy` | circular-dependency and unused-object checks ran (2 unused) | the same 2 | | `os diff --json`, one object added to the service package | `total: 0` | `total: 1`, `prb_extra` added | `total: 1` | | `os migrate meta --from 16 --json`, a `file` field in the service package | `dataMigrations: []` | `adr-0104-file-references` | `adr-0104-file-references` | ## The PM readings (H1–H5) - **H1, reproduced, and narrower than the card says.** Bare `os serve` on a two-package config reads `[]` (table, row 1). **`os dev` and `os start` reach the same line of code, but they do NOT reproduce the defect.** Both boot a compiled artifact: `os dev` always compiles, and `os start` auto-compiles when no artifact exists. `createStandaloneStack` resolves the artifact's `packages[]` (`resolveArtifactCollections`), and `mergeBootConfig` lays the resolved `requires` over the top level. Measured on `6729e107` with the same two-package fixture: `os dev` and `os start` each log `Plugin loaded: com.objectstack.service-automation` once and `Optional service not present` zero times. So does `os serve` with a built `dist/objectstack.json` beside the config. The `requires` defect is therefore confined to a config boot with no compiled artifact. Triage graded p1 on `os dev` / `os start` reach. On this measurement, the `requires` half does not reach them. The `tiers`, `analyticsCubes` and flow-count halves do reach `os dev` (table, rows 3–5), because the artifact path does not carry those keys. - **H2.** Serve calls the existing rule (`resolveStackCollection`) and does not copy it. The import stays off `artifact-packages.ts`. - **H3, measured, with two narrowings rather than one.** (1) A capability a package declares with no installed provider now stops an `os serve` config boot. This is table row 2, and the same exit 1 the one-package app always gave. `os dev` / `os start` already stopped there, since the artifact path made those tokens "declared". (2) A package's own `tiers` now apply on `os serve`, `os dev` and `os start` (row 5). The claim named one narrowing. Both are named in the changeset. Nothing in this repo boots a multi-package config whose packages declare `requires` or `tiers`. `examples/app-multi-package` declares neither, and no test boots a `preserve` config with either. - **H4.** Both readers read `[]` on a multi-package config, and both are fixed (rows 6 and 7). The `os generate` hint now reads the list a server mounts. Its JSDoc keeps the `defineStack` half true. On a one-package stack the list is its top-level `requires`, which is also what `defineStack`'s trigger rule reads. On a multi-package stack, `defineStack` judged each package against its own `requires` when the config loaded. A refusal there is the `load-failed` branch, reached before the hint reads anything, so the union is what decides whether the item runs. - **H5.** `test/normalized-call-sites.test.ts` gains a second table. It enumerates every member read of a package-owned key off a stack-named receiver in `packages/cli/src`. The key set comes from `packageOwnedCollectionKeys()` (now exported from `stack-collections.ts`, derived from the two schemas, 37 keys, envelope keys excluded). Every read is classified. A `resolved` row carries `evidence`: code that must be present in the file (comments and strings blanked), which proves the fold. A `top-level` row carries the reason. 53 reads in 15 files after the fix: 6 `top-level` rows (the build preflight's `config.requires` ×2, serve's `config.analyticsCubes` first leg and `config.docs`, `collect-docs`' artifact `stack.docs`, `scaffold-wiring`'s presence probe) and 12 `resolved` receivers. The grammar's bounds are written in the file: a stack held under another name, a computed key, a member chain, and a cast whose type has parentheses. None of those spellings reads a package-owned key in `src` today. ## Pins, and the tier each runs in - `test/serve-package-declared-capabilities.test.ts`: **integration tier (per-PR)**, `runServe(` boots. Covers `automation` mounted on two packages, the one-package control, the `ai` narrowing, cubes plus the flow line, and the `tiers` narrowing. 5 boots, about 60s under a shared box. Not named `*.e2e.test.ts`, so it gates PRs. - `test/package-owned-command-readers.test.ts`: **integration tier (per-PR)**, spawns `os doctor`, `os diff --json` and `os migrate meta --json`, two packages vs one. - `src/utils/schema-migrate.requires-providers.integration.test.ts`: **integration tier**, a second case where a package declares `requires`, on the real kernel boot. - `test/package-owned-readers.test.ts`: **unit tier**. `stackDeclaredCapabilities`, the `os generate` hint and the types/migration generators against real `composeStacks` output, each paired with one `defineStack`. - `test/normalized-call-sites.test.ts`: **unit tier**, the enumeration, with a scanner self-test (read forms, writes skipped, envelope keys and prose ignored) and a positive control on the real tree. ## Ablation (one-time; nothing permanent left behind) Run with `scripts/ablation-replace.mjs` (WRAP mode, the anchor must hit, restore on EXIT/INT/TERM), at `6a30bcff0`. No build leg: the scanner reads `src` and the spawned CLI runs `src` through tsx (`bin/run-dev.js`). - **serve.ts back to `Array.isArray((config as any).requires) ? … : []`**: anchor 1→0, blob `7a588141d660`→`4bf8b068950a`. Three tests red: `no read is unclassified`, naming `commands/serve.ts :: config.requires`; `two packages: the service package's automation is mounted`; and the `ai` narrowing. The one-package control and the cubes/tiers cases stayed green. Restored: blob `7a588141d660` == HEAD, `git diff HEAD` empty. - **scaffold-wiring.ts back to a top-level-only read**: two hint tests red in `package-owned-readers.test.ts`. Restored: blob `3ec828a90838` == HEAD, `git diff HEAD` empty. Direction observed: red, as expected. ## Verification Everything below ran at the merged head `a58d828b0`. That is this branch merged with `origin/main` at `dc4a5c630`, which brought objectstack-ai#22304's `serve.ts` change. The merge was clean, and all four edits to `serve.ts` were checked present after it. The dists were rebuilt after the merge. - `pnpm --filter @objectstack/cli typecheck`: exit 0. That is `tsc --noEmit`, then `check:test-typecheck: OK … 3 file(s) / 28 error(s) / 6 pinned signature(s) held`, so no new signature. - `pnpm --filter @objectstack/cli exec vitest run --project unit`: 267 files, 3943 tests passed. - `pnpm --filter @objectstack/cli exec vitest run --project integration`: 97 files, 922 passed, 2 skipped. This tier is owed locally because the diff adds integration-tier files and touches the serve boot path. - Gate union: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derives the same 67 commands as the dispatch order lists. All 67 ran and exited 0. The `--ran` reconciliation reads `67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN`. On the pre-merge tree, `check:dual-build-cjs-loads` and `check:i18n-coverage` first refused with `PREREQUISITE NOT MET` because some packages had no dist. Those dists were built and both gates re-ran green. - `pnpm lint` (full, `eslint . --no-inline-config`): exit 0. - Before the merge, at `d12d93891`: the unit tier (267/3943), the integration tier (96 files, 919 passed, 2 skipped), full lint and the gate union were all green as well. ## Acceptance notes (observations, not filed) - **Artifact-only boots.** `createStandaloneStack`'s result carries `requires`, `objects`, `manifest`, `permissions`, `positions` and `i18n`, but not `tiers`, `analyticsCubes` or `flows`. An `os start` with an artifact and no config therefore reads those three as empty even for a one-package artifact. Inferred from the code, not measured. That is a different shape from this card (not multi-package specific). Carrier: none. - **Doc embed lint.** `collectAndLintDocs`' comment says a doc already on a package body "is also in `docs` above, where `lintMetadataEmbeds` has judged it". That was true of the additive shape, but the 2026-09-22 addendum emits a body's docs only in the body, so a package body's inline docs may not reach the embed lint. Inferred, not measured. Carrier: none. - **Legacy spellings kept as they were.** Serve's `cubes` fallback, and `os generate`'s `data?.objects` fallback, are consumer-side spellings that predate this card. They are kept byte-for-byte in precedence, not endorsed. - **Wiring lines.** On a multi-package config, the `os generate` "Not wired" advice still says `inside defineStack({ … })` without naming which package. The `requires:` line it prints is the union of what the packages declare plus what is missing, so it never drops a declared token. - **objectstack-ai#22190**, the producer-side member of this family, is not addressed here and remains open. --- _Generated by [Claude Code](https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22238
Clause-②: no
Fixes #22189
Folded into one PR by the seat's claim (comment 6058224414, posted on both cards). #22190, the producer-side member of the same family, remains open and is not touched here.
What was wrong
composeStacks([a, b], { manifest: 'preserve' })emits each definition once, inside the body of the package that owns it (ADR-0130 D4, 2026-09-22 addendum). The artifact's top level keepsmanifest,packagesandi18n, and nothing a package owns. Five@objectstack/clireaders looked at that top level alone, so on a two-package app they judged nothing and answered clean.What changed
os validate,os build; 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). NewpreflightDeclaredCapabilitiesinutils/capability-preflight.ts. A top-levelrequireswins whenever present, which is theresolveStackCollectionrule, so every single-package stack and the additive shape are read exactly as before. When the top level declares none, eachpackages[]body'srequiresis classified and each finding is attributed to its package.renderCapabilityMessageprefixes an attributed finding withpackage 'ID' —, the spelling the per-package author-time pass uses. Both doors hand it theartifactPackagesentries they already compute; the module does not importartifact-packages.ts, which would load@objectstack/lintintoos serve.os lint,os i18n check; 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):computeI18nCoverage(authoringRuleUnionStack(normalized), …).os i18n extract:extractTranslations(authoringRuleUnionStack(normalized), …).os validate,os build):lintUnknownAuthoringKeys(authoringRuleUnionStack(normalized), …). The stack-key lint beside it stays on the envelope (table below).test/normalized-call-sites.test.ts(unit, the enumeration),test/package-union-readers.test.ts(integration, the real commands),test/capability-preflight.test.ts(unit, attribution), and the gate-parity roster row renamed topreflightDeclaredCapabilities(test/validate-build-gate-parity.test.ts).@objectstack/clipatch. It says plainly that a multi-package app that passed with an unprovidable capability or a missing translation now fails, as the same app shipped as one package always did.PM readings, measured
All readings use real fixtures: two
defineStackpackages undercomposeStacks(…, { manifest: 'preserve' }), a service module plus an app, and the same content in onedefineStackas the control. Readings were taken through the builtbin/run.js, before on8cbe255eand after on this branch.H1, reproduced before the fix.
requires: ['ai'],os validatepackage 'com.probe.svc' — Capability "ai" …os buildpluralLabel,os lint --stricti18n/missing-objectos i18n checkos i18n check --strictos i18n extract --jsonstrict: falsepackage),os validate --json/os build --jsonwarningsThe emitted two-package artifact's top level was
manifest,packages, withrequires: ["ai"]only inpackages[0](com.probe.svc).H2.
authoringRuleUnionStackmeasured on the fixtures. It keepsi18n, an envelope key it never touches. It foldstranslationsand every collection back in, so coverage, extraction and the key walk take it as is. It also foldsrequires, but as one concatenated list with the package lost, which cannot name a package. The preflight therefore reads the per-package bodies instead. A single-package stack comes back by identity (identity: trueon both controls).H3. A refusal on a two-package artifact names its package:
package 'com.probe.svc' — Capability "ai" resolves to @objectstack/service-ai, …, in the text face and in--jsonerrors[].message. The{ token, message }record shape is unchanged. The single-package text is byte-identical (H6).H4. The hierarchy-security advisory is the same reader: it is the preflight's
installablewarning for@objectstack/security-enterprise. Before the fix it was printed for one package and absent (count 0) for two. After, it appears on both doors aspackage 'com.probe.app' — Capability "hierarchy-security" is provided by …. It is fixed by the same change.H6, single-package parity. 17 outputs (
os validate/os build, text and--json, over the capability, advisory and i18n controls;os lint --stricttext and--json;os i18n checkplain,--jsonand--strict). Each output was compared before and after with only the timing tokens normalized: 17 of 17 identical, same exit codes. The only bytes that moved were"duration": N.Malformed
packages(found while measuring). A non-arraypackages(strict: false) onos validatewas refused before by the schema parse (invalid_type); it is now refused one step earlier by the resolver'sINVALID_ARTIFACT_PACKAGES, still exit 1.os i18n checkandos i18n extractused to accept it with exit 0 and zero keys; they now refuse it with exit 1 andINVALID_ARTIFACT_PACKAGES, the ruling-A direction the other CLI readers already follow.os buildis unchanged: its parse runs first.H5: every
(normalizedcall site inpackages/cli/src/commandsThere were 19 hits on
8cbe255e(git grep -n "(normalized"). Two are in test files:dev-default-db.test.ts:43is a string in a test title, andmigrate/meta.report-order.test.ts:138is a test callingapplyMetaMigrationson its own fixture. The 17 non-test sites, at this branch's head:compile.ts:330lowerCallables(normalizedpackages[i].manifestbody itself (lower-callables.ts)compile.ts:541authoringRuleUnionStack(normalizednormalizedtier: it is the foldcompile.ts:828lintUnknownStackKeys(normalizedcompile.ts:829lintUnknownAuthoringKeys(…validate.ts:289lintUnknownStackKeys(normalizedvalidate.ts:290lintUnknownAuthoringKeys(…validate.ts:346lowerCallables(normalizedvalidate.ts:529authoringRuleUnionStack(normalizedlint.ts:1005resolveJsxGateManifest(normalizedsdui-manifest.ts)lint.ts:1006lintConfig(normalizedauthoringRuleUnionStackon entrylint.ts:1031computeI18nCoverage(…lint.ts:1044scoreMetadata(normalizedlintConfig, which foldsi18n/check.ts:161computeI18nCoverage(…i18n/extract.ts:259(normalized …).i18ni18nis an envelope key (compose dispositionsingle): kept at the top level, carried by no bodyi18n/extract.ts:294extractTranslations(…migrate/meta.ts:1062applyMetaMigrations(normalized--writeedits source at the paths it reports, which a folded union would not hold. Which bodies a conversion reaches is the chain's own walk (see Acceptance notes)migrate/meta.ts:1072planProtocolRange(normalizedmanifest, where the load seam reads it (AppPlugin:bundle.manifest || bundle); apreserveartifact keepsmanifestat the top level(a) already read the union or the bodies; (b) moved onto the union here; (c) top level on purpose. The capability preflight reads
config.requires, notnormalized, so it is not a row; its per-package reading is pinned separately.test/normalized-call-sites.test.tsholds the table. It names each site by file and by the callee chain wrappingnormalized(for examplecomputeI18nCoverage(authoringRuleUnionStack(normalized), never by line or count. It fails on an unclassified site, on a stale row, and on a row whose verdict disagrees with its spelling. Comments and string bodies are masked; the scanner's own cases cover a spread call, a grouping paren, a method call and prose.Ablation (run from the committed fix, restored to the HEAD blob)
Each leg used
scripts/ablation-replace.mjsin wrap mode: the anchor had to hit, the on-disk counts went 1 → 0 and 0 → 1, the blob changed, then the restore was proven by blob equality and an emptygit diff HEAD. Nodist/leg applies: the integration pins spawn the CLI fromsrc/throughbin/run-dev.js, and the enumeration pin reads source text.lint.tscoverage routed back tocomputeI18nCoverage(normalized, {, blobdad7d827→2acc4f7d. Result: 4 red, 8 green. Red were theos lint --strictpin (lintTwoback to"passed": true, "issues": []) and three enumeration assertions (lint.ts :: computeI18nCoverage(normalizedunclassified, its row stale, the positive control). Restored: blobdad7d827== HEAD.validate.tspreflight handedpackages: [], blob3f2d706b→7818f8b0. Result: 1 red, 6 green. Red was theos validate --jsonpin (exit 0 where 1 is pinned). Theos buildpin stayed green because its door was not ablated, so the pins are door-specific. Restored: blob3f2d706b== HEAD.Verification (head
7bba74ec)pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 265 files, 3925 passed.pnpm --filter @objectstack/cli typecheck: exit 0.check:test-typecheckholds 3 files / 28 errors / 6 pinned signatures, unchanged.vitest run --project integration test/package-union-readers.test.ts: 7 passed (46s). This is the integration file this PR adds. The rest of the integration tier is CI's.node scripts/pm/dispatch-gates.mjs --commandsderives (identical to the dispatch's list): 65 of 65 exit 0.check:i18n-coverage(13 configs, 621 baselined, none new) andcheck:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET, unbuilt packages); after the prerequisite builds, both reran and exited 0.dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lint: exit 0.origin/mainmoved 3 commits during the run (spec flow value slots, docs, one unrelated CLI integration test). None touches a file here.git merge-treeis clean, and CI's merge ref covers the rest.Acceptance notes
os serve/os devreadrequiresat the top level only (serve.ts:2847), the same family outside this card's surface. Measured with a bounded boot: one package withrequires: ['automation']logsPlugin loaded: com.objectstack.service-automation, while the two-package app whose service package declares it logsOptional service not present: automation. The same top-level read sits inutils/schema-migration-plugins.ts:1453(os migratehost-config capability providers) andutils/scaffold-wiring.ts:118(theos generatemissing-capability hint); those two were not measured. Reported to the seat for filing, not fixed here.os migrate metaon a composed project whose package carries a retired spelling exits 1 withSTACK_PROVENANCE_MISSING, naming a stack that is wrapped indefineStack. The authored-source load handscomposeStacksan unbuilt stack. The one-package control exits 0 and applies the conversion. SoapplyMetaMigrationsis never reached there, and whether the chain's conversions reach package bodies (the shared walker in spec'sconversions/walk.tsdoes not descend intopackages[]; the manifest-level entries do) could not be measured through the door. Reported to the seat.package 'ID' —prefix is now spelled in two places:artifact-packages.ts's prefixer for author-time findings, andcapability-preflight.tsfor capability messages. They are separate surfaces, so the module stays free of@objectstack/lint.os i18n extractwrites one bundle for all packages: the union, as the pre-addendum flattened artifact gave. Per-package bundle emission is not attempted.Generated by Claude Code