Found while implementing #9418 (the load-refusal half). Filed unassigned for triage — deliberately out of that card's scope and not fixed there.
Blocked-by: #9418 — a source rewriter cannot be built on a load path that refuses to open those sources. That half has landed; this one is downstream of it and was pre-existing.
The gap
Every retiredKey() prescription whose surface an ADR-0087 conversion covers closes with a sentence whose wording is maintainer-ruled (2026-08-09, #6856) and pinned class-wide by packages/spec/src/shared/retired-key-migrate-sentence.test.ts:
Run os migrate meta --from N to rewrite existing sources automatically.
The rule that fixed that wording is explicit about what the sentence claims — quoting packages/spec/src/shared/retired-key.ts:
The sentence states a property of the TOOL — it rewrites your source files — never the fate of the key.
It ships 144 times across 39 files under packages/spec/src.
The command does not rewrite source files. Measured on origin/main @ 6ce8feb72, packages/cli/src/commands/migrate/meta.ts writes in exactly two places, both guarded by --out, both writing the same artifact:
347: if (flags.out) writeFileSync(resolve(flags.out), JSON.stringify(result.stack, null, 2));
395: writeFileSync(resolve(flags.out), JSON.stringify(result.stack, null, 2));
That is a JSON snapshot of the migrated stack at a path the operator names, and the flag's own description says exactly that: Write the migrated stack as a JSON snapshot to this path. There is no --write / --fix / in-place flag; objectstack.config.ts and the per-artifact modules under src/ are never touched. os lint --fix is not the missing half either — its own description reads Show what would be fixed (dry-run).
So an author who follows the prescription gets the chain replayed, a printed diff, and optionally a JSON document — and then still edits every .ts file by hand. For a project that authors metadata across per-artifact modules (defineView / defineAgent / defineFlow — the shape the example apps use), the JSON snapshot is not even in the shape the sources are written in.
Reproduction
With #9418's fix in place, in a project whose config carries a retired key:
os migrate meta --from 16 --out migrated.json
# exit 0, "Applied 2 mechanical change(s)", migrated.json written
git status # objectstack.config.ts: unmodified
The two honest resolutions — triage's call, not the finder's
- Implement the promise. An in-place rewrite of authored TypeScript means an AST codemod;
ts-morph is already a packages/cli dependency. The chain's semantic TODOs must stay manual either way, so the rewrite covers the mechanical applied set only. - Reword the promise. 144 messages across 39 files, plus the class-wide pin and the maintainer ruling that produced the current wording. Cheaper, but it withdraws the automatic-fix claim from the v17 upgrade path — which is the claim that makes the retirement audible.
Option 2 edits a maintainer-ruled sentence, so neither option is an implementer's scope call.
Found while implementing #9418 (the load-refusal half). Filed unassigned for triage — deliberately out of that card's scope and not fixed there.
Blocked-by: #9418— a source rewriter cannot be built on a load path that refuses to open those sources. That half has landed; this one is downstream of it and was pre-existing.The gap
Every
retiredKey()prescription whose surface an ADR-0087 conversion covers closes with a sentence whose wording is maintainer-ruled (2026-08-09, #6856) and pinned class-wide bypackages/spec/src/shared/retired-key-migrate-sentence.test.ts:The rule that fixed that wording is explicit about what the sentence claims — quoting
packages/spec/src/shared/retired-key.ts:It ships 144 times across 39 files under
packages/spec/src.The command does not rewrite source files. Measured on
origin/main@6ce8feb72,packages/cli/src/commands/migrate/meta.tswrites in exactly two places, both guarded by--out, both writing the same artifact:That is a JSON snapshot of the migrated stack at a path the operator names, and the flag's own description says exactly that:
Write the migrated stack as a JSON snapshot to this path.There is no--write/--fix/ in-place flag;objectstack.config.tsand the per-artifact modules undersrc/are never touched.os lint --fixis not the missing half either — its own description readsShow what would be fixed (dry-run).So an author who follows the prescription gets the chain replayed, a printed diff, and optionally a JSON document — and then still edits every
.tsfile by hand. For a project that authors metadata across per-artifact modules (defineView/defineAgent/defineFlow— the shape the example apps use), the JSON snapshot is not even in the shape the sources are written in.Reproduction
With #9418's fix in place, in a project whose config carries a retired key:
The two honest resolutions — triage's call, not the finder's
ts-morphis already apackages/clidependency. The chain's semantic TODOs must stay manual either way, so the rewrite covers the mechanicalappliedset only.Option 2 edits a maintainer-ruled sentence, so neither option is an implementer's scope call.