Skip to content

Commit e7dfb1d

Browse files
os-litantclaude
andauthored
fix(runtime): honour external.validation.checkOnBoot in the boot sweep (#13037) (#13149)
`checkOnBoot` was declared on `DatasourceSchema` with `.default(true)` and read by nothing: `ExternalValidationPlugin.start` hooked `kernel:ready` and called `runValidation(ctx)` unconditionally. An author who wrote `checkOnBoot: false` and left `onMismatch` at its default still got the boot sweep, and a measured mismatch still aborted boot with `ExternalSchemaMismatchError`. The sweep now resolves each datasource's parsed `checkOnBoot` and drops the rows of any datasource that set `false` before a verdict is drawn from them: no `onMismatch` policy, no unreachable-remote warning, not counted in the all-clear, and one info line naming what was skipped. `true` or absent leaves the sweep exactly as it was. Uncertainty resolves towards validating — an absent key, a legacy stored row, or a definition the metadata service could not read is checked, never inferred to have opted out. Scope pinned at the maintainer's ruling: the boot step only. `scheduleDriftChecks()` / `checkIntervalMs` stay independent, stated in an in-place comment at both read points and held by a test. Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd Co-authored-by: Claude <noreply@anthropic.com>
1 parent 62e87f1 commit e7dfb1d

3 files changed

Lines changed: 613 additions & 10 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
"@objectstack/runtime": patch
3+
---
4+
5+
fix(runtime): honour `datasource.external.validation.checkOnBoot` in the boot validation sweep (#13037)
6+
7+
`checkOnBoot` has been declared on `DatasourceSchema` — with `.default(true)`
8+
since the `external.validation` block was written, and **nothing read it**. Its
9+
two block-mates are read (`onMismatch` by `resolveOnMismatch()`,
10+
`checkIntervalMs` by `scheduleDriftChecks()`), which is what made the gap legible
11+
rather than a whole-block miss: `ExternalValidationPlugin.start` hooked
12+
`kernel:ready` and called `runValidation(ctx)` with no condition on it.
13+
14+
So an author who wrote `validation: { checkOnBoot: false }` and left `onMismatch`
15+
at its default got the boot sweep anyway, and a measured mismatch threw
16+
`ExternalSchemaMismatchError` and **aborted boot** — the exact outcome the key
17+
reads as opting out of. The `.default(true)` made it worse than an ignored key:
18+
the knob is materialized into every parse output, so a dead setting is
19+
byte-identical to an honoured one in stored and serialized datasources, and
20+
neither an author, an AI author, nor someone reading the metadata store could
21+
tell which one they had.
22+
23+
Maintainer ruling 2026-08-29 — ADR-0049 disposition **enforce, not remove**:
24+
25+
- `checkOnBoot: false` ⇒ that datasource is skipped by the `kernel:ready` sweep.
26+
No `onMismatch` policy is applied to its rows, so a measured mismatch on it can
27+
no longer abort startup; its unreachable-remote rows raise no boot warning; and
28+
its objects are not counted in the all-clear. The skip is logged once, naming
29+
the datasources and stating that the verdict beside it covers the remaining
30+
ones only.
31+
- `checkOnBoot: true` or absent ⇒ today's behaviour, unchanged — a measured
32+
mismatch still throws `ExternalSchemaMismatchError` and aborts boot under the
33+
default `onMismatch: 'fail'`.
34+
35+
The gate is **per datasource**, because the sweep is whole-farm and the key is
36+
per-source: in one boot, an opted-out datasource does not suppress another
37+
datasource's abort. Every uncertainty resolves towards running the check — an
38+
absent key, an unparsed or legacy stored row, a managed datasource with no
39+
`external` block, and a definition the metadata service could not read are all
40+
validated, never inferred to have opted out.
41+
42+
**Scope, pinned at the ruling: the boot step only.** `scheduleDriftChecks()` and
43+
its `external.validation.checkIntervalMs` read point stay independent — a
44+
datasource that opts out of the boot check keeps whatever background drift
45+
checking it armed. The two keys answer different questions ("gate my startup on
46+
this" versus "watch this while I run"), and both the code comment and a test hold
47+
that boundary.
48+
49+
Not a contract-face change: no schema, no key, and no accepted spelling moves.
50+
`checkonboot` and `validateonboot` remain what they already were — entries in the
51+
`strictObject` rejection table that refuse the misspelling and prescribe
52+
`checkOnBoot` — so `checkOnBoot` is the single authorable spelling and the gate
53+
has a single read point.

0 commit comments

Comments
 (0)