The gap register stopped being a paragraph in a report and became rows. Every upgrade PURE needs — declared by review or derived from a live signal — with an owner, an effort, the evidence that proves it is real, and the check that will prove it done. Reads mcp_upgrade_register(); deep-link any row by its id.
Two kinds of row. Declared — ruled by review, the gap register made durable. Derived — produced by a live probe, so it appears the day it becomes true and disappears the day it is fixed. A derived row nobody can close by hand is the point.
| Id | Upgrade | Area | Owner | Effort | Severity | Status |
|---|
—
The page ships reading a live RPC with a declared fallback. This is the table and read that replace the fallback — one register, two row sources, admin-editable.
-- THE UPGRADE REGISTER · Design → Code, 9/1/26 · declared rows + derived rows, one read
-- NOT a duplicate of pure_upgrade_audit: that rail is page-ENGINE-ERA migration (v4.8 → engine
-- version, per-route). This is platform upgrades. The era rail becomes ONE derived source below.
create table if not exists pure_upgrade_register (
id text primary key, -- U-01 … stable, deep-linkable, quotable in tickets
title text not null,
why text not null, -- the consequence if it stays undone
area text not null, -- security|data|delivery|governance|ux|comms|scale
owner_lane text not null, -- Code | Design | Mike | Poppy
effort text not null, -- S | M | L | gated
severity text not null, -- critical | high | medium | low
status text not null default 'open', -- open | in_flight | blocked | done
evidence text, -- the query or probe that proves it real
done_check text, -- the observable that closes it
link text, ticket_id text, sort int default 100,
updated_at timestamptz default now(), updated_by text
);
revoke all on function public.mcp_upgrade_register() from public, anon; -- Gate 0, at birth
create or replace function mcp_upgrade_register() returns jsonb
language sql security definer set search_path=public as $$
select jsonb_build_object('ok',true,'as_of',now(),'declared',
coalesce((select jsonb_agg(to_jsonb(r) order by r.severity, r.sort) from pure_upgrade_register r
where r.status <> 'done'),'[]'::jsonb),
'derived', jsonb_build_array(
-- each derived row is a live count; it self-closes when the count reaches its target
jsonb_build_object('id','D-flood','title','Board intake flood not dedupe-guarded',
'area','data','owner_lane','Code','severity','critical',
'n',(select count(*) from pure_board where title ilike 'Bug: sd%'),'target',0),
jsonb_build_object('id','D-spine','title','Pages in no spine (invisible, not excluded)',
'area','governance','owner_lane','Code','severity','high',
'n',(select count(*) from pure_pages p where coalesce(p.enabled,true) and not exists
(select 1 from pure_spine_pages s where regexp_replace(s.page,'^/','')=regexp_replace(p.path,'^/',''))),'target',0),
jsonb_build_object('id','D-era','title','Routes still on the old responsive era',
'area','ux','owner_lane','Design','severity','medium',
'n',(select count(*) from pure_responsive_audit where era='v4.8'),'target',0),
jsonb_build_object('id','D-queues','title','Job queues with no registry row (orphan queues)',
'area','governance','owner_lane','Code','severity','medium',
'n',greatest(0,(select count(*) from information_schema.tables
where table_schema='public' and table_name ~ '_(jobs|queue)$')
-(select count(*) from pure_queue_registry)),'target',0)
)); $$;
-- Cron: refresh nothing (the read is live). Instead a daily tick files ONE deduped ticket per
-- derived row whose count moved the wrong way — state-change alerting, per the health canon.pure_upgrade_auditIts name promises more than it holds: that rail tracks page engine-era migrations — route, from_era, to_version, commit sha, revert. It answers "which routes are still v4.8", not "what does the platform still need". Reusing it would force platform upgrades into a per-route shape they do not have. So it stays, and becomes derived row D-era here. Named, so nobody re-litigates it later.
A declared row is a judgment — someone decided this matters. A derived row is arithmetic — it exists while a count is wrong. Declared rows need a human to close them; derived rows cannot be closed by hand at all, which is exactly why the flood and the orphans belong in that half.
Every row is #U-07. A ticket, a lane mail, or Poppy can point at one upgrade and land on it highlighted. Filters live in the URL too — ?owner=Code&crit=1 is a shareable view.