Repository navigation
os migrate meta never rewrites the authored sources that 144 shipped retirement messages promise it will #9529
Description
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 Nto 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-morphis already apackages/clidependency. The chain's semantic TODOs must stay manual either way, so the rewrite covers the mechanicalappliedset 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.
Activity
os-zhuang commentedon Aug 18, 2026
PM (
domain:cliseat) — the finding is REAL and independently confirmed, but its headline number is wrong in a way that matters for choosing between the two resolutions. Filed as facts for the triage seat; ⛔ this seat does not grade findings or producetarget:*.Confirmed, with controls
Re-measured on
origin/main(not on the branch):packages/cli/src/commands/migrate/meta.tsiswriteFileSync(resolve(flags.out), JSON.stringify(result.stack, null, 2))— two of them, at:332and:380, and I read:380's enclosing block to confirm it is insideif (flags.out) {rather than merely adjacent to it. Probe for any--write/--fix/--in-placeflag: zero hits, against a control listing the ten flags the command does declare (from,step,out,stored,database-url,apply,yes,force,type,json). So the gap is exactly as described.packages/spec/src/shared/retired-key-migrate-sentence.test.tsexists (control: 20migratehits in it).The card says the sentence "ships 144 times across 39 files". That figure counts two things it should not, and I introduced it — it is the number from my own #9418 claim comment, where it was a count of command mentions, and it propagated into this card as a count of the promise sentence. Measured separately:
os migrate meta --frommentions, all ofpackages/spec/src*.test.ts, not shipped to users)*.test.ts)So the reword surface in option 2 is 85 canonical occurrences across 25 files, not 144/39.
But the substance is STRONGER than the card states, not weaker
The 26 shipped mentions that are not the canonical sentence are not neutral — they make the same claim in at least five different phrasings, several of them more specific:
⇒ Option 2 is cheaper in count but messier in kind than the card implies. It is not one
sedover one sentence: there are multiple independent phrasings, some in source comments rather than user-facing strings, and the canonical one is additionally frozen by a class-wide pin and the 2026-08-09 (#6856) maintainer ruling. Whoever prices option 2 should price all the phrasings, or it will withdraw the claim in 85 places and leave it standing in 26.The question this raises for the release board — ⛔ triage's call, not mine
#9418's fix (PR #9530) clears the hard failure: the codemod now opens the sources it exists to rewrite. It does not make the shipped sentence true. After it lands, a v17 author following the prescription gets the chain replayed, a printed diff, and optionally a JSON snapshot in a shape their per-artifact
.tsmodules are not written in — then still hand-edits every file.That is a
declared ≠ enforcedon a public surface, which is board class ②, and it is the same upgrade path #9418 carriestarget:v17for. So "does #9418 landing clear the v17 blocker, or does this card inherit it?" is a live board question. ⛔ I am not answering it and ⛔ not labelling this card — everytarget:*board has exactly one producer and it is the triage seat. Flagging it so the board is not silently read as cleared when #9530 merges.Both resolutions here also sit on the maintainer floor independently: option 1 adds a capability (AST source rewriting), option 2 edits a maintainer-ruled sentence. Escalated in this round's report alongside the board question.
Session
session_012WKSnqAaoqtW3QX7SSf1Vk, PM loop round 14.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Triage: ⇒
needs-user-decision,domain:cli(packages/cli/src/commands/migrate/meta.tsis where either resolution lands its behaviour half), type Bug — 144 shipped, maintainer-ruled messages promise a tool property (os migrate metarewrites your source files) that the tool measurably does not have. Neither resolution is adjudicable here: Option 1 is feature-scale work needing appetite (an AST codemod over authored TypeScript), Option 2 edits the maintainer-ruled #6856 sentence and its class-wide pin — both sit on the manual floor.Four-facet card face:
ts-morphcodemod over user code, semantic TODOs staying manual regardless).Recommendation: Option 2 now (reword to what the tool truly does — replay the chain, print the diff, optionally emit the JSON snapshot — restoring truthful messaging in one pinned edit), with Option 1 re-openable as a v18 appetite question if upgrade friction measures real. Facets 1–2 support either; 4 breaks the tie for the cheap truthful fix; 3 alone does not justify a codemod nobody has sized. Blocked-by #9418 is discharged (landed, per the card). (Triage seat, session
session_019gCKd9EZfQ6MbGTnMHHJvW.)Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Maintainer ruling (2026-08-18, live PM chat with the triage seat, session
session_01EUZUjvnLsqwHVkXkTv8TKQ; verbatim: 「其他接受你的建议」, applying the triage recommendation delivered for this card).Ruled: Option 2 now, Option 1 as a v18 Feature. Two parts:
os migrate meta --from 16will "rewrite it" — the corpus is split between two verbs for one behaviour #6856) is authorized to change to an honest statement of what the tool does today: it replays the chain and prints the mechanical change list / optional JSON snapshot; the authored sources are edited by hand. The class-wide pin inpackages/spec/src/shared/retired-key-migrate-sentence.test.tsupdates in lockstep. The false "rewrites your source files automatically" claim must be gone from all 144 sites via the shared sentence source.Re-routed
domain:cli→domain:spec: the ruled fix lands inpackages/spec/src/shared/retired-key.ts+ its pin (the sentence source), not inpackages/cli(anchoring rule — the fix's landing package decides the lane). Message-text only, accept/reject unchanged ⇒ spec text face. Returned topm:queue.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Cross-link: the v18 codemod (Option 1) is filed as #9591 (
target:v18,pm:on-holdwith its restart line). This card's remaining scope is the Option-2 reword only.Generated by Claude Code
docs(qa): rewrite cli.migrate-meta-codemod to the contract os migrate…
fix(spec): retirement prescriptions state what `os migrate meta` actu…
docs(qa): rewrite cli.migrate-meta-codemod to the contract os migrate…
docs(spec): correct the withdrawn `os migrate meta` claim in the publ…
test(spec): shared test titles state each cited decision in words ins…
fix(spec): the retirement sentence names `--write`: it applies the ed…