Skip to content

fix(cli): serve --no-server with OS_MIGRATE_AND_EXIT=1 provisions the server boot's table set - #22304

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22202-migrate-exit-schema-parity
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22202-migrate-exit-schema-parity

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22202
Clause-②: no

What was wrong

os serve composed createRestApiPlugin only under flags.server. That plugin's init() registers sys_import_job through the manifest service (packages/rest/src/rest-api-plugin.ts), and schema sync creates tables only for the objects registered in that boot. So serve --no-server with OS_MIGRATE_AND_EXIT=1 created one table fewer than the same config's server boot.

Reproduced first (H1) on origin/main fbcbcf12 with examples/app-todo. Each run used a fresh SQLite file and OS_MIGRATE_AND_EXIT=1; --no-server was the only difference.

run plugins tables difference
server boot 39 70
--no-server 36 69 sys_import_job, and nothing else

The card measured 113 against 114 on cloud's control-plane config, with the same single difference. The cloud report it cites was not readable from this session (see Acceptance notes), so that number is the card's, not re-read here.

What changed

packages/cli/src/commands/serve.ts now composes the REST API plugin on every boot. Only the dispatcher and the no-auth refusal ride flags.server. On a server boot the kernel.use sequence is unchanged: the no-auth refusal, then REST, then the dispatcher.

After the fix, with the same config and procedure:

run plugins tables difference
server boot 39 70
--no-server 37 70 none

With no HTTP server, RestApiPlugin.start() mounts nothing and prints one warn: RestApiPlugin: HTTP Server service 'http.server' not found. REST routes skipped. About a dozen service plugins on that path already do the same: in the same --no-server boot log, storage, settings, sharing, auth, i18n, marketplace and api-trigger each print a "no HTTP server, routes not registered" line.

Where the registration lives, and why it did not move (H3)

The PM's lean was to move sys_import_job to the plugin that owns its neighbouring @objectstack/platform-objects/audit objects, or to the boot path that registers platform objects on every boot. I measured both:

  • The neighbours have no common owner. Each one is registered by the plugin that consumes it: sys_job / sys_job_run by JobServicePlugin, sys_job_queue by QueueServicePlugin, sys_notification by MessagingServicePlugin, sys_attachment by StorageServicePlugin, and sys_email / sys_email_template by EmailPlugin. RestApiPlugin already follows that pattern for sys_import_job; its own comment says the REST plugin owns the import feature, so it owns this object.
  • serve composes those owners whatever the flag, and without a listener they only skip their routes. So the listener-independent home is the owner itself, composed on every boot. This is the claim's second option: register it from the serve path on every boot.
  • The every-boot platform path is PlatformObjectsPlugin in packages/platform-objects. It belongs to another lane, and the order makes a move there a stop-and-report. It would also change the owning manifest. sys_import_job rides com.objectstack.rest.api with scope: 'system' and defaultDatasource: 'cloud', and ObjectQL.resolveDatasourceBinding routes an object by its owning package's defaultDatasource. That is why PlatformObjectsPlugin keeps a separate manifest for the activation ledger. Keeping the owner keeps routing and ownership unchanged.
  • A new @objectstack/rest export for the object half would widen the public surface, contrary to Clause-②: no. It would also either break every other composer of createRestApiPlugin (plugin-dev, the verify harness, embedders) or leave two registrars.

Nothing registers it twice. It has one registrar, RestApiPlugin.init(), composed once per boot.

The other flag-gated plugins (H4)

serve composes three plugins only under --server. Only the REST plugin registers an object:

  • createRestApiPlugin registers sys_import_job (one manifest registration). This PR fixes it.
  • createDispatcherPlugin registers nothing: its init() is a no-op ("Consumer-only plugin — no services registered"), and the file never reads the manifest service.
  • HonoServerPlugin registers nothing: zero manifest reads.

After the fix the table sets are equal on both examples/app-todo and the pin's fixture, so no other object is missing.

H5 and docs

The "refuse loudly" fallback is not used, because H3 found a home. No docs edit is owed: cli.mdx's "Skip HTTP server (kernel only)" and "Toggle HTTP server plugin" stay true, since --no-server still adds no HTTP server plugin and no dispatcher.

The pin

packages/cli/test/serve-migrate-exit-schema-parity.integration.test.ts boots os serve twice on one fixture config, each run on a fresh SQLite file with OS_MIGRATE_AND_EXIT=1. It asserts:

  • control: both runs exit 0 and print the migrate path's completion line;
  • control: the server boot's table set contains the fixture's table and sys_import_job;
  • the --no-server table set equals the server boot's.

Tier: integration. The file spawns the CLI, so it runs in pnpm test's integration project on every PR (Test Core), not in the nightly. Locally the file took 22s.

Ablation

I put the REST composition back under flags.server with scripts/ablation-replace.mjs, changing try { to if (flags.server) try { at the createRestApiPlugin import. The anchor went 1 → 0 and the blob 6102920f → 62abf481. The pin spawns bin/run-dev.js, which runs serve.ts from src/ through tsx, so the mutation reached the code under test with no build.

The equality went red and both controls stayed green:

AssertionError: expected [ 'parity_task', 'sys_account', …(66) ] to deeply equal [ 'parity_task', 'sys_account', …(67) ]
-   "sys_import_job",
Tests  1 failed | 2 passed (3)

After the restore, the blob equals HEAD's (6102920f), and both git diff HEAD and git status --porcelain are empty.

Verification, at f0a27d66

  • The pin: 3 of 3 passed. That run was at f2264e62; f0a27d66 adds only the changeset.
  • @objectstack/cli, --project unit: 264 of 264 files and 3886 tests passed (262 files on the first run; the 2 published-subpath-* pins needed packages/cli/dist and passed once it was built), plus 29 skipped. test/vitest-tiers-partition.test.ts passed.
  • @objectstack/cli, --project integration: 94 of 94 files and 903 tests passed, 2 skipped (exit 0). This was run locally because the diff touches the serve boot path and adds an integration-tier file.
  • @objectstack/cli typecheck: exit 0. The new test file is in the tsconfig.test.json program (--listFiles, 1 hit) and adds no error; check:test-typecheck reports the 28 existing ledgered errors held.
  • @objectstack/rest tests: 262 of 262 files and 4938 tests passed; typecheck exit 0. The package is unchanged; the order named it.
  • pnpm lint (full, eslint . --no-inline-config): exit 0.
  • The 67 derived gate commands (dispatch-gates.mjs --commands, the same set the order listed): all exit 0. Four of them first answered PREREQUISITE NOT MET (check:dual-build-cjs-loads, check:i18n, check:i18n-coverage and check:i18n-walk-parity), and each was re-run green after the build. dispatch-gates --ran: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.

Acceptance notes

  • One composition changes. A config that puts its own HonoServerPlugin in plugins and runs --no-server now gets the REST routes on that server. I measured a fixture of that shape on HEAD and under the ablation above. GET /api/v1/discovery went from 404 to 200, and GET /api/v1/data/OBJECT from 404 to 401 (anonymous access is denied). /api/settings (200) and /api/v1/auth/get-session (401) answered the same in both, so those plugins already mounted on that server. No config in this repo has that shape. Cloud's measured boot differs by three plugins (Hono, REST, dispatcher), so it composes no Hono of its own. The no-auth boot refusal stays keyed on --server, as before.
  • --no-server boots now print one more warn line: the REST plugin's "no HTTP server" line.
  • NOT MEASURED: the cloud dev report the card cites (objectstack-ai/cloud#2274, comment 6053481678). This session has no access to objectstack-ai/cloud: REST answered 403, and add_repo was refused.

Generated by Claude Code

claude added 4 commits October 8, 2026 12:31
…ver provisions sys_import_job

The REST plugin's init() registers sys_import_job; schema sync creates
tables only for objects registered in that boot. While the whole plugin
rode flags.server, an OS_MIGRATE_AND_EXIT=1 run with --no-server
provisioned one table fewer than the server boot it prepares for.
Only the dispatcher and the no-auth refusal now ride the listener flag.

Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-Authored-By: Claude <noreply@anthropic.com>
…and the server boot

Boots os serve twice on one fixture config, each on a fresh SQLite file
with OS_MIGRATE_AND_EXIT=1, and asserts the --no-server run leaves the
same table set as the server boot, with sys_import_job in the server
boot's set as the control. Integration tier, so it gates pull requests.

Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Oct 8, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os serve (command, 31 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 14:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 14:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit dc4a5c6 Oct 8, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22202-migrate-exit-schema-parity branch October 8, 2026 14:46
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants