Commit 59ed2e9
* feat(service-settings): report-only sys_secret orphan classifier (#8103)
Lands the non-destructive half of #8103: an operator-facing answer to "which
sys_secret rows are orphaned, and why", with no deletion and no migration. The
sweep's vehicle stays an open maintainer decision.
Re-measuring #8063's three reachability facts falsified the middle one:
sys_setting.value_enc is NOT the only column holding a sys_secret handle. The
store has three producers (SettingsService, the engine's secret-field channel
storing `secret:<id>` on arbitrary business rows, and the datasource credential
binder storing `sys_secret:<id>` at external.credentialsRef), and the engine's
holders are not statically enumerable. So the classifier reports a row it cannot
attribute as `unattributable` rather than `orphaned` — the guard that stands
between a future sweep and a live datasource credential.
Also pins the two directional guards the card names: a rotateKey() re-wrap keeps
the handle stable and reads as in-force (rotation metadata never decides a
verdict), and a legacy inline value_enc contributes no handle while flagging its
(namespace, key) siblings.
Tests drive a real ObjectQL over the real SysSetting/SysSecret schemas: the
orphan-generating mechanism is reproduced rather than cited, and the fixture
carries all four classes so the pins are not vacuous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARidKDYSCD56LaygrvDPnk
* fix(service-settings): write the refKey NUL separator as an escape, not a raw byte
check:nul-bytes caught a raw 0x00 in the composite-key separator -- the exact
accident the gate documents (an editing tool materialising an escape while the
author writes about it). The backslash-u-0000 spelling is byte-identical at run
time. Also documents why NUL rather than a printable separator: no namespace or
specifier key can contain one, so no two distinct pairs alias into one
composite -- and an aliased pair here would silently change a row's verdict.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARidKDYSCD56LaygrvDPnk
* test(service-settings): route the context-dropping engine double through assertEngineUpdateDispatch
check:engine-double-contract (convention-scoped, so dispatch-gates.mjs cannot
derive it) flagged the #8103 harness's SettingsEngine double as declaring an
update() that does not route through the shared dispatch predicate.
Took the pin rather than a baseline entry: the predicate reads only where /
multi / data.id and never looks at context, so it is orthogonal to the defect
this double reproduces (dropping context so the engine strips the read-only
value_enc). The double stays loose in exactly one dimension and is now
conformant in the one that drifts silently -- by-id vs multi.
multi:true is passed unconditionally instead of re-deciding the branch, because
that IS the settings adapter's contract: a scalar where.id outranks multi in
the shared predicate, so one call reproduces both of wrapEngineAsSettingsEngine's
branches without mirroring the guard.
Imported from @objectstack/objectql (already a devDependency, already aliased to
source by vitest.config.ts) rather than @objectstack/metadata-core: the cycle
rule prefers metadata-core only when objectql depends on the package being
pinned, and objectql does not depend on service-settings.
No verdict logic changed; settings-service.ts and crypto-adapter.ts untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARidKDYSCD56LaygrvDPnk
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent aca68eb commit 59ed2e9
4 files changed
Lines changed: 1088 additions & 0 deletions
File tree
- .changeset
- packages/services/service-settings/src
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
69 | 88 | | |
70 | 89 | | |
71 | 90 | | |
| |||
0 commit comments