Repository navigation
Commit 6073bb9
fix(cli): os migrate meta prints the manual change that judges an applied edit beside that edit, marked review (#21025)
Fixes #20620
Clause-②: no
Direction 2 of triage's first grade on #20620 (comment `5888087153`):
「Where an applied conversion has a semantic sibling that judges it,
print the sibling beside the applied edit, marked **review**.」 It pairs
through the declared `SemanticMigration.conversionIds` only, never
through text mentions (release `5916560364`). Direction 1 (order) landed
in PR #20691. Direction 3 (write-back) is not taken by triage, so this
PR closes the card.
## What changes
`printMigrationReport` (`packages/cli/src/commands/migrate/meta.ts`),
the human face of `os migrate meta --from N`, now prints group 2 through
`printAppliedEdits`. Under each run of applied edits by one conversion,
it prints one review line for every semantic entry that names that
conversion in `conversionIds`:
```text
Applied 3 mechanical change(s):
• dashboards[0].refreshIntervalSeconds: refreshInterval → refreshIntervalSeconds (dashboard-refresh-interval-to-refresh-interval-seconds)
• flows[0].nodes[1].config.mode: mode unset (2 conditioned out-edges; every one whose condition held was taken) → inclusive (flow-decision-mode-inclusive-explicit)
• flows[1].nodes[1].config.mode: mode unset (2 conditioned out-edges; every one whose condition held was taken) → inclusive (flow-decision-mode-inclusive-explicit)
↳ review the 2 edits above against the manual change [protocol 18] flow.nodes[].config.mode (decision) — an OMITTED mode on a decision that branches on its out-edges and carries two or more conditioned ones → nothing, where the out-edge conditions partition (exactly one can hold for any record): […]
```
(The review line is cut at `[…]` here. It is printed in full.)
- **It is a copy, not a move.** The review line repeats the entry's
`[protocol N] surface → replacement` headline, which is the line group 3
prints for that entry. Group 3 still prints every entry, byte for byte,
under the same `N manual change(s) require your judgment:` count, so
ADR-0087 D3's "never silence" holds. The `why` and `verify` lines are
found in group 3 under the same headline.
- **Declared links only.** `judgesByConversion` reads
`todo.conversionIds`, which the chain copies from the entry. Nothing
matches prose.
- **Nothing else moves.** Every edit line is byte-identical and in chain
order, and the group 2 header count is unchanged. An edit that no entry
judges prints with nothing under it. `--json`, `--out`, the exit code
and the loader's stderr are untouched. `--stored` does not use this
printer.
## The dispatch's mechanism assumptions, measured
- **H1 holds.** `applyMetaMigrations` builds each todo as `{ ...s,
toMajor }` (`packages/spec/src/migrations/chain.ts`), so every todo
carries `conversionIds`. On `main` the registry declares exactly two
links, and both point into the same hop:
`flow-decision-edge-branching-first-match` (18) →
`flow-decision-mode-inclusive-explicit` (18), and
`time-default-zone-refused` (18) → `time-default-utc-suffix-dropped`
(18). No authored link crosses a hop yet. The join runs over the flat
`applied` / `todos` arrays, so a cross-hop link pairs by construction. A
pin proves it by adding a link from a protocol-18 entry to the
protocol-17 conversion `action-execute-to-target` on a real chain
result. Across steps, the 108 step conversion ids are unique (0
duplicates).
- **H2 holds, as a copy.** Group 3 keeps every entry, byte-identical,
with its header count unchanged. The review line sits in group 2 beside
the edits it judges. I did not change it to a move. A move would take
the entry out of group 3, and its count would then stop matching the
chain's `todos`.
- **H3: one review line per run, not one per edit.** A conversion's
edits are contiguous by construction, because the chain replays one
conversion at a time and its `apply` emits every edit before the next
conversion starts. So a run is "this conversion's edits in this hop".
HotCRM had 13 edits by `flow-decision-mode-inclusive-explicit`. One
review line per edit would repeat a paragraph-long `replacement` 13
times, which is the noise this card is about. No information is lost:
every edit line still names its `conversionId`, and the review line
counts the edits above it ("the 2 edits above", or "the edit above" for
a run of one). If a conversion's edits were ever split into two runs,
each run would get its own review line, so a review line never sits far
from its edits.
- **H4: `--stored` does not use `printMigrationReport`.** `runStored`
prints `formatStoredMigrationReport` from
`@objectstack/metadata-protocol`, so this PR does not touch it.
## Evidence: real stacks through `bin/run.js`
Two small authored stacks (`export default { … }` configs) were run with
`os migrate meta --from 17`, once on `origin/main` at `315888d660` and
once at this PR's head, `c6622657f2`:
- a decision stack: two flows, each with an edge-branched decision with
two conditioned out-edges and no `mode`, plus a dashboard
`refreshInterval`;
- a time stack: two `time` field defaults, `09:00Z` and
`17:30:00+00:00`, plus a dashboard `refreshInterval`.
| | decision stack | time stack |
|:---|:---|:---|
| stdout lines, before → after | 813 → 814 | 813 → 814 |
| stdout diff (timing and `--out` path normalised) | one added line, the
review line under the 2 `flow-decision-mode-inclusive-explicit` edits |
one added line, the review line under the 2
`time-default-utc-suffix-dropped` edits |
| group 3 entries / header count | 253 = 253 | 253 = 253 |
| `--json` apart from `duration` | identical | identical |
| `--out` snapshot | byte-identical | byte-identical |
| stderr | 0 bytes both runs | 0 bytes both runs |
| exit code (human / `--json`) | 0 / 0 both runs | 0 / 0 both runs |
Before this change, the decision edits printed on lines 13 and 14, and
the entry that judges them printed at line 351.
## Tests
The pins extend
`packages/cli/src/commands/migrate/meta.report-order.test.ts` rather
than a new file. That file already holds the printer's helpers (`run`,
`appliedLines`, `noticeLines`, `refusalLines`) and the SET pins the
pairing must not break, so the pairing pins reuse them, and every pin on
this printer stays in one file. The new `PAIR` block adds:
1. the judge prints once, directly under the run of edits it judges,
counting them, inside group 2;
2. a run of one says "the edit above";
3. an edit no entry judges prints exactly as before, with no review line
under it, and group 2's edit lines equal the chain's, in order;
4. a stack whose applied conversions have no link prints no review line
at all;
5. group 3 keeps every entry, the judge included, with the chain's count
and bytes;
6. every non-blank line is a header, a refusal, an edit, a notice, or a
review line that the declared links call for. The expected review lines
are computed without the printer's run logic;
7. every link the registry declares is paired, replayed over the linked
conversion's own `fixture.before`, so a link the spec lane adds later is
covered when it lands. Anti-vacuity: the two links named in the dispatch
are asserted present;
8. a link to a conversion that an earlier step replays pairs across
hops.
Measured at `c6622657f2`:
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2 src/commands/migrate/meta.report-order.test.ts`: 16
passed (8 existing, 8 new).
- The unit-tier neighbours (`doctor-deprecation-hint-commands`,
`meta.stored-flags`, `migrate-meta-strict-factories`, and this file): 4
files, 41 passed.
- `test/migrate-meta-default-range.test.ts`,
`test/migrate-meta-engine-guidance.test.ts` and
`test/migrate-meta.e2e.test.ts` are integration tier, so they are
declared to CI. Their only group 2 assertions are `toContain` checks,
which an added line cannot break.
- `pnpm --filter @objectstack/cli typecheck`: exit 0. The test file is
in the `tsc --noEmit` program (`--listFiles`, 1 hit).
**Ablation.** The fix was committed first (`19563f6c4e`). `node
scripts/ablation-replace.mjs` replaced `const judges =
judgesByConversion(result.todos);` with an empty map (anchor x1 → x0,
blob `868eef7e8983` → `542c67cdfca2`). The subject is imported
relatively from source, so no build was involved. Result: **5 red**
(pins 1, 2, 6, 7 and 8) and **11 green**. The 11 are the 8 order, count
and set pins and the 3 control pins: unjudged edit, no-link stack, and
group 3 intact. The restore was proven by blob `868eef7e8983` equal to
HEAD and an empty `git diff HEAD`.
## Gates
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`, with no paths, at `c6622657f2` (merge base `9b0de7de73`),
derived 62 commands. Each was run and its exit code recorded before any
pipe. All 62 exited 0. `--ran`: `62 derived, 62 run, 0 NOT-MEASURED, 0
UNRUN`. On the first round, `check:dual-build-cjs-loads` and
`check:i18n-coverage` answered PREREQUISITE NOT MET (exit 3, no dist).
They were re-run after `pnpm turbo run build
--filter=!@objectstack/docs`, and both are green. At reconciliation
time, `origin/main` was already 3 commits ahead, and one family file
(`scripts/check-platform-checklist.mjs`) had changed there. That family
reads `docs/qa/platform-checklist/`, which this diff does not touch.
**Lint, as a proven narrowing.** `pnpm lint` is a repo-wide scan owned
by CI. Run at `c6622657f2`:
- **Population.** ESLint's own config was asked through its API
(`isPathIgnored` and `calculateConfigForFile` against
`eslint.config.mjs`, inline config off). `meta.ts` is LINTED with 6
rules and `meta.report-order.test.ts` is LINTED with 5. The changeset is
in no config's population.
- **Count.** `--format json` over those 2 files: 2 results, 0 errors, 0
warnings.
- **Invariance.** `eslint.config.mjs` enables no type-aware linting:
none of its 7 `parserOptions` blocks sets `project`. Its only disk reads
at load are two baseline JSON files this diff does not touch. So no
untouched file's verdict can move.
## Acceptance notes
- `skills/objectstack-upgrade/SKILL.md:139-147` describes `Applied N
mechanical change(s)` as "one line per rewritten site". With this PR, a
judged run also carries a review line. The surface is Tier H governed,
and a one-line mention can ride with the group reorder that the previous
ACCEPT on this card already left for that surface's next queue entry.
- The `printMigrationReport` docblock cites "242 notices for protocol
18" as the measurement behind direction 1. Today's chain prints 253. It
is a dated measurement, not a claim this PR depends on, so it is left as
written.
- `docs/qa/platform-checklist/areas/cli.json` item
`cli.migrate-meta-codemod` locates planted spellings in the `Applied`
block by their edit line. The added review lines do not change that.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5d5e679 commit 6073bb9
3 files changed
Lines changed: 344 additions & 15 deletions
File tree
- .changeset
- packages/cli/src/commands/migrate
| 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 | + | |
Lines changed: 241 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
27 | 37 | | |
28 | 38 | | |
29 | 39 | | |
30 | 40 | | |
31 | 41 | | |
32 | 42 | | |
33 | | - | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
34 | 46 | | |
35 | 47 | | |
36 | 48 | | |
37 | 49 | | |
38 | | - | |
| 50 | + | |
39 | 51 | | |
40 | 52 | | |
| 53 | + | |
41 | 54 | | |
42 | 55 | | |
43 | 56 | | |
| 57 | + | |
44 | 58 | | |
45 | 59 | | |
46 | 60 | | |
| |||
102 | 116 | | |
103 | 117 | | |
104 | 118 | | |
105 | | - | |
106 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
107 | 130 | | |
108 | | - | |
| 131 | + | |
109 | 132 | | |
110 | 133 | | |
111 | 134 | | |
| |||
257 | 280 | | |
258 | 281 | | |
259 | 282 | | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
0 commit comments