Repository navigation
feat(core): ObjectKernelConfig.bootPhaseHooks, a declared option to bootstrap without dispatching kernel:ready / kernel:bootstrapped / kernel:listening - #22354
Conversation
…ootstrap without dispatching kernel:ready / kernel:bootstrapped / kernel:listening Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
…PhaseHooks Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 27 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 259716e465df1cbb6c9e3319fa3c68cb2c9af936 && git checkout 259716e465df1cbb6c9e3319fa3c68cb2c9af936
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 54c3ce10ce8cf89290b67813c34d1f10dbab6938 4e62f58ea0f19b334fed827e6295287fac5291da && git checkout -B drift-repro 54c3ce10ce8cf89290b67813c34d1f10dbab6938 && git merge --no-ff 4e62f58ea0f19b334fed827e6295287fac5291da
node scripts/docs-audit/affected-docs.mjs --json 54c3ce10ce8cf89290b67813c34d1f10dbab6938
|
Contract reviewServed-tier: Inputs: card #22272 (body and all six comments: the triage grade ① Derived judgments
② Semver level
③ Boundary flags
Check-runs on the head, read at 2026-10-08T21:43Z: 23 success, 3 skipped ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…ess when its teardown times out (objectstack-ai#22389) Fixes objectstack-ai#22335 Clause-②: no A kernel built with `gracefulShutdown: false` no longer calls `process.exit(1)` when its teardown overruns `shutdownTimeout`. It logs the timeout at `error`, marks itself `stopped`, and `shutdown()` resolves, so the host decides whether the process exits. A kernel with `gracefulShutdown: true` (the default) keeps the hard `exit(1)`, with the same log line as before. This implements the maintainer's ruling of record on objectstack-ai#22335 (comment 6070759640, letter B): > **B.** `gracefulShutdown: false` means the kernel does not own the process: when its teardown times out it logs the error, marks itself `stopped`, and returns to the host, which decides whether the process exits. `gracefulShutdown: true` (the default; the kernel installed the signal listeners itself) keeps today's `exit(1)` on a genuine timeout. The objectstack-ai#5274 pin keeps what it tests, on a `true` fixture; a new pin holds "`false` does not exit on timeout"; the JSDoc states both meanings of the option; the changeset names the host that relied on the old exit and what it must do (exit itself, as it already handles signals). ⛔ Not taken: A (the docs bent to the code, and one hung environment ends every environment in the hosted runtime), C (a second public switch for a split no measured user needs), D (a change to `shutdown()`'s never-throws contract for every host). No option, key or export is added, and `shutdown()` still never rejects. ## What changed - **`packages/core/src/kernel.ts`, `shutdown()`'s genuine-timeout branch.** The branch is still reached only by the identity-matched timeout error. It now reads `this.config.gracefulShutdown`, the same test the constructor uses to install the signal listeners, so a kernel exits on a timeout exactly when it took the signals itself. - `true`: the old code, unchanged. It logs `Shutdown timed out — forcing exit`, destroys the logger and calls `process.exit(1)`. - `false`: it logs at `error` and falls through to the `finally`. The log line names the timeout in ms and says that the teardown is still running in the background, that `gracefulShutdown` is false, that the process is NOT being exited, and that the host decides. - The non-timeout branch is unchanged. - **`ObjectKernelConfig.gracefulShutdown` JSDoc** now states both meanings: who installs the signal listeners, and who exits the process when a teardown times out. It also says that the hung teardown is not cancelled and keeps holding what it has not released. - **`packages/core/src/kernel.test.ts`.** The objectstack-ai#5274 timeout pin ("still logs the timeout and still forces exit(1) when shutdown genuinely times out") keeps its title and assertions; only its fixture moves to `gracefulShutdown: true`. Because a `true` kernel listens on the worker's own process, the pin now removes any signal listener it added in its `finally`. - **New `packages/core/src/kernel-shutdown-timeout-ownership.test.ts`** (4 tests): - `false` plus a genuine timeout: no exit, `stopped`, `shutdown()` resolves, exactly one error-level timeout line carrying an `Error`, and no `forcing exit` line. The hung teardown is then released: it resumes in the background (`teardown-resumed`, `destroy`), the kernel stays `stopped`, there is still no exit, and a second `shutdown()` is the already-stopped no-op. - R7's shape: two `false` kernels in one process; stopping the hung one leaves the sibling `running` with no exit, and the sibling still stops normally. - Two controls under `false`: a throwing `destroy()` (isolated inside the teardown), and an error escaping the teardown (the catch's non-timeout branch). Neither exits, and neither is reported as a timeout. - **`.changeset/22335-graceful-shutdown-false-no-exit.md`**: `@objectstack/core` `minor` with the **BREAKING** banner, under the launch-window convention, with ADR-0087 disposition `not-required (no-migration-prescription)`. It names the host that relied on the old exit (a `gracefulShutdown: false` host: a server framework with its own signal handlers, or one kernel per environment in one process) and the remedy: exit the process yourself once `await kernel.shutdown()` returns, as you already do for signals. ## Before and after (in process, `process.exit` stubbed) Probe: two kernels of the same setting per row, `shutdownTimeout: 20`. The first kernel's teardown either hangs (a `kernel:shutdown` subscriber that never settles) or throws an error that escapes the teardown. The host stops the first kernel. "Other kernel" is the second kernel's state when `exit` was called, or after `shutdown()` returned if it was not. | Row | Before: exit calls (base `16096e8d7b`) | Before: other kernel | After: exit calls (`030a795001`) | After: other kernel | |:--|:--|:--|:--|:--| | `false`, teardown hangs | 1, `exit(1)` | `running` when the process exits | 0 | `running`, process alive | | `true`, teardown hangs | 1, `exit(1)` | `running` when the process exits | 1, `exit(1)` | `running` when the process exits | | `false`, teardown throws | 0 | `running` | 0 | `running` | In every row, before and after, the stopped kernel ends `stopped` and `shutdown()` resolves. ## Measurements behind the dispatch's assumptions - **M1, the defect: reproduced.** The first row above, at base `16096e8d7b`: `exit(1)` fires while the other `false` kernel is `running`. - **M2, who relied on the exit.** - In this repo, no production code builds a kernel with `gracefulShutdown: false`. `git grep -E "gracefulShutdown:\s*false" origin/main -- 'packages/**'` at `345d3f3d86` finds 49 files, all tests, and 0 non-test lines. - No test except the objectstack-ai#5274 pin can reach the timeout branch. The only fixtures with a short or explicit `shutdownTimeout` are that pin (20 ms, now on `true`) and the objectstack-ai#10604 guard pin (60 s, fake timers, its teardown completes). - Every production kernel in the repo takes the default (`true`), so its behaviour is unchanged: - CLI `serve` (`packages/cli/src/commands/serve.ts:3173`, `new Runtime({ kernel: { logger } })`); - every one-shot CLI command through `bootSchemaStack` (`packages/cli/src/utils/schema-migrate.ts:492`, `new Runtime({ cluster: false })`), which still calls `stack.shutdown()` and then exits; - `packages/objectql/src/kernel-factory.ts:35`; - `packages/verify/src/harness.ts:504`. - No CLI path can now hang where it used to exit. - The one measured producer of `false` is objectstack-ai/cloud's hosted runtime (`packages/objectos-runtime/src/artifact-kernel-factory.ts`, per the ruling's premise read). That path is not in this repo, and this session has no read access to objectstack-ai/cloud. **NOT MEASURED: what that host does after `shutdown()` returns.** The changeset names its shape and remedy. - **M3, the `true` path is unchanged.** Same branch body, same log line (`Shutdown timed out — forcing exit`), same `logger.destroy()` and then `process.exit(1)`. `handleShutdownSignal` is untouched. The objectstack-ai#5274 pin on a `true` fixture is green, and ablation leg B below shows it depends on this branch. - **M4, the abandoned teardown.** After a `false` timeout, `performShutdown()` keeps running and the kernel does not act on it again: - It writes no state; `state` is assigned only in `shutdown()` and `bootstrap()`. - It releases no listeners; that happens only in those two methods. - `raceWithTimeout`'s `Promise.race` keeps a handler on the operation, so a later rejection is not unhandled. - The kernel's own logger, already destroyed in `finally`, writes later lines to stdout and stderr only (`fileStream` is cleared). - The first new test pins this: release the hung teardown after `shutdown()` resolved, and the kernel stays `stopped` with no exit. - The log line and the JSDoc both say that the hung teardown keeps running and that the host owns what follows. ## Tests (at `77e21f89c2`; the one commit after the code commit `030a795001` adds only the changeset) - `@objectstack/core`, `pnpm --filter @objectstack/core test`: 82 files, 2223 tests passed. `test:repo`: 3 files, 48 passed. - `pnpm --filter @objectstack/core typecheck`: exit 0. `check:test-typecheck` is OK with the debt ledger unchanged at 4. `tsc -p tsconfig.test.json --listFiles` compiles both touched test files. - `@objectstack/runtime` (its dependency closure built first, 32 projects): `vitest run --project local` gives 341 files, 4787 passed and 19 skipped. `--project repo` gives 3 files, 751 passed. - **Reverse verification (ablation)** on committed `030a795001`. Each leg ran through `scripts/ablation-replace.mjs` (anchor hit 1 to 0, blob changed on disk), then restored with `git checkout HEAD`; the restore was proven by the blob matching HEAD (`0bf80b6dc7ce`) and an empty `git diff HEAD`. The tests import `./kernel` from source, so no `dist/` is involved. - **Leg A**: the new condition forced to `if (true)`. Both `false` pins go red (`expected [ [ 1 ] ] to deeply equal []`), and the two controls and the objectstack-ai#5274 pins stay green. - **Leg B**: the condition forced to `if (false)`. Only the objectstack-ai#5274 timeout pin goes red (`expected [ Array(1) ] to include 'Shutdown timed out — forcing exit'`), and the `false` pins stay green. - **Lint, a declared narrowing.** - eslint `--no-inline-config --format json` on the three touched TS files: 3 files, 0 errors, 0 warnings. `--print-config` resolves a config for each, so they are inside the linted population. - This narrowing cannot change any verdict on an untouched file: `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project` and no `projectService`, which it also states in prose). - The full `pnpm lint` belongs to CI. - **Gates.** - `node scripts/pm/dispatch-gates.mjs --commands` at `77e21f89c2` derived 63 commands, and each was run with its exit captured. - All 63 exited 0. `pnpm check:dual-build-cjs-loads` first exited 3, PREREQUISITE NOT MET, because it reads every package's `dist/`. It was re-run after a whole-tree `turbo run build` (72 tasks) at the same head: 107 published require entry points across 66 packages load. - `--ran` reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN. ## Serial check with objectstack-ai#22354 Re-fetched just before opening: `origin/main` is `117d34de3f`, and objectstack-ai#22354 has not landed. Nothing in `packages/core` moved since base `16096e8d7b`, so this branch is not merged with main. Both merges are clean: - `git merge-tree --write-tree origin/main HEAD` exits 0 (tree `4c796f278a`). - `git merge-tree --write-tree` of objectstack-ai#22354's head (`4e62f58ea0`) with this branch exits 0 (tree `8dead452b0`), and the merged `kernel.ts` carries both changes. This branch does not touch `bootstrap()` or `bootPhaseHooks`. ## Acceptance notes - `content/docs/protocol/kernel/lifecycle.mdx` is not edited. Its sentence (a `false` kernel "stays out of the process-management business") was false only on the timeout path, and is now true there too. - `content/docs/kernel/events.mdx` says `process.exit(1)` "is reserved for a genuine `shutdownTimeout` overrun". It is still true as an exclusivity statement, but a reader could take it to mean every overrun exits. Not edited (not in this card's file surface). Carrier: none. - The pending note `.changeset/22286-kernel-signal-listeners.md` listed under "Unchanged" that "A genuine shutdown timeout still calls `process.exit(1)`". Both notes ship in the same pre-mode release, so patch round 1 qualifies that sentence (see below). - `new ObjectKernel({ gracefulShutdown: undefined })`, an explicit `undefined`, overrides the spread default. Such a kernel installs no listeners and, after this PR, also does not exit on a timeout. That is consistent by construction, because both halves read one predicate. No producer in this repo passes a possibly-undefined value. - `shutdown()` resolves the same way whether the teardown finished or timed out; the timeout reaches the host only through the `error` log line. A host that wants a non-zero exit status after a hung teardown has no return-value signal, since option D was not taken. - Read, not measured: if a logger `file` is configured and a plugin logs through a child logger after a `false` timeout, that write meets the file stream the parent closed. The logger's own `error` handler disables file logging with one notice; it is not fatal. Under `true` this was unreachable, because the process exited. ## Patch round 1: objectstack-ai#22286's pending note qualified Commit `c6c4351a82` changes one sentence in `.changeset/22286-kernel-signal-listeners.md`, in its "Unchanged" bullet, and nothing else in that file. It makes no code or test change. - Before: "A genuine shutdown timeout still calls `process.exit(1)`." - After: "With `gracefulShutdown: true`, a genuine shutdown timeout still calls `process.exit(1)`; a `gracefulShutdown: false` kernel no longer exits (objectstack-ai#22335)." **Why.** `.changeset/pre.json` on `main` is in pre mode (`mode: pre`, tag `next`) and has consumed no changesets yet. So that note and this PR's `.changeset/22335-graceful-shutdown-false-no-exit.md` ship in the same release, and the unqualified sentence would be false there for a `gracefulShutdown: false` kernel. This is a deliberate correction of another card's pending note. `Check Changeset` stays red on it by design (the foreign-correction rule), and the seat's same-head contract review is the written confirmation. **Gates at `c6c4351a82`.** - `dispatch-gates --commands` derived the same 63 commands as before. It warned that the tree is behind `origin/main` `c8c803c293`, where two gate inputs changed (`scripts/migrate/overlay-views-to-sys-view-definition.md` and `scripts/platform-object-tenancy-census.json`). Nothing in `packages/core`, the two changesets or `.changeset/pre.json` moved on `main`, and objectstack-ai#22354 has not landed, so this branch is not merged with main. - The whole tree was rebuilt first (`turbo run build`, 72 of 72 tasks cached), then each command ran with its exit captured. 62 exited 0. - `node scripts/check-empty-changeset.mjs --base origin/main` exited 1, as expected. It reports ".changeset/22286-kernel-signal-listeners.md: present on the merge base and CHANGED by this PR", the deliberate-correction class above. - `--ran` reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN. --- _Generated by [Claude Code](https://claude.ai/code/session_01EUBvqtauTDmHi2ZgY759p2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22272
Clause-②: yes (widening: a new public option on
ObjectKernel)That line is the claim's (comment
6067012449), copied verbatim.What
ObjectKernelConfig.bootPhaseHooks?: boolean, defaulttrue.new ObjectKernel({ bootPhaseHooks: false })runs the rest ofbootstrap()unchanged: dependency ordering, every plugin'sinit(), the core-service fallbacks, every plugin'sstart()and the system-requirement check. It then returns without dispatchingkernel:ready,kernel:bootstrappedorkernel:listening, and the kernel isrunning. Only an explicitfalsewithholds the hooks. An absent option orundefinedkeeps today's boot.Runtimeforwards the option through its existingkernelconfig with no change.A withholding boot logs one
warn. It names each withheld hook and how many registered handlers it left uncalled, and the line's meta carries the same counts aswithheldHandlers. The boot's completion line says the hooks were withheld.The option's docblock states three things:
init()orstart(), including a driver connected there.shutdown()is unchanged.The PM's mechanism hypotheses, as measured
H1 holds. In
ObjectKernel.bootstrap()(packages/core/src/kernel.tsat3599fef12), the order is:preInjectCoreFallbacks()(L451);state = 'running'at L455;validateSystemRequirements()(L487);context.trigger('kernel:ready')(L489),'kernel:bootstrapped'(L501) and'kernel:listening'(L511);No hook fires between
initandstart, and only the completion line follows the three.git grepover the non-test files inpackages/**finds no other caller that triggers any of the three names.hook-dispatch.tsholds only the shared dispatch loop, so the option is one branch aftervalidateSystemRequirements().H2: the option gives the same observable result as the consumer's wrapper, measured with in-repo plugins. A one-off script (not committed) composed
ObjectQLPlugin,DefaultDatasourcePlugin(sqlite-wasm, in memory),AppPlugin(examples/app-todo)and a probe plugin that subscribes to all three hooks. It booted that composition three ways: a full boot, cloud's patch reproduced (wrapcontext.hookand drop the three names), andbootPhaseHooks: false. Results:objectql.registry.getAllObjects(): 5 in each mode, with the JSON byte-equal across all three (sha256 prefixe5a58708de9898a4);["default"]in all three;kernel:ready,kernel:bootstrappedandkernel:listeningon the full boot, and nothing in the other two modes;stopped.That a full boot matches the withheld boot is again a reading of one slate. The two mechanisms differ in one place: the wrapper refuses the registration, while the option skips the dispatch. So under the option a handler for those names is still stored, and counted in the
warn. A plugin that calledcontext.triggeron one of the names itself would still run its handlers, but no plugin in this repository does that.H3: the name is
bootPhaseHooks?: boolean, defaulttrue. It names the mechanism the kernel controls, and it is the card's own spelling:{ bootPhaseHooks: false }reads as "no boot-phase hooks". Among the existing options it sits with the positive-sense booleans that default on,gracefulShutdownandrollbackOnFailure. It does not followskipSystemValidation, because that option'sskip*spelling and docblock mark a test escape, and this is a production mode for a repair host. A mode named for the outcome, such asdefinitionsOnlyorbootMode: 'repair', was rejected. It would promise complete definitions, and the kernel cannot keep that promise, because a definition registered in a boot-phase handler is absent.The card's open question (the seat's ruling in the claim)
This is a separate
ObjectKerneloption, not a variant ofcomposeForDeclarations, and the cli utility is not touched.The cli utility cannot share this mechanism as it is written.
composeForDeclarationsworks per host plugin. A context proxy declineskernel:bootstrappedandkernel:listeningat registration, and that plugin'sstart()is suppressed. The base stack's hooks,kernel:readyincluded, still run on that boot. The option is kernel-wide and keeps everystart(). Moving the cli onto it would change which plugins' hooks a migration boot runs.Tests
packages/core/src/kernel.boot-phase-hooks.test.tsholds 9 tests over one composition. A registry plugin and a declarer register one definition each ininit(), instart()and in akernel:readyhandler, with a spy on each boot-phase hook.bootPhaseHooks: false:init()andstart()definitions are present and the kernel runs;kernel:readydefinition is absent;kernel:readyhandler that throws and akernel:bootstrappedhandler that never settles do not stop the boot;warnreports{ kernel:ready: 2, kernel:bootstrapped: 1, kernel:listening: 1 }, once;shutdown()runskernel:shutdown,destroy()andonShutdown()in that order, and reachesstoppedunder a 2 s guard without callingprocess.exit. No withheld hook fires during shutdown.trueandundefined): all three hooks fire once, in order, all three definitions are present (thekernel:readyone included), and nothing is reported as withheld.Ablation. The option was made a no-op through
scripts/ablation-replace.mjs: the anchorif (this.config.bootPhaseHooks === false) {was replaced withif (this.config.bootPhaseHooks === false && false) {, the anchor count went from 1 to 0, and the blob changed. The result was 5 red and 4 green, in the expected direction.init()/start()guarantee pin and the three controls.The run was repeated at the merged head
4e62f58ea(blob707ede024adbto5ed4fdcf902c) with the same 5/4 split. After restore, the blob equals HEAD's707ede024adbandgit diff HEADis empty. The test imports./kernelfrom source, sodist/is not in the resolution path.Runs at
4e62f58ea(the branch withorigin/main54c3ce10cmerged in, which brings in #22334's signal-listener change to the same file; the textual merge was clean):pnpm --filter @objectstack/core test: 82 files, 2228 tests passed. Before the merge, at63173d1c6: 80 files, 2208 tests.pnpm --filter @objectstack/core typecheck: green.check:test-typecheckis OK: the new file is in thetsconfig.test.jsonprogram and compiles clean, and the ledger is unchanged..d.ts, run from@objectstack/runtimeat63173d1c6with a throwaway file that was then deleted:{ bootPhaseHooks: false }compiles when typed asObjectKernelConfigand asRuntimeConfig.kernel;{ bootPhaseHooks: 'no' }fails with TS2322;Gates
node scripts/pm/dispatch-gates.mjs --commandswas derived at4e62f58eawith no paths passed. The change set is 3 paths against merge base54c3ce10c, and the derivation gives 63 commands, the dispatch list's 49 plus 14. Most of the 14 come from the changeset, which now exists. The rest come from the new test file:check:engine-double-contract,check:objectql-double-limit,check:query-options-erasure,check:where-matcher,check:type-check-coverageandcheck:type-check-debt.4e62f58ea, and all 63 exited 0. Exit codes were captured before any pipe.✓ dispatch-gates --ran: 63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED (a DERIVED zero — all 63 recorded an exit code and none of them is 3).check:kernel-hook-pairs: 4 dispatchedkernel:*hooks, each pinned in bothkernel.test.tsandlite-kernel.test.ts;check:nul-bytes: OK;check:type-check-debt: 26 raw errors, none above their recorded number;check:dual-build-cjs-loads: 106 require entry points across 66 packages load. An earlier run at5eb197934printed PREREQUISITE NOT MET, which is not a pass. It measured on a later tree.Signal listeners under
bootPhaseHooks: false(thedomain:engineseat's question6067560824)Written into this body by the
domain:specseat 2 at 2026-10-08T21:45Z. The comments before it on this PR are read: the docs-drift bot's, and the contract review PASS6069627157.ObjectKernelinstalls its SIGINT / SIGTERM / SIGQUIT listeners in the constructor whengracefulShutdownis true, the default (kernel.tsabout:301). It releases them at every transition intostopped(fix(core): a stopped ObjectKernel removes its signal listeners and never exits the process #22334).bootPhaseHooks: falsechanges neither. A kernel booted with it isrunningand holds the listeners until itsshutdown().gracefulShutdown: falseas well, as this PR's test fixture does (kernel.boot-phase-hooks.test.tsabout:41). A repair kernel inside such a host is that shape.gracefulShutdown: falsemeans for process ownership is [decision] a kernel with gracefulShutdown: false still calls process.exit(1) when its teardown times out, ending every other kernel in a multi-kernel host; the docs say such a kernel stays out of process management #22335's decision, not this PR's. This PR adds nothing to it and takes nothing from it.packages/corehunks by thedomain:engineseat, as it stated in6067560824: requested when this PR leaves draft. Auto-merge is enabled only after that review.Acceptance notes
LiteKernelhas no matching option. The claim scopes this card toObjectKernel, the kernel a hosted runtime boots. No hook name is added, socheck:kernel-hook-pairsstays green. Carrier: none.content/docs/protocol/kernel/lifecycle.mdxlists the kernel options and could gain a row for this one. That file is outside this claim's file surface, and the table makes no claim to be complete, so nothing in it is false now. Carrier: none.packages/cli/src/utils/schema-migrate.tssayskernel.tsfireskernel:ready"unconditionally". That is still true for that boot, which does not set the option. Noted, not changed.destroy()that assumes its boot-phase handler ran is that plugin's concern, and the docblock says so. The in-repo HTTP adapter'sclose()already returns early when it never listened (packages/plugins/plugin-hono-server/src/adapter.ts).packages/corebelongs to thedomain:enginelane, declared on [PM seat] domain:engine — ⏳ vacant #6367 (comment6067025526). A contract review atCONTRACT_REVIEW_TIERis owed before enqueue. That review belongs to the seat.Generated by Claude Code