Commit 417443e
fix(cli): a narrowed os migrate --apply records no deployment flag, and an unknown --object is refused (#21662)
Fixes #21644
Clause-②: no
A deployment-level flag is now written only by a full-scope run. `os
migrate value-shapes` and `os migrate files-to-references` narrowed by
`--object` apply their fixes, record no deployment flag, and say so. A
full-scope `--apply` records the flag exactly as before. Across the
family (`value-shapes`, `files-to-references`, `summary-nulls`,
`duplicates`), an `--object` name the deployment does not declare is
refused with `OBJECT_NOT_FOUND` before anything is read or written. The
refusal names the unknown name and the declared objects. This follows
triage ruling `5974774596`. `--apply --object` is not refused.
## Measured first (base `759dbe9ed3`)
### A1. The reach, at the public door (hypothesis confirmed)
A throwaway SQLite project held three objects, one of them
`os21644_site` with a `location` field. A served-shape boot seeded one
clean row per object. Then one off-shape value was written past the
write path: the site's `geo` stored as `{latitude, longitude}`. The
fresh-datastore attestation had recorded both ADR-0104 flags as verified
at birth, so the flag table was emptied first.
- `os migrate value-shapes --json` exited 1, with `gatePassed: false`
and `blocking: 1`.
- `os migrate value-shapes --object os21644_sitee --apply --yes --json`
(misspelled) exited **0**. It answered `gatePassed: true` with
`scannedObjects: []`, and the `adr-0104-value-shapes` row read
**verified** (`verified_at` set, `blocking: 0`).
### A2. The census, one row per command
| command | `--object` | `--apply` records a deployment flag | where it
is written (base) | unknown `--object` on base (measured) |
| --- | --- | --- | --- | --- |
| `value-shapes` | repeatable | `adr-0104-value-shapes` | the CLI:
`recordDataMigrationRun` at `value-shapes.ts:221` | exit 0,
`scannedObjects: []`; with `--apply`, the flag is recorded **verified**
|
| `files-to-references` | repeatable | `adr-0104-file-references`, then
the column step's `columns_moved_at` | the producer:
`runFilesToReferencesMigration` at
`files-to-references-migration.ts:120`; the column stamp is
`recordFileColumnMove` in the CLI (`files-to-references.ts:472`) | exit
0, both scans' `scannedObjects: []`; with `--apply`, the flag is
recorded **verified**, **and the column step moved
`os21644_product.image` and stamped `columns_moved_at`** |
| `summary-nulls` | repeatable | none (its header: "No deployment flag,
deliberately") | none | exit 0, `fields: []`, on a dry run and on
`--apply` |
| `duplicates` | single | none: no `--apply`, and it writes nothing |
none | exit 0, `scanned: []`, `filter: { object: 'os21644_sitee' }` |
A correctly spelled narrowed `files-to-references --apply` on base also
recorded the flag verified and moved the column. Every scan draws its
default candidates from the same registry: `options.objects ??
Object.keys(engine.getConfigs())` in `scanValueShapes`,
`backfillFileReferences`, `verifyFileReferences` and
`backfillSummaryNulls`, and `stack.allObjects()` for
`collectScanTargets`. Each keeps only the candidates it covers, which is
where an undeclared name was dropped.
### A3. The narrowed run
- **CLI-recorded flag (`value-shapes`).** The flag write is skipped on a
narrowed run, whether the run passes or fails. `--json` carries `flag:
null` and `filter: { objects }` (`null` on a full-scope run, the shape
`duplicates` already keeps). Both faces print one sentence: the run was
narrowed, no deployment flag was recorded, and the command that records
one is the same command without `--object`.
- **Producer-recorded flag (`files-to-references`).**
`runFilesToReferencesMigration` skips the write when it is given
`objects`. This is the declared `service-storage` path only. Its `flag`
result is `null` on a narrowed run. The CLI prints the same sentence and
carries `filter`.
- **The column step (`files-to-references`) does not run on a narrowed
run.** The census row above is why. The step retypes every single-value
media column in the database on the authority of the gate, and a
narrowed gate vouches only for the named objects. Its stamp also
requires a verified flag, which a narrowed run no longer records. Left
running, a narrowed `--apply` would move columns and then fail to record
the move. It now returns a stated skip, `narrowed_run`, and the human
face says why.
- **What "narrowed" means.** Any `--object` narrows, even a list that
names every declared object. The flag is earned by the one spelling that
means "every object", which is a run without `--object`. Treating a full
list as full scope would need a second definition of "the whole
deployment", checked against the registry of the moment, and that
registry changes with the composition between two runs. The operator
also gets one unambiguous prescription.
- **Deviation from the dispatch wording ("skips it when `objects` is
non-empty").** The producer treats **any** `objects` as narrowed, `[]`
included. A scan handed `[]` walks nothing (`[] ?? …` is `[]`). A
non-empty test would therefore record a verified flag over an empty
scan, the card's own defect at the producer's API. A unit pin holds
this.
- The prompts and closing lines that promised a flag on a narrowed run
now say it records none. ⛔ `--apply --object` is not refused, and the
full-scope write is unchanged.
### A4. Unknown `--object`
- **Checked against the registry the command's own boot resolved, before
the scan.** For `value-shapes`, `files-to-references` and
`summary-nulls` that registry is `Object.keys(engine.getConfigs())`. For
`duplicates` it is the names of `stack.allObjects()`. These are the same
sets the scans draw from, so the refusal and the scan judge one
population. There is no `packages/objectql` edit and no scanner edit.
- **The refusal is #21643's.** It is `objectNotFoundError` from
`@objectstack/core`: `code: 'OBJECT_NOT_FOUND'`, `status: 404`, and
`object` naming the first unknown name. Its message names every unknown
name and the declared objects, sorted. There is no new error code.
`value-shapes`, `files-to-references` and `summary-nulls` answer `{
error, code }`, as `unmapped-columns` does. `duplicates` keeps its own
error shape, `{ error: 'report_failed', detail, code }`: its catch now
passes `errorCodeFields` through.
- **The list is the declared set, not the covered subset.** Computing
the covered subset for `value-shapes` needs
`isScannableValueShapeField`, which `@objectstack/objectql` does not
export, and that package is fenced. The declared set is also exactly the
accept set. A declared object the command has nothing to check on is
accepted, because an empty answer about a real object is true. On the
fixture boot the list is 12 names, platform objects included.
- **Clause-②: no stands as the claim declared it.** A misspelled name
moves from exit 0 to exit 1, which is the ruled correction of a wrong
answer. Every declared name and `--apply --object` are still accepted.
## Changes
- `packages/cli/src/utils/migrate-object-scope.ts` (new):
`refuseUndeclaredObjects`, `isNarrowedRun` and `narrowedFlagNote`,
shared by the four commands.
- `packages/cli/src/commands/migrate/value-shapes.ts`: refuses an
unknown name, skips the flag on a narrowed run, adds `filter`, and
adjusts the narrowed prompt and closing lines.
- `packages/cli/src/commands/migrate/files-to-references.ts`: refuses an
unknown name, adds the `narrowed_run` column-step skip, adds `filter`,
and adjusts the narrowed prompt and closing lines.
- `packages/cli/src/commands/migrate/summary-nulls.ts` and
`duplicates.ts`: refuse an unknown name. `duplicates`' error document
carries the error's `code`.
-
`packages/services/service-storage/src/files-to-references-migration.ts`:
skips the flag write when given `objects`.
- `content/docs/deployment/cli.mdx`: one paragraph under "Data
migrations" (`--object` narrows, an unknown name is refused, only a
full-scope run records a flag), and the two `--object` example comments.
- `.changeset/21644-narrowed-apply-flag.md`: `@objectstack/cli` patch
and `@objectstack/service-storage` patch, `Clause-②: no`.
`packages/objectql`, `packages/platform-objects`, `packages/spec`, every
other `service-storage` path, and `content/docs/releases/` are
untouched.
## Pins
- **`object-scope.integration.test.ts`** spawns the CLI against SQLite,
one database copy per run, and is one enumeration over the census
(`FAMILY`).
- A narrowed `--apply` (`value-shapes`, `files-to-references`): exit 0,
`flag: null`, `filter: { objects }`, no flag row, and the note on stderr
naming the full-scope command.
- A full-scope `--apply`: the flag recorded verified, in the document
and in the row.
- `summary-nulls` and `duplicates`: no flag row, narrowed or not, as
before.
- A narrowed `--apply` after an earned flag leaves that row byte-equal.
- `files-to-references` narrowed: `columnMove: null` and
`columnsMovedAt: null`. Its full-scope control moves
`os21644_product.image` and stamps it.
- Unknown `--object`, on all four: exit 1 and `OBJECT_NOT_FOUND`, naming
the name and the declared objects. The one document is the refusal and
no report, no flag row is written, and the app rows are unchanged. The
human face exits 1 and names it.
- The measured repro. Control: the full-scope scan sees `blocking: 1`
and exits 1. The misspelled `--object --apply` exits 1 with
`OBJECT_NOT_FOUND`, and the flag stays unrecorded. Spelled right, the
narrowed run finds the value, exits 1, and still records no flag.
- **`migrate-object-scope.test.ts`** (unit): the envelope (`code`,
`status`, `object`), every unknown name named once, the declared list
sorted, the empty-registry message, the accepted cases, and what
`isNarrowedRun` treats as narrowed (an empty list and a full list both
narrow).
- **`files-to-references-migration.test.ts`** (`service-storage`, beside
the producer):
- a narrowed apply converts and records no flag;
- a narrowed failing apply records nothing;
- a narrowed apply leaves an earned flag row equal;
- `objects: []` records nothing.
## Reverse verification (implementation committed first; all three legs
re-run at the final head `fe988c20f0`)
Each leg ran through `node scripts/ablation-replace.mjs` in wrap mode,
under a script trap that restores from `HEAD`. The spawned CLI loads its
commands from `src/` through `bin/run-dev.js`. In the first round
`packages/cli/dist` did not exist. In the final round it held a build of
the unmutated source, and legs 1a and 2 still went red, which shows the
spawned CLI read the mutated `src/`. The `service-storage` unit pin
imports the producer from `src/`. Neither needed a rebuild.
- **Leg 1a, the narrowed-run skip in the CLI** (`value-shapes.ts`):
- The anchor `if (apply && !narrowed) {` became `if (apply) {`: anchor 1
to 0, replacement 0 to 1, blob `9f241dc2` to `d3a5c236`.
- **3 red, 17 green.** Red: the `value-shapes` narrowed pin, the
earned-flag-unchanged pin, and the spelled-right repro. Green: both
full-scope controls (the column-step control among them), the
`files-to-references` narrowed pin (its skip is the producer's), and
every unknown-name pin.
- Restored: blob `9f241dc2` equals `HEAD`, and `git diff HEAD` is empty.
- **Leg 1b, the narrowed-run skip in the producer**
(`files-to-references-migration.ts`):
- The same anchor and replacement: anchor 1 to 0, blob `1aa9fea2` to
`d4bb5002`.
- **4 red, 6 green.** Red: all four narrowed pins (passing, failing,
earned-flag-unchanged, and `objects: []`). Green: the six original pins,
the full-scope apply among them.
- Restored: blob `1aa9fea2` equals `HEAD`.
- **A first round is recorded here because one of its readings was
vacuous.** At `80e5eda6ea` this leg read 3 red and 7 green: the
earned-flag-unchanged pin stayed green under the mutation, because the
fake engine's rewrite landed in the same millisecond as the earned row.
The pin now dates the earned row in the past (`fe988c20f0`), and the
re-run is the reading above.
- **Leg 2, the unknown-name refusal** (`migrate-object-scope.ts`):
- The anchor `if (unknown.length === 0) return;` became `if
(unknown.length >= 0) return;`: anchor 1 to 0, replacement 0 to 1, blob
`b78a88b4` to `9286a990`.
- **Unit: 3 red, 3 green.** Red: the three refusal cases. Green: the
accepted cases and the two `isNarrowedRun` cases.
- **Integration: 10 red, 10 green.** Red: all eight unknown-name pins
(two per command, on all four), the human face, and the misspelled
repro. Green: every narrowed and full-scope pin, and the repro's
control.
- Restored: blob `b78a88b4` equals `HEAD`.
- In the first round, the `duplicates` "refused before anything was
read" pin stayed green under this mutation: it asserted only on a key
that report never carries. It now asserts that the one document is the
refusal, which reds on all four commands.
After all legs, `git diff HEAD` was empty and `git status --porcelain`
was clean.
## Local verification (final head `fe988c20f0`, on base `759dbe9ed3`)
`origin/main` was `759dbe9ed3` for the whole verification. Just before
this PR opened, it gained four commits, `f40bb3217f` to `1a230548cf`
(#21649, #21632, #21648, #21650). None of them touches this diff's paths
(`packages/spec`, `metadata-protocol`, `service-automation`, `lint`,
skills and docs references), so they were not merged in. CI runs on the
merge ref.
- **Builds.** The CLI's dependency closure (`turbo run build
--filter=@objectstack/cli^...`) gave VERDICT 0.
`@objectstack/service-storage` was rebuilt after the producer change
(exit 0), and `@objectstack/cli` was built (exit 0). A repo build for
the gate prerequisites gave VERDICT 0 (turbo: 72 tasks, 71 cached).
- **`@objectstack/cli` typecheck** (`tsc --noEmit` plus
`check:test-typecheck`): exit 0 at `80e5eda6ea`. No CLI file changed
after that commit.
- **`@objectstack/cli` unit project in full** at `80e5eda6ea`:
- 255 of 257 files passed, with 3742 tests passed and 29 skipped (the
two files below).
- The other two files,
`test/published-subpath-{console,hook-body}.pin.test.ts`, refused before
testing because `packages/cli` was not built (their own prerequisite
message). After the CLI build, both passed: 2 files, 29 tests.
- **`@objectstack/service-storage`**: typecheck exit 0, and the full
suite at `fe988c20f0` passed 41 files and 633 tests.
- **`os migrate` integration pins on built packages**, at `80e5eda6ea`:
- this PR's pin plus the absent-database roster: 2 files, 59 passed;
- the one-shot family plus `duplicates.integration`: 2 files, 79 passed.
- **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths) derived 97 commands at
`fe988c20f0`.
- All 97 ran there, and each exited 0.
- `--ran` with exit codes: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN.
- An earlier round at `80e5eda6ea` had three gates answer `PREREQUISITE
NOT MET` (exit 3): `check:skill-examples`, `check:dual-build-cjs-loads`
and `check:i18n-coverage`. They read packages outside the CLI closure.
The repo build cleared them.
- **Full `pnpm lint`** (`eslint . --no-inline-config` over the whole
repo): exit 0 at `fe988c20f0`, with nothing printed.
- **No exported symbol was renamed or moved,** so the liveness-ledger
anchor check had nothing to read.
## Acceptance notes
- **A narrowed run's counterexample is not recorded.** The ruling says a
narrowed `--apply` records no flag, so it records none even when it
finds a violation. Such a counterexample is deployment-level evidence,
since one off-shape value disproves "every value is on shape". The
operator still gets exit 1 and the findings, and the next full-scope run
closes the gate. This is an observation, not a filing. Carrier: none.
- **The declared list includes platform objects.** It is 12 names on the
fixture's lean boot, and a deployment that composes more plugins prints
more. A long list in an error message is the price of naming the exact
accept set. Carrier: none.
- **A narrowed `value-shapes --apply` still takes the plain
(DDL-performing) boot** even though it now writes nothing. That is
unchanged, and the boot paragraph in the docs still describes it
truthfully. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 98eb3b9 commit 417443e
11 files changed
Lines changed: 830 additions & 27 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- cli/src
- commands/migrate
- utils
- services/service-storage/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 | |
|---|---|---|---|
| |||
1132 | 1132 | | |
1133 | 1133 | | |
1134 | 1134 | | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
1135 | 1149 | | |
1136 | 1150 | | |
1137 | 1151 | | |
1138 | 1152 | | |
1139 | | - | |
| 1153 | + | |
1140 | 1154 | | |
1141 | 1155 | | |
1142 | 1156 | | |
| |||
1189 | 1203 | | |
1190 | 1204 | | |
1191 | 1205 | | |
1192 | | - | |
| 1206 | + | |
1193 | 1207 | | |
1194 | 1208 | | |
1195 | 1209 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
874 | 875 | | |
875 | 876 | | |
876 | 877 | | |
877 | | - | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
878 | 881 | | |
879 | 882 | | |
880 | 883 | | |
| |||
906 | 909 | | |
907 | 910 | | |
908 | 911 | | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
909 | 924 | | |
910 | 925 | | |
911 | 926 | | |
| |||
973 | 988 | | |
974 | 989 | | |
975 | 990 | | |
976 | | - | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
977 | 996 | | |
978 | 997 | | |
979 | 998 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
96 | 98 | | |
97 | 99 | | |
98 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
99 | 108 | | |
100 | 109 | | |
101 | 110 | | |
102 | 111 | | |
103 | | - | |
| 112 | + | |
| 113 | + | |
104 | 114 | | |
105 | 115 | | |
106 | 116 | | |
| |||
117 | 127 | | |
118 | 128 | | |
119 | 129 | | |
120 | | - | |
| 130 | + | |
| 131 | + | |
121 | 132 | | |
122 | 133 | | |
123 | 134 | | |
| |||
126 | 137 | | |
127 | 138 | | |
128 | 139 | | |
129 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
130 | 143 | | |
131 | 144 | | |
132 | 145 | | |
| |||
144 | 157 | | |
145 | 158 | | |
146 | 159 | | |
| 160 | + | |
147 | 161 | | |
148 | 162 | | |
149 | 163 | | |
| |||
197 | 211 | | |
198 | 212 | | |
199 | 213 | | |
200 | | - | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
201 | 220 | | |
202 | 221 | | |
203 | 222 | | |
| |||
248 | 267 | | |
249 | 268 | | |
250 | 269 | | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
251 | 277 | | |
252 | 278 | | |
253 | 279 | | |
| |||
293 | 319 | | |
294 | 320 | | |
295 | 321 | | |
| 322 | + | |
296 | 323 | | |
297 | 324 | | |
298 | 325 | | |
299 | 326 | | |
| 327 | + | |
300 | 328 | | |
301 | 329 | | |
302 | 330 | | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
303 | 334 | | |
304 | 335 | | |
305 | 336 | | |
| |||
341 | 372 | | |
342 | 373 | | |
343 | 374 | | |
344 | | - | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
345 | 392 | | |
346 | 393 | | |
347 | 394 | | |
| |||
398 | 445 | | |
399 | 446 | | |
400 | 447 | | |
| 448 | + | |
401 | 449 | | |
402 | 450 | | |
403 | | - | |
| 451 | + | |
404 | 452 | | |
405 | 453 | | |
406 | 454 | | |
407 | 455 | | |
408 | 456 | | |
409 | 457 | | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
410 | 465 | | |
411 | 466 | | |
412 | 467 | | |
| |||
503 | 558 | | |
504 | 559 | | |
505 | 560 | | |
506 | | - | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
507 | 567 | | |
508 | 568 | | |
509 | 569 | | |
| |||
0 commit comments