Commit 4a6ef3e
feat(metadata): a raw-document vocabulary rewriter, driven by the retirement map
Plan: docs/superpowers/plans/2026-08-21-meta-upgrade-vocabulary-rewriter.md. The engine
behind `meta upgrade` (CLI wiring lands next). Adds `rewrite` to the retirement map and
`rewriteDocument()`, which fixes what can be fixed mechanically and REFUSES the rest.
ONE MAP, NOT TWO. The loader already reads RETIRED_VOCABULARY to explain a failure; the
rewriter reads the same entries to fix it. So the message and the fix cannot drift — the
error says "use @mutability" and the tool writes @mutability for the same reason. A
parameterised `--from X --to Y` would have let those diverge and made the adopter supply the
mapping they came here to be told.
IT CANNOT USE THE LOADER, and that shapes the whole design. Once an attribute is
deregistered, metadata carrying it FAILS THE LOAD — that is what a retirement is. So
load → transform → canonical-serialize is impossible: the input does not load, and the
canonical serializer needs a loaded model. This works on RAW TEXT, which is also the only
reason an upgrade path exists: the adopter installs the new CLI and runs this against
metadata the new CLI refuses. A fixer needing a successful load is a chicken-and-egg with no
exit.
SURGICAL, NOT PARSE-AND-REPRINT. Adopters author JSONC with comments and meaningful key
order. A JSON.parse/stringify round-trip destroys both while reporting success, so every
edit is a span replacement against the original text, collected and applied right-to-left in
one pass — rewriting incrementally would invalidate every later offset. A document with
nothing to change comes back BYTE-IDENTICAL, and that is pinned.
REFUSES WHAT IT CANNOT KNOW. `@status: abandoned` has no `rewrite`, so it is reported with
its guide and left alone. Deleting the node, retyping it, and fixing the residue it describes
are all defensible and only a human knows which; a wrong guess emits metadata that LOADS and
means something different, which is strictly worse than refusing because the adopter would
believe the migration finished. Refusals are reported ALONGSIDE the mechanical changes, so a
partial upgrade is visible rather than silent.
VALUE RETIREMENTS FIRE ONLY ON THE RETIRED VALUES. `@dbColumnType: uuid_array` goes;
`@dbColumnType: jsonb` on the same attribute is live vocabulary and is untouched — removing
it would silently change a column type. Type scoping likewise: `@unique` is retired on
identity.secondary and live on a field, so the rewriter takes an explicit type scope rather
than matching bare names.
A TEST FOUND A REAL DEAD END while I was writing it: the `@dbColumnType` array kinds had
neither a rewrite nor a migration guide, so an adopter hitting them was told the vocabulary
was retired and given nowhere to go. Now mechanically dropped, since the attribute only ever
restated array-ness that `isArray` already carries.
Verified on real output rather than substring assertions: two retired attrs dropped with no
dangling comma and no blank line, comment and key order intact, the judgment case refused
with its guide, line numbers correct. metadata 2463 pass / 0 fail, workspace typecheck 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent ef0e7f9 commit 4a6ef3e
6 files changed
Lines changed: 560 additions & 1 deletion
File tree
- docs/superpowers/plans
- server/typescript/packages/metadata
- src
- test
Lines changed: 82 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 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
287 | 287 | | |
288 | 288 | | |
289 | 289 | | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
Lines changed: 39 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
46 | 62 | | |
47 | 63 | | |
48 | 64 | | |
| |||
53 | 69 | | |
54 | 70 | | |
55 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
56 | 75 | | |
57 | 76 | | |
58 | 77 | | |
| |||
65 | 84 | | |
66 | 85 | | |
67 | 86 | | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
68 | 90 | | |
69 | 91 | | |
70 | 92 | | |
71 | 93 | | |
72 | 94 | | |
73 | 95 | | |
74 | 96 | | |
| 97 | + | |
75 | 98 | | |
76 | 99 | | |
77 | 100 | | |
| |||
89 | 112 | | |
90 | 113 | | |
91 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
92 | 120 | | |
93 | 121 | | |
94 | 122 | | |
| |||
119 | 147 | | |
120 | 148 | | |
121 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
122 | 160 | | |
123 | 161 | | |
124 | 162 | | |
| |||
0 commit comments