Found while migrating hotcrm onto the 17.4.0 line (hotcrm#1807, hotcrm PR #1814). Filed unassigned for triage.
What happens
@objectstack/spec@17.4.0's dashboard.refreshInterval tombstone tells the author, verbatim:
Rename the key to refreshIntervalSeconds; the value (seconds) is unchanged. Run os migrate meta --from 17 to list the mechanical edits for existing sources; apply them by hand.
Running exactly that command, on a stack that authors the retired key five times, lists nothing:
$ os migrate meta --from 17
✗ dashboards.0.refreshInterval: `dashboard.refreshInterval` was renamed to `refreshIntervalSeconds` … Run `os migrate meta --from 17` to list the mechanical edits …
… (five of these)
→ Replaying chain: protocol 17 → 17…
ℹ Chain: protocol 17 → 17 (this runtime implements protocol 17)
✓ Nothing to migrate — the metadata is already canonical for this range.
Exit code 0. The same command with an explicit --to 18 lists all five:
$ os migrate meta --from 17 --to 18
Applied 5 mechanical change(s):
• dashboards[0].refreshIntervalSeconds: refreshInterval → refreshIntervalSeconds (dashboard-refresh-interval-to-refresh-interval-seconds)
… (five)
Why
The conversion is registered with toMajor: 18. --to defaults to this runtime's protocol major, which is 17. So the default range 17→17 excludes the only conversion the tombstone is telling the reader to run the command for. The command is correct about its own range; the tombstone prescribes a range that cannot contain the conversion it belongs to.
Why it matters more than the one flag
The failure is silent and reads as success: ✓ Nothing to migrate — the metadata is already canonical for this range, exit 0, immediately under five refusals naming that exact command. A reader following the tombstone concludes the migration registry has no entry for their key and hand-writes the rename — which is what the ADR-0087 registry exists to stop. In hotcrm#1807 the acceptance criterion was literally "the rename was produced by os migrate meta, ⛔ not hand-written", and the prescribed invocation would have failed it.
This is not the tombstone-wording family already closed (#6914, #10831, #10418) — those are about whether the command exists and whether it rewrites sources. Here the command exists, does the right thing, and is invoked exactly as instructed, and still reports nothing.
Candidate fixes, not a prescription
- Have
retiredKey() render the command with the range that actually contains the conversion (--from 17 --to 18 for a toMajor: 18 entry), so each tombstone names an invocation that works.
- Or, when the requested range yields zero conversions but conversions exist for the authored surfaces just outside it, say so instead of
Nothing to migrate — naming the range that would list them.
(1) is the narrower change and fixes every tombstone at once; (2) also catches the reader who typed the range themselves.
Reproduction
Any 17.4.0 stack authoring dashboard.refreshInterval. Measured on @objectstack/cli/17.4.0 linux-x64 node-v22.22.2, runtime 17.0.0 (the protocol major).
Generated by Claude Code
Found while migrating
hotcrmonto the 17.4.0 line (hotcrm#1807, hotcrm PR #1814). Filed unassigned for triage.What happens
@objectstack/spec@17.4.0'sdashboard.refreshIntervaltombstone tells the author, verbatim:Running exactly that command, on a stack that authors the retired key five times, lists nothing:
Exit code 0. The same command with an explicit
--to 18lists all five:Why
The conversion is registered with
toMajor: 18.--todefaults to this runtime's protocol major, which is 17. So the default range 17→17 excludes the only conversion the tombstone is telling the reader to run the command for. The command is correct about its own range; the tombstone prescribes a range that cannot contain the conversion it belongs to.Why it matters more than the one flag
The failure is silent and reads as success:
✓ Nothing to migrate — the metadata is already canonical for this range, exit 0, immediately under five refusals naming that exact command. A reader following the tombstone concludes the migration registry has no entry for their key and hand-writes the rename — which is what the ADR-0087 registry exists to stop. In hotcrm#1807 the acceptance criterion was literally "the rename was produced byos migrate meta, ⛔ not hand-written", and the prescribed invocation would have failed it.This is not the tombstone-wording family already closed (#6914, #10831, #10418) — those are about whether the command exists and whether it rewrites sources. Here the command exists, does the right thing, and is invoked exactly as instructed, and still reports nothing.
Candidate fixes, not a prescription
retiredKey()render the command with the range that actually contains the conversion (--from 17 --to 18for atoMajor: 18entry), so each tombstone names an invocation that works.Nothing to migrate— naming the range that would list them.(1) is the narrower change and fixes every tombstone at once; (2) also catches the reader who typed the range themselves.
Reproduction
Any 17.4.0 stack authoring
dashboard.refreshInterval. Measured on@objectstack/cli/17.4.0 linux-x64 node-v22.22.2,runtime 17.0.0(the protocol major).Generated by Claude Code