Phase 7 — Paradise Road: migrate + retire the legacy edge
23 JUL AT 10:54 PM

Phase 7 — Paradise Road: migrate + retire the legacy edge

0 LOVES 2 VIEWS
The race that retires the old champ: an idempotent backfill (21 posts → 21 blocks / 138 items, VERIFY OK on dogfood), then the Post→Tags edge removed — compile-proof retirement plus a gated raw-SQL DROP of tag_posts. Goal 3 done. Shipped 2026-06-11.

Paradise Road is where the race is run and the old champ finally loses. Phase 7 retires the legacy Post → Tags edge — the whole point of the block was to replace it. But you cannot just delete it: real tags live in production. So the phase is a sequenced migration — backfill first, verify, then retire — and the order is load-bearing because the migration tool itself reads the very edge it is about to remove.

End state: the tag block, through its TagBlockItems, is the sole owner of post↔tag. The Post.tags M2M and its physical tag_posts table are gone. Goal 3, done.

The run order
Five steps in strict sequence — the migration reads the edge it later removes, so backfill and verify must run before codegen drops it and the raw-SQL DROP lands.
1
1 · Backfill (edge present)
Ship and run cmd/migrate-tags-to-blocks --commit. For each post with legacy tags and no tag block, it creates one tag block whose ordered TagBlockItems are that post's tags — reusing the same atomic CreateTagBlock the Studio uses. The edge must still exist here: the tool reads post.QueryTags().
Idempotent — skips any post that already has a tag block, so re-runs are safe.
2
2 · Verify (edge present)
Run cmd/migrate-tags-to-blocks --verify. It asserts that zero posts have legacy tags but no tag block — proof the backfill is complete before anything is dropped. On dogfood this returned VERIFY OK across 21 posts / 138 items; dev had 0 to migrate.
The gate: never drop the table until this is green.
3
3 · Remove the edges (codegen)
Delete the Post→Tags / Tag→posts edges from the schema and run go generate ./ent. Now QueryTags() and AddTags() no longer compile — which is the retirement proof: if anything still read the edge, the build would fail. Nothing did.
Compile failure is the feature here — it certifies zero remaining readers.
4
4 · DROP the table (post-codegen)
Ent's additive SQLite migrate never drops a table, so the physical tag_posts lingers after codegen. An explicit raw-SQL DROP TABLE tag_posts — gated on step 2's green verify — removes it for good, on dev and dogfood.
This is the one destructive command in the program; it runs only after verify passed.
5
5 · Clean up (cleanup)
Remove the studio tag_ids plumbing, delete posts_tags_test.go, and trim the legacy fixture in tag_browse_test.go. Keep seedTagGroupAndTag (used by guide tests) and GetTree (the block picker reuses it). Steps 1–2 shipped in one commit, steps 3–5 in a second.
Retire what you replace as you go — never leave a vague later.

As-built — two commits, VERIFY OK

  • The tool. cmd/migrate-tags-to-blocks mirrors an existing keystore-expiry migration: idempotent (skip posts that already have a tag block), per-site, with a deterministic per-item sort_order derived from the unordered M2M (by tag sort_order then name), and one block per Post — not per PostI18n, since tags hang off the Post. Its integration test self-seeds tag_posts, because no SQL seed creates them.
  • The operator-present run. On dogfood: --commit then --verify21 posts → 21 blocks / 138 items, VERIFY OK. Dev had 0 to migrate. Then codegen dropped the edge, and a raw-SQL DROP TABLE tag_posts removed the physical table on both.
  • Kept on purpose: seedTagGroupAndTag (guide tests use it) and GetTree (the block picker reuses it) — removing the legacy picker must not remove the tree loader.
  • Smoked after: kontent group-only chips and /tags/{slug} on dev and dogfood; a Studio save reported success with no tag loss.
American Graffiti — Tags as a Content Block (master)
American Graffiti — Tags as a Content Block (master)
Jul 23, 2026 American Graffiti
← Back to American Graffiti