From 41c8804bd75df2d5b9a7da0326cd6bd32cc0569d Mon Sep 17 00:00:00 2001 From: Ravi Teja KNTS Date: Sat, 10 Oct 2026 16:07:46 +0530 Subject: [PATCH] Archive and drop the retired page content columns Release 2 of plan step 2e. Release 1 (72f5bec7) removed every reader and made the columns optional, so nothing reads them now. This copies every value into stack127_retired_page_content, then drops the columns. It's idempotent and was applied and verified in development. Also marks steps 2e and 3 done in the plan. Co-Authored-By: Claude Opus 5.5 --- dev-space/charts-and-content-in-code-plan.md | 4 +- ...1010063116_retire_page_content_columns.sql | 104 ++++++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 supabase/migrations/20261010063116_retire_page_content_columns.sql diff --git a/dev-space/charts-and-content-in-code-plan.md b/dev-space/charts-and-content-in-code-plan.md index f52b58a0..574ae248 100644 --- a/dev-space/charts-and-content-in-code-plan.md +++ b/dev-space/charts-and-content-in-code-plan.md @@ -16,8 +16,8 @@ Work through the steps in order. Step 0 and Part 1 ship together in pull request | 2b | Pilot: MDX setup, export script, two pages | Claude Opus worker, Sol for the script, Sol review | #36 | Shipped in `ce282243`: `/cac-payback-calculator` and `/epp-status-codes` | | 2c | Docs and skills for content in code | Opus | #36 | Shipped in `ce282243` | | 2d | Content pass: rewrite and move every published tool, directory, child, and catalog page | Claude Opus thread in its own worktree, Sol for scripts and review | #39 | Done 2026-10-10. Released in 84043484; titles, search text, and cards published to production in 25 batches, all verified | -| 2e | Cleanup and retire database content columns | Sol | Final PR | Not started | -| 3 | Production release | Coordinator | Release | Not started | +| 2e | Cleanup and retire database content columns | Sol, Opus and Luna workers, Sol review | #42, #43 | Done 2026-10-10. #42 (released in `72f5bec7`) removed every database content reader, retired the sync commands, and made the columns optional. #43 archives the columns into `stack127_retired_page_content` and drops them | +| 3 | Production release | Coordinator | #42, #43 | Done 2026-10-10. Both releases verified on stack127.com; shortcut records, launch data, the DNS lookup version, and three shortcut page titles published | ## Decisions diff --git a/supabase/migrations/20261010063116_retire_page_content_columns.sql b/supabase/migrations/20261010063116_retire_page_content_columns.sql new file mode 100644 index 00000000..0b18c392 --- /dev/null +++ b/supabase/migrations/20261010063116_retire_page_content_columns.sql @@ -0,0 +1,104 @@ +begin; +set local lock_timeout = '10s'; + +-- Block writes until the archive and column drops commit together. +lock table public.stack127_tool_pages, public.stack127_directory_pages, + public.stack127_catalog_pages in access exclusive mode; + +create table if not exists public.stack127_retired_page_content ( + page_id bigint not null, + table_name text not null check (table_name in ( + 'stack127_tool_pages', 'stack127_directory_pages', 'stack127_catalog_pages' + )), + column_name text not null check (column_name in ('intro_markdown', 'usage_markdown', 'content_markdown')), + value text, + archived_at timestamptz not null default now(), + primary key (page_id, table_name, column_name) +); +comment on table public.stack127_retired_page_content is + 'Original page words retired when tools, directories and catalogs moved to route-owned MDX. No foreign key so archived words survive page deletion.'; +alter table public.stack127_retired_page_content enable row level security; +revoke all on public.stack127_retired_page_content from public, anon, authenticated; +grant select on public.stack127_retired_page_content to service_role; + +create or replace function public.touch_stack127_tool_page_public_date() +returns trigger language plpgsql set search_path = pg_catalog as $$ +begin + if (new.title, new.tool_version) is distinct from (old.title, old.tool_version) then + perform public.stack127_touch_page( + (select slug from public.stack127_pages where id = new.page_id), 'tool_content' + ); + elsif (new.card_name, new.card_description, new.card_icon_key, new.card_art_key) + is distinct from (old.card_name, old.card_description, old.card_icon_key, old.card_art_key) then + update public.stack127_pages set row_updated_at = now() where id = new.page_id; + end if; + return new; +end; +$$; + +-- PL/pgSQL trigger bodies do not create column dependencies. Refuse to drop +-- columns if another installed function still refers to them. +do $$ +declare remaining text; +begin + select string_agg(p.oid::regprocedure::text, ', ') into remaining + from pg_proc p join pg_namespace n on n.oid = p.pronamespace + where n.nspname = 'public' and p.prokind = 'f' + and p.prosrc ~ '\m(intro_markdown|usage_markdown|content_markdown)\M' + and (p.prosrc ~ '\m(stack127_tool_pages|stack127_directory_pages|stack127_catalog_pages)\M' + or exists (select 1 from pg_trigger t where t.tgfoid = p.oid and t.tgrelid in ( + 'public.stack127_tool_pages'::regclass, 'public.stack127_directory_pages'::regclass, + 'public.stack127_catalog_pages'::regclass + ))); + if remaining is not null then + raise exception 'Retired page columns still have function readers: %', remaining; + end if; +end; +$$; + +do $$ +declare field record; +declare mismatches bigint; +begin + for field in + select table_name, column_name from information_schema.columns + where table_schema = 'public' + and table_name in ('stack127_tool_pages', 'stack127_directory_pages', 'stack127_catalog_pages') + and column_name in ('intro_markdown', 'usage_markdown', 'content_markdown') + order by table_name, column_name + loop + execute format( + 'insert into public.stack127_retired_page_content(page_id,table_name,column_name,value) + select page_id,%L,%L,%I from public.%I + on conflict(page_id,table_name,column_name) do nothing', + field.table_name, field.column_name, field.column_name, field.table_name + ); + execute format( + 'select count(*) from public.%I live + left join public.stack127_retired_page_content archive + on archive.page_id=live.page_id and archive.table_name=%L and archive.column_name=%L + where archive.page_id is null or archive.value is distinct from live.%I', + field.table_name, field.table_name, field.column_name, field.column_name + ) into mismatches; + if mismatches <> 0 then + raise exception 'Archive verification failed for %.%: % rows', field.table_name, field.column_name, mismatches; + end if; + end loop; + + -- Without CASCADE, an unexpected view or other dependent object aborts the + -- whole transaction. Column checks retire with their columns. + alter table public.stack127_tool_pages + drop column if exists intro_markdown, + drop column if exists usage_markdown, + drop column if exists content_markdown; + alter table public.stack127_directory_pages + drop column if exists intro_markdown, + drop column if exists content_markdown; + alter table public.stack127_catalog_pages + drop column if exists intro_markdown, + drop column if exists content_markdown; +end; +$$; + +notify pgrst, 'reload schema'; +commit;