Repository navigation
Commit f6e1d49
feat(core): ObjectKernelConfig.bootPhaseHooks, a declared option to bootstrap without dispatching kernel:ready / kernel:bootstrapped / kernel:listening (#22354)
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`, default `true`.
`new ObjectKernel({ bootPhaseHooks: false })` runs the rest of
`bootstrap()` unchanged: dependency ordering, every plugin's `init()`,
the core-service fallbacks, every plugin's `start()` and the
system-requirement check. It then returns without dispatching
`kernel:ready`, `kernel:bootstrapped` or `kernel:listening`, and the
kernel is `running`. Only an explicit `false` withholds the hooks. An
absent option or `undefined` keeps today's boot. `Runtime` forwards the
option through its existing `kernel` config 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 as `withheldHandlers`. The boot's completion line says
the hooks were withheld.
The option's docblock states three things:
- **What it withholds:** the kernel's dispatch of the three hooks, and
nothing else.
- **What it guarantees:** every service and definition a plugin
registers in `init()` or `start()`, including a driver connected there.
`shutdown()` is unchanged.
- **What it does not guarantee:** that the definitions equal a full
boot's. A definition registered in a boot-phase handler is absent. "No
handler registers one" is a reading of a given set of plugins, not a
property of the option. It withholds the kernel's dispatch, not the hook
names.
## The PM's mechanism hypotheses, as measured
- **H1 holds.** In `ObjectKernel.bootstrap()`
(`packages/core/src/kernel.ts` at `3599fef12`), the order is:
- the Phase 1 init loop (L442-445);
- `preInjectCoreFallbacks()` (L451);
- the Phase 2 start loop (L453-484), with `state = 'running'` at L455;
- `validateSystemRequirements()` (L487);
- `context.trigger('kernel:ready')` (L489), `'kernel:bootstrapped'`
(L501) and `'kernel:listening'` (L511);
- the completion line (L513).
No hook fires between `init` and `start`, and only the completion line
follows the three. `git grep` over the non-test files in `packages/**`
finds no other caller that triggers any of the three names.
`hook-dispatch.ts` holds only the shared dispatch loop, so the option is
one branch after `validateSystemRequirements()`.
- **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 (wrap `context.hook`
and drop the three names), and `bootPhaseHooks: false`. Results:
- object definitions from `objectql.registry.getAllObjects()`: 5 in each
mode, with the JSON byte-equal across all three (sha256 prefix
`e5a58708de9898a4`);
- datasource definitions: `["default"]` in all three;
- the probe fired `kernel:ready`, `kernel:bootstrapped` and
`kernel:listening` on the full boot, and nothing in the other two modes;
- every kernel shut down to `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 called `context.trigger` on one of the names itself would
still run its handlers, but no plugin in this repository does that.
- **H3: the name is `bootPhaseHooks?: boolean`, default `true`.** 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, `gracefulShutdown` and `rollbackOnFailure`. It does not
follow `skipSystemValidation`, because that option's `skip*` spelling
and docblock mark a test escape, and this is a production mode for a
repair host. A mode named for the outcome, such as `definitionsOnly` or
`bootMode: '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 `ObjectKernel` option, not a variant of
`composeForDeclarations`, and the cli utility is not touched.
The cli utility cannot share this mechanism as it is written.
`composeForDeclarations` works per host plugin. A context proxy declines
`kernel:bootstrapped` and `kernel:listening` at registration, and that
plugin's `start()` is suppressed. The base stack's hooks, `kernel:ready`
included, still run on that boot. The option is kernel-wide and keeps
every `start()`. Moving the cli onto it would change which plugins'
hooks a migration boot runs.
## Tests
`packages/core/src/kernel.boot-phase-hooks.test.ts` holds 9 tests over
one composition. A registry plugin and a declarer register one
definition each in `init()`, in `start()` and in a `kernel:ready`
handler, with a spy on each boot-phase hook.
- **Under `bootPhaseHooks: false`:**
- the `init()` and `start()` definitions are present and the kernel
runs;
- the `kernel:ready` definition is absent;
- no spy fires;
- a `kernel:ready` handler that throws and a `kernel:bootstrapped`
handler that never settles do not stop the boot;
- the `warn` reports `{ kernel:ready: 2, kernel:bootstrapped: 1,
kernel:listening: 1 }`, once;
- `shutdown()` runs `kernel:shutdown`, `destroy()` and `onShutdown()` in
that order, and reaches `stopped` under a 2 s guard without calling
`process.exit`. No withheld hook fires during shutdown.
- **Controls** (the option absent, `true` and `undefined`): all three
hooks fire once, in order, all three definitions are present (the
`kernel:ready` one included), and nothing is reported as withheld.
**Ablation.** The option was made a no-op through
`scripts/ablation-replace.mjs`: the anchor `if
(this.config.bootPhaseHooks === false) {` was replaced with `if
(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.
- Red: the absent definition, no dispatch, the throw and never-settle
case, the withheld report, and shutdown (the spies fired).
- Green: the `init()`/`start()` guarantee pin and the three controls.
The run was repeated at the merged head `4e62f58ea` (blob `707ede024adb`
to `5ed4fdcf902c`) with the same 5/4 split. After restore, the blob
equals HEAD's `707ede024adb` and `git diff HEAD` is empty. The test
imports `./kernel` from source, so `dist/` is not in the resolution
path.
**Runs at `4e62f58ea`** (the branch with `origin/main` `54c3ce10c`
merged 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, at `63173d1c6`: 80 files, 2208 tests.
- `pnpm --filter @objectstack/core typecheck`: green.
`check:test-typecheck` is OK: the new file is in the
`tsconfig.test.json` program and compiles clean, and the ledger is
unchanged.
- A reverse check of the rebuilt `.d.ts`, run from
`@objectstack/runtime` at `63173d1c6` with a throwaway file that was
then deleted:
- `{ bootPhaseHooks: false }` compiles when typed as
`ObjectKernelConfig` and as `RuntimeConfig.kernel`;
- `{ bootPhaseHooks: 'no' }` fails with TS2322;
- runtime's program reports no other error.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands` was derived at
`4e62f58ea` with no paths passed. The change set is 3 paths against
merge base `54c3ce10c`, 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-coverage` and `check:type-check-debt`.
- All 63 were run at `4e62f58ea`, and all 63 exited 0. Exit codes were
captured before any pipe.
- The reconciliation over an exit-coded record printed: `✓
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).`
- Some readings:
- `check:kernel-hook-pairs`: 4 dispatched `kernel:*` hooks, each pinned
in both `kernel.test.ts` and `lite-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 at `5eb197934` printed PREREQUISITE NOT
MET, which is not a pass. It measured on a later tree.
- Left to CI, as the derivation itself lists: the path-scheduled CI
jobs, the four type-check lanes, the 52 artifact-roster families and the
11 wide-population families.
## Signal listeners under `bootPhaseHooks: false` (the `domain:engine`
seat's question `6067560824`)
Written into this body by the `domain:spec` seat 2 at 2026-10-08T21:45Z.
The comments before it on this PR are read: the docs-drift bot's, and
the contract review **PASS** `6069627157`.
- **The two options are orthogonal, by intent.**
- `ObjectKernel` installs its SIGINT / SIGTERM / SIGQUIT listeners in
the constructor when `gracefulShutdown` is true, the default
(`kernel.ts` about `:301`). It releases them at every transition into
`stopped` (#22334).
- `bootPhaseHooks: false` changes neither. A kernel booted with it is
`running` and holds the listeners until its `shutdown()`.
- **A host that owns the process** passes `gracefulShutdown: false` as
well, as this PR's test fixture does (`kernel.boot-phase-hooks.test.ts`
about `:41`). A repair kernel inside such a host is that shape.
- What `gracefulShutdown: false` means for process ownership is #22335's
decision, not this PR's. This PR adds nothing to it and takes nothing
from it.
- **Review of the `packages/core` hunks by the `domain:engine` seat**,
as it stated in `6067560824`: requested when this PR leaves draft.
Auto-merge is enabled only after that review.
## Acceptance notes
- **`LiteKernel` has no matching option.** The claim scopes this card to
`ObjectKernel`, the kernel a hosted runtime boots. No hook name is
added, so `check:kernel-hook-pairs` stays green. Carrier: none.
- **Docs.** The "Boot Configuration" table in
`content/docs/protocol/kernel/lifecycle.mdx` lists 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.
- **A cli comment.** A comment in
`packages/cli/src/utils/schema-migrate.ts` says `kernel.ts` fires
`kernel:ready` "unconditionally". That is still true for that boot,
which does not set the option. Noted, not changed.
- **Shutdown.** The kernel waits on nothing a withheld hook would have
done. A plugin's own `destroy()` that assumes its boot-phase handler ran
is that plugin's concern, and the docblock says so. The in-repo HTTP
adapter's `close()` already returns early when it never listened
(`packages/plugins/plugin-hono-server/src/adapter.ts`).
- **Cross-lane.** `packages/core` belongs to the `domain:engine` lane,
declared on #6367 (comment `6067025526`). A contract review at
`CONTRACT_REVIEW_TIER` is owed before enqueue. That review belongs to
the seat.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c8c803c commit f6e1d49
3 files changed
Lines changed: 342 additions & 0 deletions
File tree
- .changeset
- packages/core/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
63 | 73 | | |
64 | 74 | | |
65 | 75 | | |
| |||
85 | 95 | | |
86 | 96 | | |
87 | 97 | | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
88 | 143 | | |
89 | 144 | | |
90 | 145 | | |
| |||
507 | 562 | | |
508 | 563 | | |
509 | 564 | | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
510 | 576 | | |
511 | 577 | | |
512 | 578 | | |
| |||
542 | 608 | | |
543 | 609 | | |
544 | 610 | | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
545 | 634 | | |
546 | 635 | | |
547 | 636 | | |
| |||
0 commit comments