Commit 25797a1
Fixes #21471
Clause-②: no
## What changed
`os secret orphans` is a report that promises to write nothing. It
composed the settings service with no crypto provider, so the service
built its default one. In a development posture with no env key and no
key file, that default creates a key file in the key home. The database
stayed untouched, but the key home did not, and the next
development-posture process on that host adopted the minted key. The
storage arm of the data-migration plugins (`os storage orphans`, also
report-only, and `os migrate files-to-references`) composed the service
the same way.
- **One composition.** New `packages/cli/src/utils/one-shot-settings.ts`
holds the idiom `os secret rewrap` already used, moved rather than
copied:
- `resolveExistingDataKey()` builds the provider over a key that already
exists, in the strict posture with the auto-key opt-in withheld, so it
never mints;
- `refusingCryptoProvider()` refuses every call and names why;
- `oneShotSettingsPlugin()` hands the settings service one of those two,
never the default.
- `secret/orphans.ts` and `utils/data-migration-plugins.ts` compose
through it. `secret/rewrap.ts` moves onto it, so there is one spelling,
not two.
- The two driver-contract tests boot the commands' own composition
again. Their "the command's own list" comments were stale for `rewrap`
since its provider change.
- Changeset: `@objectstack/cli` patch
(`.changeset/21471-one-shot-never-mints-key.md`).
## Census: every CLI composition of the settings service, by any
spelling
How the census was built:
- every non-test module under `packages/cli/src` whose comment-masked
code names `SettingsServicePlugin`, `LocalCryptoProvider` or its
deprecated alias (the enumeration pin's own detector; at `da6acc015d` it
named exactly the four source composers below plus the new helper);
- the commands that reach those modules through a call;
- the one composition a command reaches inside another package.
| Member | Reaches the settings service through | Disposition | Reading
|
|---|---|---|---|
| `os secret orphans` (report and `--delete`) |
`commands/secret/orphans.ts` | **closed here** | Composed the default.
The key file appeared in both modes, red at `da6acc015d`, green at
`bda27b5073` |
| `os storage orphans` | storage arm of
`utils/data-migration-plugins.ts` | **closed here** | Report-only. Red
at `da6acc015d`, green at `bda27b5073` |
| `os migrate files-to-references` (dry run and `--apply`) | the same
storage arm | **closed here** | Red at `da6acc015d`, green at
`bda27b5073`. It needs the real key when one exists, because the storage
plugin reads its stored credentials through the settings service. That
is why the composition reads an existing key rather than always refusing
|
| `os secret rewrap` | `commands/secret/rewrap.ts` | already correct;
**moved onto the helper** | Green at both commits |
| `os serve`, and `os dev` / `os start`, which spawn it | the capability
table's `settings` row; two default providers for secret fields | not
affected | This is the long-lived host. Persisting a key in a
development posture so restarts reuse it is its documented behaviour,
and a production posture refuses without a key. It is the one host the
enumeration pin allows, with that reason |
| `os migrate plan` / `os migrate apply` (`composeHostStack`) | only a
host config's own plugins | not affected | Host plugins are composed for
declarations only, with `start()` suppressed. The settings plugin builds
its default from a hook registered in `start()`. Both are green in the
family pin |
| the other `bootSchemaStack` callers (`meta resync`, `migrate`
`account-issuer` / `audit-metadata-bodies` / `duplicates` / `meta
--stored` / `multi-value-columns` / `recorded-by` / `resume` /
`summary-nulls` / `value-shapes`) | none | not affected | They compose
no settings service. Every mode is green in the family pin |
| `os verify` | `@objectstack/verify`'s boot harness, in another package
| **affected, not closed here** | See Out of scope below |
## Pins
- **`src/utils/one-shot-settings.pin.test.ts`** (unit tier)
- The enumeration: every module whose code names the plugin or the
provider is the helper or `commands/serve.ts`, failing by file name. A
self-check confirms the detector ignores prose and sees a renamed
destructure, the capability-table string and the alias.
- The helper's contract in a development posture with an empty key home.
Control: the default provider mints there.
- Cases: no key means none is resolved, none is minted, and the service
gets a refusing provider; an existing key file is read and never
rewritten; an env key is used and the home is untouched; a
set-but-unusable key is an answer, not a throw; the refusing provider
refuses all five contract members.
- **`src/utils/schema-migrate.one-shot-family.integration.test.ts`**
(integration tier, by its existing `bootSchemaStack` import). It gains a
third promise across the source-derived family: in a development posture
with an empty key home, every mode of every `bootSchemaStack` caller
leaves the home empty.
- Positive control: the read-only boot with `new SettingsServicePlugin({
registerRoutes: false })`, the composition the report used to pass,
leaves exactly one key file there.
- A new caller is already forced into the table by the file's first
case.
- New cases ran at about 0.1 to 0.2 s each locally under the file's
existing 120 s per-case timeout.
## Verification (head `bda27b5073`; red leg at `da6acc015d`)
- **Red first.** The pins were committed before the fix (`da6acc015d`)
and run against the unfixed commands:
- the enumeration pin failed, naming `commands/secret/orphans.ts`,
`commands/secret/rewrap.ts` and `utils/data-migration-plugins.ts` (7 of
8 tests passed, the control included);
- the family key-home cases failed 5 and passed 23, the control
included. The 5 failures were `migrate files-to-references`, `secret
orphans`, `storage orphans`, `migrate files-to-references --apply` and
`secret orphans --delete`, each as "key material was created in the key
home", with the key file present.
- **Green at `bda27b5073`.**
- `vitest run src/utils/one-shot-settings.pin.test.ts`: 8 / 8 passed.
- `vitest run --project integration` over the family file and both
driver-contract files: 3 files, 85 / 85 passed.
- The other tests that reach the changed modules: unit, 8 files, 61
passed; integration (`orphans.guards`, `rewrap.guards`, `summary-nulls`,
`sys-secret-rewrap`), 4 files, 37 passed.
- `pnpm --filter @objectstack/cli typecheck` (tsc plus
`check:test-typecheck`): exit 0, with no new test-typecheck debt.
- **Public door.** The built CLI's `os secret orphans --json`, in a
development posture with an empty key home, left the home empty.
- **`pnpm lint`** (`eslint . --no-inline-config`, the whole repo): exit
0 at `bda27b5073`, not narrowed.
- **Gates.** `dispatch-gates.mjs --ran` with no paths: 64 derived, 64
run, every one exit 0, and 0 NOT MEASURED (a derived zero, from recorded
exit codes). Four gates first refused on a missing build (exit 3,
nothing measured). After the prerequisite builds they were re-run to
exit 0: `check:dual-build-cjs-loads`, `check:i18n`,
`check:i18n-coverage` and `check:i18n-walk-parity`.
- Not merged with `origin/main`. It is three commits ahead, and none of
them touches these files. The derivation's stale-tree note names
`scripts/engine-double-contract.pinned.json`, which this diff does not
touch.
## Acceptance notes
- **Visible difference.** On a host whose key lives only in the key
file, these commands now print the strict posture's one-line note on
stderr, naming the persisted key's location, as `os secret rewrap`
already did. stdout and `--json` are unchanged.
- **Unreadable stored values.** With no key, a stored settings value
that cannot be opened reads as `null` with a warning. A freshly minted
key produced the same, because it can open nothing stored.
- **The enumeration pin's reach** is `packages/cli/src`. A composition
inside another package that a command calls into names nothing there;
the pin header says so, and the census lists the one that exists.
## Out of scope (reported to the seat, not filed here)
- **`os verify`** reaches `@objectstack/verify`'s boot harness. The
harness composes the settings plugin with no provider and also sets a
default local provider on the engine for secret fields.
- Measured through the built CLI on `examples/app-todo`, in a
development posture with an empty key home: after the run, the key home
held a key file.
- It is not closed here for two reasons. The composer is outside
`packages/cli`. And it needs a different provider shape: the harness
seals and opens secret fields against an in-memory database, so a
read-or-refuse provider would break `os verify` on a keyless host where
an ephemeral in-process key would not.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ee8383 commit 25797a1
9 files changed
Lines changed: 414 additions & 49 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
Lines changed: 4 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
| 54 | + | |
54 | 55 | | |
55 | 56 | | |
| 57 | + | |
56 | 58 | | |
57 | 59 | | |
58 | 60 | | |
| |||
137 | 139 | | |
138 | 140 | | |
139 | 141 | | |
140 | | - | |
| 142 | + | |
141 | 143 | | |
142 | 144 | | |
143 | 145 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| |||
196 | 197 | | |
197 | 198 | | |
198 | 199 | | |
199 | | - | |
200 | | - | |
| 200 | + | |
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
| |||
212 | 212 | | |
213 | 213 | | |
214 | 214 | | |
215 | | - | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
216 | 220 | | |
217 | 221 | | |
218 | 222 | | |
| |||
Lines changed: 6 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
| 34 | + | |
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
| |||
102 | 103 | | |
103 | 104 | | |
104 | 105 | | |
105 | | - | |
106 | | - | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
107 | 110 | | |
108 | 111 | | |
109 | 112 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
23 | | - | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
62 | 64 | | |
63 | 65 | | |
64 | 66 | | |
| |||
140 | 142 | | |
141 | 143 | | |
142 | 144 | | |
143 | | - | |
144 | | - | |
| 145 | + | |
145 | 146 | | |
146 | 147 | | |
147 | 148 | | |
148 | 149 | | |
149 | 150 | | |
150 | 151 | | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
| 152 | + | |
| 153 | + | |
161 | 154 | | |
162 | 155 | | |
163 | 156 | | |
| |||
175 | 168 | | |
176 | 169 | | |
177 | 170 | | |
178 | | - | |
179 | | - | |
180 | | - | |
181 | | - | |
| 171 | + | |
182 | 172 | | |
183 | 173 | | |
184 | 174 | | |
| |||
322 | 312 | | |
323 | 313 | | |
324 | 314 | | |
325 | | - | |
326 | | - | |
327 | | - | |
328 | | - | |
329 | | - | |
330 | | - | |
331 | | - | |
332 | | - | |
333 | | - | |
334 | | - | |
335 | | - | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | 315 | | |
344 | 316 | | |
345 | 317 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
21 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
22 | 26 | | |
23 | 27 | | |
24 | 28 | | |
| |||
61 | 65 | | |
62 | 66 | | |
63 | 67 | | |
64 | | - | |
65 | | - | |
| 68 | + | |
66 | 69 | | |
67 | 70 | | |
68 | 71 | | |
| |||
0 commit comments