Skip to content

SettingsService accepts a write before its engine is bound and answers "resolved" while nothing reaches sys_setting — every kernel:ready hook registered from init() is inside that window #10159

Description

@os-elon

Found while measuring #9451's A′ preconditions (that card needed to know whether a kernel:ready migration can write a durable receipt through SettingsService). Filing separately rather than widening that card. Not a live defect today — no shipped caller writes settings at that moment — so this is a latent trap, recorded before someone walks into it.

Observation

SettingsService keeps an in-process memory array as its fallback store, and upsertRow picks between the engine and that array on if (this.engine). The engine is bound in exactly one place — SettingsServicePlugin.start() registers a kernel:ready hook, and bindEngine is called inside it (packages/services/service-settings/src/settings-service-plugin.ts:202).

kernel:ready handlers run in registration order, and every plugin's init() runs before any plugin's start(). So any hook registered during init() fires before the settings plugin has bound its engine — and a set() from such a hook returns a fully resolved value while nothing reaches sys_setting.

That is not a hypothetical position: assembleMetadataProtocol registers its three platform migrations' kernel:ready hook from ObjectQLPlugin.init() (packages/objectql/src/plugin.ts:350packages/metadata-protocol/src/plugin.ts:306), which is the exact window.

Measured

Real LiteKernel, real ObjectQLPlugin over a real SqlDriver (better-sqlite3), real SettingsServicePlugin, and a probe plugin registering its kernel:ready hook from init():

serviceResolvableAtReady : true
engineBoundAtReady : false
setAtReady : resolved:"written-at-kernel-ready" <- reports success
rowsInDbAfterBoot : [] <- nothing persisted
engineBoundAfterBoot : true <- control, same boot
rowsInDbAfterControlWrite: [ { namespace: 'receipt_probe', key: 'last_run',
scope: 'global', organization_id: null, … } ]
auditRowsAfterControlWrite: [ { action: 'set', new_hash: 'fnv32:…' } ]

The control write moments later lands a real sys_setting row and a real sys_setting_audit row on the same connection, so the table existed throughout — the timing is the whole cause.

Why it is worth recording

This is the shape AGENTS.md's degradation-log-level rule exists for, one level up: a write that claims to persist does not, nothing looks broken from the outside, and there is no log line at any level — the write did not fail, it succeeded against the wrong store. Both audit ledgers are silent too (no engine ⇒ no sys_setting_audit row, no config_change row), so the usual evidence that a settings write happened is also absent.

Options

  • A. Make an engine-less write refuse or report, at least for a non-test kernel: the memory fallback is documented for "unit tests, bootstrap, control-plane mock", and it is the bootstrap reading that is doing the damage here.
  • B. Bind the engine earlier (the service already resolves objectql lazily elsewhere) so the window closes rather than being reported.
  • C. Leave the behaviour and document the window on SettingsService + the kernel:ready contract, so a caller knows the earliest safe moment is kernel:bootstrapped.
  • D. Accept as-is: no shipped caller writes there today.

No recommendation strong enough to pre-empt triage — the memory fallback is deliberate and has other users, so this is a decision about which of its readings it should keep.

Filed unassigned.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions