Repository navigation
Commit 6b97a20
fix(spec): the 17 → 18 chain names the bare element:filter / element:form node it leaves standing (#17845)
Fixes #17594
**Clause-②: no** — an ADR-0087 D3 semantic TODO plus a disposition move.
No key is added to any published payload, no accept set moves, and the
refusal this TODO describes already exists at HEAD.
## What was wrong
`element:filter` and `element:form` were retired whole at element grain
(ADR-0049). The two ADR-0087 D2 conversions that carry the retirement —
`element-filter-removed` and `element-form-removed` — strip every
authorable key and **deliberately leave the bare component node**:
deleting an authored page node is a layout decision a mechanical
conversion must not make.
That residue was inert until both names joined
`RETIRED_PAGE_COMPONENT_TYPES` and `PageComponentSchema.type` began
refusing them **by name**. At that point deleting the node stopped being
optional and became a required step of the 17 → 18 chain — and nothing
in the chain said so.
## The defect, reproduced before the change
A stack carrying one node of each, driven through the real CLI
(`packages/cli/bin/run-dev.js migrate meta --from 17 --to 18`):
```
--json schemaValid: false applied: 9 todos: 115
human path "Migrated stack does not yet pass schema validation —
resolve the manual changes above, then run `os validate`."
step-18 todos naming the node human output (396 lines)
element:filter 0 0
element:form 0 0
ElementFilter 0 0
ElementForm 0 0
lit control, same corpus, same probe
element:number 3 todos 6 lines
element:record_picker 3 todos —
```
⇒ the zero is a reading, not a probe that could not reach. The nine
mechanical rewrites the same run attributed are all to
`element-filter-removed` / `element-form-removed`, so the run really did
visit both nodes and really did leave them standing.
## After
Same input, same command:
```
--json schemaValid: false applied: 9 todos: 116
todos whose `surface` names BOTH node types: 1
id element-filter-and-form-node-refused
toMajor 18
human path, line 150
⚠ [protocol 18] page.component.element:filter / page.component.element:form — the bare
component node itself, left standing by the `element-filter-removed` and
`element-form-removed` conversions after they strip its properties
→ Delete the component node. `element:filter` → … `userFilters` … `element:form` →
the object-bound `object-form` block …
```
It names the thing to delete rather than describing it. And the
prescription is checkable: with both nodes deleted by hand and nothing
put in their place, the same command reports `schemaValid: true` and the
human path prints `✓ Migrated stack is schema-valid`.
`schemaValid` stays `false` in the after-run **by design** — the node is
still there until a human deletes it. What changed is that the author is
now told which node, and what to put in its place.
## How the entry was added
Per `packages/spec/src/migrations/entries/README.md`: **one new file**,
named for its id, then the generator. ⛔ Nothing was typed between the
`os-generated` markers.
```
packages/spec/src/migrations/entries/semantic/18.element-filter-and-form-node-refused.ts (new)
pnpm --filter @objectstack/spec gen:migration-registry
→ ✓ wrote src/migrations/registry.ts (203 semantic, 168 retired-key, 178 retired-def)
→ +35 lines inside the `os-generated semantic:18` region, nothing else in the file moved
pnpm --filter @objectstack/spec check:migration-registry exit 0
→ build-migration-registry --self-test: ok
→ ✓ src/migrations/registry.ts is current (203 semantic, …)
pnpm --filter @objectstack/spec check:generated exit 0
→ ✓ All 15 generated artifacts are up to date.
```
`spec-changes.json` and `docs/protocol-upgrade-guide.md` are
**unchanged, correctly**: both project only up to `PROTOCOL_MAJOR` (17).
Lit control — the pre-existing step-18 entry
`element-number-filter-rule-array` has 0 hits in either file too, so the
empty diff is a property of the projection range and not of this entry.
## The ADR-0087 disposition move
`.changeset/15110-retired-element-node-refusal.md` — the changeset of
the PR that made the node refusable — moves from
```
not-required (already-registered element-filter-removed, element-form-removed)
```
to
```
registered element-filter-and-form-node-refused
```
The old disposition was gate-true and semantically thin: the two ids it
named registered the **key strips**, not the node deletion that the
refusal turned into a required step. The `registered` form the gate
parses carries ids only, so the old marker's rationale sentence does not
survive the move — it is superseded by the entry's own `reason` field,
which states the same thing at the place a consumer reads it.
`node scripts/check-adr-0087-registration.mjs --base origin/main` → exit
0 (and `--self-test` → exit 0).
## Ablation of the new pin
The pin is six cases in
`packages/spec/src/migrations/migrations.test.ts`. Mutation = delete the
entry file and re-run the generator; the fix was committed first, so the
restore point is a commit that exists.
```
HEAD blobs (restore target)
entry 48c9acb
registry b457da5
on-disk proof of the mutation (not an editor exit code)
entry file present after rm: no
occurrences of `element-filter-and-form-node-refused` in registry.ts: pre 1 → post 0
registry.ts blob after regeneration: 9b9914a
— byte-identical to the pre-change registry blob, so the generator is deterministic
and the whole diff to that file is this entry
RED Tests 5 failed | 119 passed (124)
× finds the entry (anti-vacuity …)
× a 17 → 18 run emits exactly one todo naming BOTH node types (ADR-0087 D3)
× prescribes DELETING the node, and names each element's replacement
× ⛔ does not prescribe an automatic delete …
× the acceptance criterion is checkable, and names `os validate` …
restore git checkout HEAD -- BOTH_PATHS
hashes read back EQUAL to the HEAD blobs above; `git diff HEAD` empty;
`git status --porcelain` empty
GREEN Tests 124 passed (124)
```
The sixth case — *the replay really does leave the bare nodes* — stays
green through the ablation on purpose: it pins the **conversions'**
behaviour, which this PR does not touch, so the block is not one
undifferentiated assertion. `trap RESTORE_FN on EXIT INT TERM` with
absolute paths throughout; the restore is proven by `git hash-object`,
never by an exit code.
No dist preflight: the pinned subject is `./registry.js` imported
relatively from inside the same package, so vitest resolves it to
`src/`. No `exports` hop, no `dist` on the resolution path.
## Changeset — measured, not assumed
Build first (both passes confirmed: `dist/.build-input-hash` and
`dist/.build-input-hash-dts` stamped at the same input hash,
`check-dts-emitted: 34/34`), then `npm pack --dry-run --json` over
`packages/spec` (2012 files):
| probe | packed files containing it |
|---|---|
| **target** — the entry id | **4** (`dist/index.js`, `dist/index.mjs`,
`dist/browser/index.js`, `dist/browser/index.mjs`) |
| **target** — the entry's `surface` prose | **4** (same) |
| **target** — `Delete the component node.` | **4** (same) |
| positive control — `element-number-filter-rule-array` | 10 |
| positive control — that entry's `reason` prose | 5 |
| negative control — test-only prose | **0** |
| negative control — the entry file's own `//` comment | **0** |
| negative control — changeset-only prose | **0** |
⇒ this round's text reaches a published `dist`. Changeset written:
`.changeset/17594-step18-element-node-todo.md`, `@objectstack/spec:
patch`.
The entry file's internal comment (the measurement provenance) lands in
**no** published byte: it sits above the `import`, so the generator
reads it as file scaffolding and it never enters `registry.ts`. The
`packages/spec` tsup-does-not-strip-comments trap therefore does not
apply here — measured, not reasoned.
## Verification
| what | result |
|---|---|
| `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` | 83 commands derived |
| those 83 | **83 exit 0** — two needed a re-run and are counted at
their real reading, see below |
| `dispatch-gates.mjs --ran` reconciliation | **83 derived, 83 run, 0
NOT-MEASURED (derived from recorded exit codes), 0 UNRUN** |
| `pnpm --filter @objectstack/spec exec vitest run --project local
src/migrations/migrations.test.ts` | 124 passed (118 before, +6) |
| `pnpm --filter @objectstack/spec test` (the **local** vitest project)
| **471 files / 13405 tests passed**, `VERDICT command-exit 0` |
| `pnpm --filter @objectstack/spec test:repo` (the **repo** project —
the corpus scanners) | **30 files / 520 tests passed**, `VERDICT
command-exit 0` |
| `pnpm --filter @objectstack/spec typecheck` | **exit 0** — includes
`check:test-typecheck`, which compiles the test layer under
`tsconfig.test.json`, so the new pin is type-checked rather than merely
executed |
| `pnpm --filter @objectstack/spec check:migration-registry` /
`check:generated` | exit 0 / exit 0 |
| `pnpm exec eslint . --no-inline-config` | exit 0 over **6647** files
(eslint's own config decides that population), 0 errors / 0 warnings, at
`bca74a4601` |
Two of the 83 first came back **NOT MEASURED**, never green:
- `pnpm check:dual-build-cjs-loads` → **exit 3**, `PREREQUISITE NOT MET`
(twelve packages had no `dist`). Built the closure (`turbo run build
--filter='./packages/*' --filter='./packages/*/*'`) and re-ran → exit 0.
- `pnpm check:type-check-debt` → **exit 143** (SIGTERM) on the first
pass, then **exit 3** for the same missing-closure reason. Re-ran after
the build → exit 0, 5 ledger entries re-measured, none above its
recorded number.
Every exit code above was captured before any pipe (`cmd > file 2>&1;
EXIT=$?`), and every locked run was read from the wrapper's own `VERDICT
command-exit` line, never a bare `$?`.
The turbo build is a shared cache across worktrees, so
`packages/spec/dist` was re-checked for this entry by occurrence count
after it (1 hit in `dist/index.js`, 1 in `dist/index.mjs`) rather than
assumed intact.
## Correction after the first CI run
The first head (`bca74a46`) went **red** on `Test Core`, and the failure
was this round's:
`src/shared/retired-key-migrate-sentence.test.ts` refused the new
entry's acceptance criterion.
That gate scans every string literal under `packages/spec/src` (and
`packages/lint/src`) for a
backticked `os migrate meta --from N` marker and requires the **house
tombstone sentence** at it —
*Run `os migrate meta --from N` to list the mechanical edits for
existing sources; apply them by
hand.* The acceptance criterion named the command that way to say the
upgrade is finished, so the
scanner read it as a tombstone prescription. The sentence was also split
across a string-concatenation
seam, which `reconstruct()` merges, so both halves were judged as one.
```
before … Re-running `os migrate meta --from 17` then reports the migrated stack
schema-valid instead of asking for the manual changes again
after … Replaying the same 17 → 18 chain over the edited source then reports the
migrated stack schema-valid — `schemaValid: true` in `--json`, and the run
closes with the schema-valid line rather than the manual-changes warning
```
Derived from the gate, not from other entries by eye, and then checked
against them: **no** file under
`migrations/entries/` spells the `--from N` marker. Nine name the bare
command or a flagged variant
(`--stored`); zero name `--from N`. The house form belongs to
`retiredKey()` guidance an author meets in
a parse error; a D3 `acceptanceCriteria` is the consumer's verify loop,
and it stays checkable — the
measurement above showed that deleting both nodes makes the same command
report `schemaValid: true`.
⛔ The gate was not weakened, skipped, or given a baseline entry. The
sentence moved.
Re-measured on head `95f4fb4b`:
```
pnpm --filter @objectstack/spec test:repo VERDICT command-exit 0 30 files / 520 tests passed
(CI's red was 1 failed | 29 passed, 2 failed | 518 passed)
pnpm --filter @objectstack/spec test VERDICT command-exit 0 471 files / 13405 tests passed
pnpm --filter @objectstack/spec check:generated exit 0
pnpm --filter @objectstack/spec check:migration-registry exit 0
```
Why the first round missed it: `packages/spec` declares **two** vitest
projects — `test` runs
`--project local`, `test:repo` runs `--project repo` — and the repo-wide
corpus scanners live in the
second. A green from `test` is not a reading about `test:repo`.
1 parent 2d34f32 commit 6b97a20
5 files changed
Lines changed: 233 additions & 1 deletion
File tree
- .changeset
- packages/spec/src/migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
76 | | - | |
| 76 | + | |
| 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 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
Lines changed: 63 additions & 0 deletions
| 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 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
211 | 211 | | |
212 | 212 | | |
213 | 213 | | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 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 | + | |
214 | 307 | | |
215 | 308 | | |
216 | 309 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7208 | 7208 | | |
7209 | 7209 | | |
7210 | 7210 | | |
| 7211 | + | |
| 7212 | + | |
| 7213 | + | |
| 7214 | + | |
| 7215 | + | |
| 7216 | + | |
| 7217 | + | |
| 7218 | + | |
| 7219 | + | |
| 7220 | + | |
| 7221 | + | |
| 7222 | + | |
| 7223 | + | |
| 7224 | + | |
| 7225 | + | |
| 7226 | + | |
| 7227 | + | |
| 7228 | + | |
| 7229 | + | |
| 7230 | + | |
| 7231 | + | |
| 7232 | + | |
| 7233 | + | |
| 7234 | + | |
| 7235 | + | |
| 7236 | + | |
| 7237 | + | |
| 7238 | + | |
| 7239 | + | |
| 7240 | + | |
| 7241 | + | |
| 7242 | + | |
| 7243 | + | |
| 7244 | + | |
| 7245 | + | |
| 7246 | + | |
7211 | 7247 | | |
7212 | 7248 | | |
7213 | 7249 | | |
| |||
0 commit comments