Repository navigation
fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies - #22321
Conversation
…ti-package config
A multi-package composeStacks(..., { manifest: 'preserve' }) config carries
every package-owned collection inside the body of the package that owns it and
none at its top level. These readers looked at the top level only:
- os serve: requires (providers mounted), tiers, analyticsCubes (the always-on
analytics provider's cubes) and the declared-flow count of the automation line;
- os migrate plan/apply: the requires tokens that order a host plugin's hard
dependency on its provider;
- os generate: the missing-capability hint, and the objects that types, client
and migration generation emit;
- os doctor, os diff and os migrate meta's data-migration advice.
Each now reads by resolveStackCollection's rule (the top level when present,
otherwise every package body) or folds the stack with authoringRuleUnionStack.
A stack with no packages[] reads exactly as before.
Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-Authored-By: Claude <noreply@anthropic.com>
… enumerate every top-level read - normalized-call-sites.test.ts gains a second table: every read of a package-owned key (the key set derived in stack-collections.ts) off a stack-named receiver in src, classified resolved (with evidence present in the code) or top-level on purpose (with the reason). - serve-package-declared-capabilities.test.ts boots os serve on two-package configs: requires, the requires narrowing, analyticsCubes, the flow count and tiers, with the one-package control. - package-owned-readers.test.ts and package-owned-command-readers.test.ts pin os generate, os doctor, os diff and os migrate meta against their one-package controls; the migrate plan provider case joins its existing file. Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU Co-Authored-By: Claude <noreply@anthropic.com>
…lugin list Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU Co-Authored-By: Claude <noreply@anthropic.com>
…dies Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU Co-Authored-By: Claude <noreply@anthropic.com>
…e its rootDir Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU Co-Authored-By: Claude <noreply@anthropic.com>
…rve-package-requires
📓 Docs Drift CheckThis PR changes 1 package(s): 29 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 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 774e754eeb1e705cbcaef4d23c94dc2795b66c66 && git checkout 774e754eeb1e705cbcaef4d23c94dc2795b66c66
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4e4111ca054fe995b5a08a07d273cad18a749ef7 a58d828b0d02a9f7df5ae85aa1387e4531fba0d9 && git checkout -B drift-repro 4e4111ca054fe995b5a08a07d273cad18a749ef7 && git merge --no-ff a58d828b0d02a9f7df5ae85aa1387e4531fba0d9
node scripts/docs-audit/affected-docs.mjs --json 4e4111ca054fe995b5a08a07d273cad18a749ef7
|
…rates its package bodies (objectstack-ai#22326) Fixes objectstack-ai#22289 Clause-②: no `os migrate meta` now runs on a project whose config exports `composeStacks([defineStack({ … }), …], { manifest: 'preserve' })`, and it converts the retired spellings inside the package bodies. The one-package path is unchanged. Every change is in `packages/cli`. There is no `packages/spec` change, and nothing in `packages/cli` writes the provenance mark: the producer marks its own output. ## What changed 1. **A refused composed input is produced again by the producer** (`src/utils/config.ts`, the authored-source shim). The shim records each stack it hands through for a refused `defineStack` call, with the options it was called with. A generated `composeStacks` wrap maps only those recorded inputs through the real `defineStack(input, { ...options, strict: false })`, then calls the real `composeStacks`. Every other input reaches the real `composeStacks` untouched, including a plain object the author never wrapped, so that one is still refused. The one-package path keeps handing the authored argument through raw (H3 below says why). The record is one bundled module that every shim module imports, so a stack handed through by one `@objectstack/spec` entrypoint is recognised by another, and a record lasts for one load only. 2. **The chain runs over each package body** (`src/commands/migrate/meta.ts`, `applyMetaMigrationsToPackages`, used at all three chain call sites: the main run, the `--write` re-run and the empty-range probe). An option-B artifact keeps each definition only under `packages[i].manifest`, and the conversions walk a stack's own collections. So the chain now also runs over each body as a stack, and lists each body edit under the body's own path (`packages[0].manifest.objects[0].fields.starts_at.defaultValue`). `applied` keeps hop order, `todos` is the stack's own list, and `absentTodos` keeps an entry only when every run names it. A stack with no `packages` list gets the plain chain result back. 3. **`--write` traces a body edit to the input that authored it** (`src/utils/authored-source-codemod.ts`). The walk now follows `composeStacks([…])`, but only into `packages[i].manifest.KEY`, and only when every input up to i is a stack literal with a `manifest` and no `packages` of its own. Then entry i is input i's body. `KEY` is read the way composition assembles a body: from the input when it is a package-owned collection the input writes, otherwise from the input's `manifest`. Any other composed key is refused as `helper` and listed. 4. `src/utils/stack-collections.ts`: `packageOwnedCollectionKeys()` is exported, unchanged, so the codemod reads the CLI's one derivation of that key set instead of writing a second one. `main` exported it too, for the enumeration pin (PR objectstack-ai#22321). After the merge it is one export, and one doc paragraph names both readers. 5. `test/normalized-call-sites.test.ts`: the ledger row for the renamed call site moves from `top-level` to `packages`, with the measurement named. 6. **The `--write` condition, stated where authors read it.** The changeset and the `--write` paragraph of `content/docs/upgrading.mdx` both say exactly when a package-body change is written: input i and every input before it must be a stack literal that writes its own `manifest` and no `packages`. Otherwise package i is not one input's body, and the change is listed. The docs paragraph keeps its list true: the clause that a value built by a function call is listed now reads "any other function call". The docs edit is prose only, and the seat declared it to `domain:devx`. ## Round 2: the seat's REWORK `6065347099` 1. **Merged `main`** with a merge commit, `82f398833` (parents `122ae3bd9` and `28bff18d0c`). There was no rebase and no force-push. `src/utils/stack-collections.ts` conflicted, because both sides exported `packageOwnedCollectionKeys` and each added a paragraph naming its own reader. The resolution keeps the export and writes one paragraph naming both. The `meta.ts` auto-merge (main folds `pendingDataMigrations` onto the union stack) is clean. The second enumeration `main` added to `test/normalized-call-sites.test.ts` (every top-level read of a package-owned key in `packages/cli/src`) is green over this PR's code. Its "no read is unclassified" test names nothing new, so there was nothing to classify. 2. **The changeset** states the condition above, and the code did not change. 3. **`content/docs/upgrading.mdx`**: the `--write` paragraph is edited as described in item 6. ## PM readings, measured Measured with the built CLI against temp projects that link this worktree's built `@objectstack/spec`. "Before" is `origin/main` `4e4111ca0`. - **H1, reproduced.** The composed project (every input wrapped in `defineStack`, a `time` default `'10:00Z'` in the service package) gave `--from 17 --json` exit 1 with `"code":"STACK_PROVENANCE_MISSING"` and the message `composeStacks provenance check failed (1 input): 'com.probe.svc' (stack #0) was not built by \`defineStack\`. … Wrap each input: …`. The one-package control gave exit 0 with `applied` = `[time-default-utc-suffix-dropped @ objects[0].fields.starts_at.defaultValue]`. - **H2, confirmed.** The shim hands back `authored[0]` unmarked, `composeStacks` is not wrapped, and its step 0 refuses the unmarked input. The composed project is refused **only when a body's `defineStack` refused**: the same project with `'10:00'` gave exit 0, `applied: []`, `schemaValid: true`. - **H3, measured. The plain route changes the control, so it was not taken.** Variant A, a `strict: false` fallback for every refused `defineStack` (applied to the built dist for the measurement only, then restored by md5), was run on two one-package controls at `--from 16` and `--from 17`: - c1, the refused stack: unchanged. - c2, the refused stack plus `datasources[0].driver: 'mongo'`, at `--from 16`: before, `applied` = `[datasource-driver-mongo-to-mongodb @ datasources[0].driver, time-default-utc-suffix-dropped @ objects[0]…]` and `write.files` = `[{ objectstack.config.ts, sites: 2 }]`, with the source written to `'mongodb'`. Under variant A, `applied` = `[time-default-utc-suffix-dropped]` only, `sites: 1`, and the source keeps `'mongo'`. So the one-package path keeps the raw hand-back, and the route applies only where `composeStacks` needs a marked input (change 1). Measured after the change: c1 and c2 at both majors give a `--json` summary (`applied`, `schemaValid`, the whole `write` object) and written source bytes **identical by md5** to the baseline. - **H4, confirmed, and fixed in `packages/cli`.** After change 1 alone, the composed project gave exit 0, but `applied: []` and `schemaValid: false`, with the schema refusing `packages.0.manifest.objects.0.fields.starts_at.defaultValue`. The artifact's top level held only `manifest` and `packages`. After change 2: `applied` = `[time-default-utc-suffix-dropped @ packages[0].manifest.objects[0].fields.starts_at.defaultValue]` and `schemaValid: true`. With `--write`, the authored literal is rewritten, `verification.ok` is true, and a second run applies nothing. The walker is unchanged. - **H5, holds.** A truly unwrapped input (a plain object beside a wrapped one) is still refused with `STACK_PROVENANCE_MISSING`, and the refusal names only `'com.probe.app' (stack objectstack-ai#1)`. The refusal text is unchanged. - **Real project.** `examples/app-multi-package` (composed, preserve, valid) under `--from 17 --json` gives exit 0, `applied: []`, `schemaValid: true`. ## Pins — `test/migrate-meta-composed.test.ts`, unit tier The test runs in-process over `MigrateMeta.run` against temp projects that link the real spec. It spawns no process and boots no kernel, so it runs in `Test Core` with the rest of the CLI unit tier. 8 tests: - **The fix.** A two-file composed project (the service stack in `src/service.stack.ts`) loads, and `applied` is exactly the body site above, with `schemaValid: true`. - **`--write`.** `write.files` = `[{ src/service.stack.ts, sites: 1 }]`. The tree equals its old bytes with only that literal edited. The re-run gives `applied: []`, and a strict `loadConfig` (no shim) accepts the sources and reports `stackProvenance: true`. - **`--write` on a body it cannot trace.** For an input built by a local function, the change is listed as `helper` and every byte is left alone. - **Control.** For both one-package stacks above at `--from 16`, `applied` (conversion, path, from, to) and `write.files` equal the values measured on `4e4111ca0`, and so do the written bytes. - **Boundary.** The unwrapped input is refused with `STACK_PROVENANCE_MISSING`, naming `'com.probe.app'` and not `'com.probe.svc'`. - **Merge contracts.** With no `packages`, the result equals `applyMetaMigrations`. With bodies: copy-on-write (the input is unmutated, and an untouched body keeps its identity), `applied` equals the hops' concatenation, and each `absentTodos` is a subset of its `todos` by identity. ## Ablations Each ablation used `scripts/ablation-replace.mjs`: the anchor hit once, the mutation was proven on disk, and the restore was proven (blob equals HEAD, `git diff HEAD` empty). The suite imports `src` directly, so no dist is involved. The runs were at `fb129c3e1`, when the file had 7 tests. 1. **The raw hand-back restored on the composed path** (`__composable` returns its input). 3 red, 4 green. The two composed pins went red with the original false `STACK_PROVENANCE_MISSING`. The boundary pin also went red, because the refusal then names `'com.probe.svc'` too (2 inputs). The controls and the merge pins stayed green. Restored: blob `982786a6180d`. 2. **The body pass removed** (`applyMetaMigrationsToPackages` returns the plain result). 3 red (both composed pins and the merge pin, `applied` `[]`), 4 green. Restored: blob `e1aa2f776836`. 3. **The codemod's `composeStacks` walk disabled.** 1 red (the `--write` pin: `write.files` `[]`), 6 green. Restored: blob `359b722e7b4c`. ## Local verification at `122ae3bd9` (round 1) - **CLI unit tier** (`pnpm --filter @objectstack/cli exec vitest run --project unit`): 267 files and 3936 tests pass. `vitest list --project unit` lists `test/migrate-meta-composed.test.ts`, and `--project integration` does not. The integration tier is CI's: this diff touches no integration file and no spawn entry. - **Typecheck** (`pnpm --filter @objectstack/cli typecheck`, which is `tsc --noEmit` plus `check:test-typecheck`): exit 0. - **Gates.** `dispatch-gates.mjs --commands`, re-derived over this diff, gives the same 65 commands the order listed. All 65 exit 0, and `dispatch-gates --ran` reports 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. On an earlier pass with only the CLI closure built, `check:dual-build-cjs-loads` and `check:i18n-coverage` exited 3 (prerequisite not met). After a full `turbo run build` both pass: `106 published require entry point(s) across 66 package(s) load` and `13 config(s) … none new`. - **Lint** (`pnpm lint`, which is `eslint . --no-inline-config`): exit 0, no findings. ## Local verification at `db3e20d1c` (round 2, merged head) - **CLI unit tier**: 268 files and 3951 tests pass. - **CLI integration tier** (`--project integration`): 97 files pass, with 922 tests passed and 2 skipped. - **Typecheck** (`pnpm --filter @objectstack/cli typecheck`): exit 0. - **Gates.** `dispatch-gates.mjs --commands`, re-derived over the final diff, gives 95 commands: the 65 from round 1 plus 30 docs-family gates that `content/docs` adds. All 95 exit 0, after a full `turbo run build`. `dispatch-gates --ran` reports 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN. Among them: `check:doc-authoring` ✓, `check:doc-anchors` ("466 internal #fragment link(s) … all resolve"), `check:docs-single-h1` ✓, spec `check:docs` ("225 generated files in sync") and `check:nul-bytes` OK. - **Lint** (`pnpm lint`): exit 0, no findings. ## Acceptance notes - **The route's one cost is objectstack-ai#22256's class, now also reached on a composed project.** For a refused composed input, `strict: false` still runs the load-time D2 pass. Measured: a composed body carrying both `'10:00Z'` and `driver: 'mongo'` lists and writes the time default, while `'mongo'` is applied at load, is not listed, and is not written. That is the same as for any input the current schema accepts. objectstack-ai#22256 remains open, and its fix will need to cover this path as well as the one-package one. The changeset states the limit. - **A body authored in map form** (`objects: { probe_slot: { … } }`) is converted and listed. `--write` lists it as `mismatch` ("an object where the loaded value is an array") and writes nothing, because `strict: false` has already normalized the body to an array. Measured. The one composed example in this repo uses array form. Carrier: none. - **`absentTodos`.** On today's registry the intersection agrees with the top-level verdict: every relevance question is `stack-declares`, which already reads `packages[]`, and the two entries that judge a conversion both ask about `agents`. No fixture separates the two, so no pin claims to. The intersection is kept because a body's own applications are visible only to that body's run. - **Manifest-level conversions.** One exists (`manifest-permissions-string-list-removed`), and it already walks `packages[].manifest`. A body run finds no `manifest` key, so nothing is applied twice. - **Not measured.** `planProtocolRange` still reads only the top-level `manifest`'s declared range. Whether a package body's own declared range needs the same rewrite was not measured here. - **File surface.** `src/utils/stack-collections.ts`, `test/normalized-call-sites.test.ts` and the one `content/docs/upgrading.mdx` paragraph were added to the claim's file surface at review. Nothing else moves. - **Docs.** The `--write` paragraph of `content/docs/upgrading.mdx` would have gone false with this change, so it is edited here (round 2, prose only). The seat declared the edit to `domain:devx`. - **The docs-drift advisory** (comment `6065243708`) lists 18 hand-written pages; its list is omitted above 15 rows. I re-derived it on this tree with `node scripts/docs-audit/affected-docs.mjs --json 28bff18`, which gave the same 18 pages plus 9 release-owned ones. I read each page for a statement about `--write` tracing, `STACK_PROVENANCE_MISSING` or the authored-source load. Only the `upgrading.mdx` paragraph above goes false, and it is edited here. `deployment/cli.mdx` line 1433 ("writes the ones it can trace to one literal") and line 1896 (`os validate`'s provenance refusal) stay true. Written by session `session_01RWZbGvPFcRKvUqASZtunCU`, rounds 1 and 2; body refreshed 2026-10-08T18:14Z. --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #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 serveread the capability providers to mount from the top-levelrequiresonly (serve.ts:2847on6729e107), 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 inpackages/cli. All of them are fixed here.Every reader now uses one rule:
resolveStackCollection(utils/stack-collections.ts, the rule #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 withauthoringRuleUnionStackwhere the stack is loaded. A stack with nopackages[]reads exactly as before.serveimports onlystack-collections.ts(core plus spec), so it stays offartifact-packages.tsand@objectstack/lint.os serveproviders (requires)commands/serve.tsstackDeclaredCapabilities(config), a new one-line export overresolveStackCollection(…, 'requires')os servetierscommands/serve.tsresolveStackCollection(config, 'tiers')os serveanalytics cubescommands/serve.tscubesspelling), then the bodiesos servedeclared-flow count (automation line)commands/serve.tsresolveStackCollection(config, 'flows').lengthos migrate plan/applyprovidersutils/schema-migration-plugins.tsstackDeclaredCapabilities(config)os generatemissing-capability hintutils/scaffold-wiring.tsdeclaredCapabilitiesreads by the same rule; JSDoc rewritten (below)os generate types/client/migrationcommands/generate.tsos doctorcommands/doctor.tsos diffcommands/diff.tsos migrate metadata-migration advicecommands/migrate/meta.tspendingDataMigrationsMeasured, 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.os serve(config, no artifact), packagerequires: ['automation']Info: Optional service not present: automation; banner 31 plugins, noAutomationServicePluginPlugin loaded: com.objectstack.service-automation; banner 32 plugins incl.AutomationServicePluginPlugin loaded: com.objectstack.service-automationos serve, packagerequires: ['ai'](no open-edition provider)✓ Server is ready✗ Capability "ai" resolves to @objectstack/service-ai, which is not available in the open edition …os dev, packageanalyticsCubes[Analytics] Service started with 0 cubes: (none)… with 1 cubes: prb_note_cube… with 1 cubes: prb_note_cubeos dev, package screen flow, no automation⚠ Flows: 1 flow(s) declared but the automation engine is not enabled …os serve, packagetierswithoutauthcom.objectstack.authmounted, ready✗ This stack mounts no auth, …os migrate plan, host plugin hard-depending on automation, packagerequires: ['automation']✗ [Kernel] Dependency 'com.objectstack.service-automation' not found for plugin 'com.probe.connector'Composed AutomationServicePlugin for requires: ['automation'] …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 arequires:line to add✓ Reaches the stack, no warningos generate types --dry-runos generate migration --format sql --dry-runCREATE TABLEos doctor✅ Environment is healthyos diff --json, one object added to the service packagetotal: 0total: 1,prb_extraaddedtotal: 1os migrate meta --from 16 --json, afilefield in the service packagedataMigrations: []adr-0104-file-referencesadr-0104-file-referencesThe PM readings (H1–H5)
os serveon a two-package config reads[](table, row 1).os devandos startreach the same line of code, but they do NOT reproduce the defect. Both boot a compiled artifact:os devalways compiles, andos startauto-compiles when no artifact exists.createStandaloneStackresolves the artifact'spackages[](resolveArtifactCollections), andmergeBootConfiglays the resolvedrequiresover the top level. Measured on6729e107with the same two-package fixture:os devandos starteach logPlugin loaded: com.objectstack.service-automationonce andOptional service not presentzero times. So doesos servewith a builtdist/objectstack.jsonbeside the config. Therequiresdefect is therefore confined to a config boot with no compiled artifact. Triage graded p1 onos dev/os startreach. On this measurement, therequireshalf does not reach them. Thetiers,analyticsCubesand flow-count halves do reachos dev(table, rows 3–5), because the artifact path does not carry those keys.resolveStackCollection) and does not copy it. The import stays offartifact-packages.ts.os serveconfig boot. This is table row 2, and the same exit 1 the one-package app always gave.os dev/os startalready stopped there, since the artifact path made those tokens "declared". (2) A package's owntiersnow apply onos serve,os devandos 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 declarerequiresortiers.examples/app-multi-packagedeclares neither, and no test boots apreserveconfig with either.[]on a multi-package config, and both are fixed (rows 6 and 7). Theos generatehint now reads the list a server mounts. Its JSDoc keeps thedefineStackhalf true. On a one-package stack the list is its top-levelrequires, which is also whatdefineStack's trigger rule reads. On a multi-package stack,defineStackjudged each package against its ownrequireswhen the config loaded. A refusal there is theload-failedbranch, reached before the hint reads anything, so the union is what decides whether the item runs.test/normalized-call-sites.test.tsgains a second table. It enumerates every member read of a package-owned key off a stack-named receiver inpackages/cli/src. The key set comes frompackageOwnedCollectionKeys()(now exported fromstack-collections.ts, derived from the two schemas, 37 keys, envelope keys excluded). Every read is classified. Aresolvedrow carriesevidence: code that must be present in the file (comments and strings blanked), which proves the fold. Atop-levelrow carries the reason. 53 reads in 15 files after the fix: 6top-levelrows (the build preflight'sconfig.requires×2, serve'sconfig.analyticsCubesfirst leg andconfig.docs,collect-docs' artifactstack.docs,scaffold-wiring's presence probe) and 12resolvedreceivers. 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 insrctoday.Pins, and the tier each runs in
test/serve-package-declared-capabilities.test.ts: integration tier (per-PR),runServe(boots. Coversautomationmounted on two packages, the one-package control, theainarrowing, cubes plus the flow line, and thetiersnarrowing. 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), spawnsos doctor,os diff --jsonandos migrate meta --json, two packages vs one.src/utils/schema-migrate.requires-providers.integration.test.ts: integration tier, a second case where a package declaresrequires, on the real kernel boot.test/package-owned-readers.test.ts: unit tier.stackDeclaredCapabilities, theos generatehint and the types/migration generators against realcomposeStacksoutput, each paired with onedefineStack.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), at6a30bcff0. No build leg: the scanner readssrcand the spawned CLI runssrcthrough tsx (bin/run-dev.js).Array.isArray((config as any).requires) ? … : []: anchor 1→0, blob7a588141d660→4bf8b068950a. Three tests red:no read is unclassified, namingcommands/serve.ts :: config.requires;two packages: the service package's automation is mounted; and theainarrowing. The one-package control and the cubes/tiers cases stayed green. Restored: blob7a588141d660== HEAD,git diff HEADempty.package-owned-readers.test.ts. Restored: blob3ec828a90838== HEAD,git diff HEADempty.Direction observed: red, as expected.
Verification
Everything below ran at the merged head
a58d828b0. That is this branch merged withorigin/mainatdc4a5c630, which brought #22304'sserve.tschange. The merge was clean, and all four edits toserve.tswere checked present after it. The dists were rebuilt after the merge.pnpm --filter @objectstack/cli typecheck: exit 0. That istsc --noEmit, thencheck: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.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderives the same 67 commands as the dispatch order lists. All 67 ran and exited 0. The--ranreconciliation reads67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. On the pre-merge tree,check:dual-build-cjs-loadsandcheck:i18n-coveragefirst refused withPREREQUISITE NOT METbecause some packages had no dist. Those dists were built and both gates re-ran green.pnpm lint(full,eslint . --no-inline-config): exit 0.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)
createStandaloneStack's result carriesrequires,objects,manifest,permissions,positionsandi18n, but nottiers,analyticsCubesorflows. Anos startwith 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.collectAndLintDocs' comment says a doc already on a package body "is also indocsabove, wherelintMetadataEmbedshas 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.cubesfallback, andos generate'sdata?.objectsfallback, are consumer-side spellings that predate this card. They are kept byte-for-byte in precedence, not endorsed.os generate"Not wired" advice still saysinside defineStack({ … })without naming which package. Therequires:line it prints is the union of what the packages declare plus what is missing, so it never drops a declared token.os 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, the producer-side member of this family, is not addressed here and remains open.Generated by Claude Code