Plan: Migrate docs/priorities.md off git to a Supabase table
Issue: #5364
Type: Code-change-design (plan only — no implementation PR yet)
Status: Reviewed against the live tree — worker session bms4-cw-1 on bms-4 (dev-coder)
Last updated: 2026-08-04
Author: Claude Opus 4.8 (worker session bms4-cw-1)
Related: #4559 (Phase 3.5 status-sync — docs/plans/plan-4559-priorities-status-sync.md, the machinery this plan simplifies) · #3178 (SOPS reorg — same “one god-file, many writers” failure class)
0. Summary — recommendation
The issue’s core mechanism is sound and I recommend building it: row-level DB writes eliminate the
concurrent-checkout merge-collision class that docs/priorities.md documents against itself. DB load is a
non-issue exactly as the issue argues (~5–20 writes/day against a project already doing ~720 dispatcher
reads/day). The prior-art reuse (copy RLS from dev_r_status_snapshots, reuse dev_r_agent_sessions for
the narrative log, mirror scripts/priorities_sync.py style) is the right call — this is engineering
surface area, not a load problem.
Five of the plan’s stated details do not survive a read of the current tree. None is fatal; two simplify the work and three are corrections that prevent a broken migration or data loss. They are listed in §1 (Live-tree verification) and folded into the design in §2–§7.
This is a plan-only deliverable. The implementation PR (migration + script + backfill + touchpoint
cutover) follows in a second issue/PR after /review-plan sign-off, per the plan-label workflow.
1. Live-tree verification — deltas from the drafted plan
Every claim in the issue body was checked against main at commit c60d02b. Results:
| # | Plan claim | Live-tree finding | Impact |
|---|---|---|---|
| 1 | ”reuse dev_r_agent_sessions.summary; add 'interactive' to the type CHECK constraint (small migration). It currently only gets rows from type IN ('worker','subagent').” | There is no CHECK constraint on dev_r_agent_sessions.type. supabase/migrations/027_dev_r_agent_sessions.sql:10 defines type TEXT NOT NULL DEFAULT 'worker' — free text, no enum guard. The only CHECK in 027 is the RLS policy’s WITH CHECK (true). No migration in the repo adds a type CHECK later, and no migration contains the string interactive. | Simplifies — no migration is needed to allow type='interactive'; the column already accepts it. BUT a CHECK may have been applied to the live DB out-of-band (not tracked in a migration file). The implementer must verify the live catalog (information_schema / Management API) before backfilling; if a CHECK exists there, add the migration; if not, skip it. status on this table does have a CHECK (worker-issue.md Step 11 relies on 'completed'/'failed'), so do not confuse the two columns. |
| 2 | RLS lockdown history cited as 038_revoke_anon_lock_down_dev_r_tables.sql and 039_lock_down_devops_schema_and_public_views.sql. | Neither filename exists. The actual anon-lockdown migrations are 20260730115455_revoke_anon_execute_definer_functions_4663.sql and 20260730160516_revoke_anon_grants_rls_exposed_tables_4672.sql. | Correction only — the plan’s point is fully supported (there is a documented history of dev_r_* tables shipping open-to-anon and needing a dedicated lockdown pass; #4663/#4672). Keep the rule: anon gets nothing from the first migration. Cite the correct filenames. |
| 3 | New table dev_r_priorities with tier text CHECK (P0/P1/P2/P3) models the whole file. | The file has six H2 data sections, not four: ## 🔴 P0, ## 🟠 P1, ## 🟡 P2, ## 🟢 P3, ## 🟢 Pending Human Actions (columns | Item | Notes | Issue | — a different 3-column shape), and ## References (a static link list, not tabular). plan-4559-priorities-status-sync.md §2 already documents that its parser “deliberately excludes ## Pending Human Actions (middle column is Notes, not Status) and ## References”. | Correction — the tier CHECK must include a value for the Pending-Human-Actions rows (recommend tier IN ('P0','P1','P2','P3','human-action')) with a nullable notes covering that section’s middle column, or those rows stay in git / a separate handling. ## References is static content, not data — the render step must reproduce it from a template, not from the table. |
| 4 | status text CHECK (open/in_progress/resolved). | The live Status column is rich free text with emoji semaphores carrying real operational nuance — e.g. 🟡 PR-E shipped — PR-D/PR-G need human coordination, 🟠 DEFERRED to 07-13 — workflows disabled, 🟢 CLOSED, 🟡 PARTIAL — 11 keys pending. A three-value enum discards this. | Correction — keep a coarse status text CHECK (open/in_progress/resolved) for machine logic plus a free-text status_detail text column that round-trips the human-facing status string, so render reproduces the current file faithfully. |
| 5 | 027_dev_r_agent_sessions.sql pairs its grafana_readonly_select RLS policy with an explicit GRANT SELECT ... TO grafana_readonly, whereas dev_r_status_snapshots shows none — “verify which is load-bearing; when in doubt include the explicit GRANT.” | Confirmed accurate. 027:48 has GRANT SELECT ON dev_r_agent_sessions TO grafana_readonly;, and a separate follow-up migration 20260625_agent_sessions_grafana_readonly_grant.sql exists specifically because the sibling agent_sessions table shipped its policy without the grant and read access silently failed. | Keep the caution — it is well-founded. dev_r_priorities must ship the explicit GRANT SELECT ON dev_r_priorities TO grafana_readonly; alongside the RLS policy in the same migration. A policy alone does not grant table privileges in Postgres. |
Additional observations (non-blocking):
- File size:
docs/priorities.mdis 158 KB (202 lines, very long session-log paragraphs), not the “several KB” the issue estimates. The backfill parser must split the## Recent session loginto per-entry rows — entries are- **YYYY-MM-DD (…) — title …**bullets, newest first, some ~5 KB each. Still trivial for Postgres; the estimate of “+10–20 MB/year” stands. scripts/priorities_sync.pyis a structural parser, not a regex scrubber (its own docstring and plan-4559 §3 say so). Its resolution safety gate is precise: apull/Nref resolves only whenstate==MERGED; anissues/Nref only whenstate==CLOSEDandmilestone.title=='Main'; any non-ghref (GitLab MR, doc path, prose) or anygherror ⇒ skip the row (fail-safe). The simplified DBresolvepath must preserve this exact gate — moving to anUPDATEremoves the git-write apparatus, not the safety criteria.
2. Schema — supabase/migrations/<ts>_dev_r_priorities.sql
<ts> = date +%Y%m%d%H%M%S. Single migration, additive only.
CREATE TABLE IF NOT EXISTS devops.dev_r_priorities (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tier text NOT NULL CHECK (tier IN ('P0','P1','P2','P3','human-action')),
item text NOT NULL, -- the "Item" cell (markdown allowed)
status text NOT NULL DEFAULT 'open'
CHECK (status IN ('open','in_progress','resolved')),
status_detail text, -- verbatim human status string incl. emoji (delta #4)
notes text, -- "Notes" cell for human-action rows (delta #3)
issue_refs jsonb NOT NULL DEFAULT '[]'::jsonb, -- [{kind:'issue'|'pr'|'gitlab'|'doc', number, url}]
sort_order int NOT NULL DEFAULT 0, -- preserve intra-tier ordering for render
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
resolved_at timestamptz
);
ALTER TABLE devops.dev_r_priorities ENABLE ROW LEVEL SECURITY;
-- service_role: full access (write path). Pattern from dev_r_status_snapshots.
CREATE POLICY "service_role_all" ON devops.dev_r_priorities
FOR ALL USING (true) WITH CHECK (true);
-- grafana_readonly: SELECT only. Policy AND explicit grant (delta #5 — both are load-bearing).
CREATE POLICY "grafana_readonly_select" ON devops.dev_r_priorities
FOR SELECT TO grafana_readonly USING (true);
GRANT SELECT ON devops.dev_r_priorities TO grafana_readonly;
-- anon gets NOTHING — no policy, no grant (delta #2). Do not add an anon lockdown follow-up later.
CREATE INDEX IF NOT EXISTS idx_dev_r_priorities_tier_status
ON devops.dev_r_priorities (tier, status);
CREATE TRIGGER trg_dev_r_priorities_updated_at
BEFORE UPDATE ON devops.dev_r_priorities
FOR EACH ROW EXECUTE FUNCTION devops.set_updated_at(); -- reuse existing helper if present; else inlineSchema placement: confirm whether
dev_r_*tables live in thedevopsschema orpublic— check an existingdev_r_*migration’sCREATE TABLEtarget and mirror it exactly (the lockdown migrations#4663/#4672touched adevopsschema; verify before writing the DDL). Theset_updated_attrigger helper may or may not exist — grep the migrations; if absent, setupdated_at = now()in the script’s UPDATE statements instead of a trigger.
Register in dev_r_services in the same migration (pattern: the INSERT INTO ... dev_r_services at the
tail of 027_dev_r_agent_sessions.sql). Per CLAUDE.md §Compliance: compliance_workbook='yes',
workbook_url → this plan doc. No new cron, no new AI system — nothing to register in
dev_r_ai_systems.
3. Narrative log — reuse dev_r_agent_sessions (delta #1)
- No CHECK migration is needed for
type='interactive'unless the live DB carries an out-of-band CHECK (verify first — §1 delta #1). If it does, add a one-line migration widening it; if not, the backfill and thelogsubcommand writetype='interactive'directly. - Historical backfill:
dev_r_agent_sessionsrequiressession_id NOT NULL UNIQUEand carries live- session columns (started_at,ended_at,branch) the markdown log never had. Backfill each session-log entry with a syntheticsession_id = 'backfill-YYYY-MM-DD-N',type='interactive',tags = ARRAY['backfill'],summary =the entry prose, and best-effortstarted_atfrom the entry’s date. This keeps them distinguishable from live tracking. - Note the
statusCHECK on this table only allows'completed'/'failed'— backfill rows should use'completed'.
4. Write path — scripts/priorities.py
Sync Python, no framework (mirrors scripts/priorities_sync.py style). Uses the Management-API-SQL
pattern from docs/playbooks/supabase-management-api-sql-windows.md — never raw psql. Subcommands:
| Subcommand | Action |
|---|---|
add --tier --item [--status] [--refs] [--notes] | INSERT a priority row |
update <id> [--status] [--status-detail] [--item] [--refs] | PATCH a row, bump updated_at |
resolve <id> | set status='resolved', resolved_at=now() — gated by the §1 safety criteria when --check-refs is passed (issue CLOSED+milestone==‘Main’ / PR MERGED) |
log --summary [--date] | INSERT a type='interactive' row into dev_r_agent_sessions |
render | SELECT all rows + narrative, emit the full docs/priorities.md markdown (P0–P3 tables, Pending Human Actions, static References footer, session log) to stdout |
This becomes the only sanctioned write path. Error path per CLAUDE.md §Error Notification (Discord
webhook P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL + gh issue label bug) on any failure.
5. Generated read-only docs/priorities.md
scripts/priorities.py render > docs/priorities.md. Stops being hand-edited; regenerated/committed
periodically or via a light CI step (not on every write). Header gains a
<!-- GENERATED — do not edit; run scripts/priorities.py --> banner so a human doesn’t hand-edit it.
6. Simplify Phase 3.5 / priorities_sync.py (delta note)
Replace the clone + GitHub-App-token + branch + PR + diff-shape-gate flow (plan-4559 §5) with a direct
scripts/priorities.py resolve --check-refs. Keep the human-visible report (Discord/log) before any
row flips to resolved, and keep the exact resolution safety gate (§1). Drop only the git-write
apparatus that existed solely because the target was a file. NightlyTriagePhaseFailed metric + Discord
error wiring are unchanged.
7. Touchpoints to rewire (p24-infra only — phase 1 scope)
| File | Change | In git / PR? |
|---|---|---|
CLAUDE.md §docs/priorities.md (lines ~548–554) | “edit the file” → “run scripts/priorities.py” | yes |
infra/agent-prompts/worker-issue.md (lines 21–32) | prohibition text “don’t edit the file” → “don’t write to dev_r_priorities” | yes |
.claude/commands/nightly-devops-triage.md Phase 3.5 (lines ~260–270, ~1800) | swap git-write flow for priorities.py resolve | yes |
docs/playbooks/changelog-priorities-pattern.md | note p24-infra is on the DB pattern; other 5 repos stay on markdown (phase 1 scope) | yes |
/sr, /p24-status skills | grep found no direct docs/priorities.md reference in .claude/skills/ — verify at implementation time; likely no change needed | yes if any |
C:\Users\konar\.claude-ecotrans\CLAUDE.md §Session Priorities | flip the generic ALL-PROJECTS “hand-edit docs/priorities.md” instruction | NO — local-only, outside git. Manual follow-up for the user (see §10). |
8. Cutover risk & rollback
- Single-PR cutover: ship table + script + full backfill (every current row + every session-log entry) and flip the p24-infra CLAUDE.md instruction in one PR — no window where both paths are “correct”. Announce the cutover commit hash so in-flight sessions rebase past it before their next priorities edit.
- Rollback: purely additive (no file/column dropped). Revert the CLAUDE.md-instruction + script-touchpoint changes to resume hand-editing the markdown; leave the table in place unused. No cleanup required.
9. Compliance
- No new cron, no new AI system → nothing to register in
dev_r_ai_systems;docs/eu-ai-act-compliance.mdunchanged. Phase 3.5 keeps its existing error-notification wiring. - New table →
dev_r_servicesrow (in-migration INSERT) + this ops/plan doc satisfy CLAUDE.md §Compliance. - Do NOT
ALTERdev_r_services— it is a VIEW (CLAUDE.md §Do-NOT /#4293→#4442→#4447); the registration is anINSERTinto its underlying relation, exactly as027does.
10. Open decisions (for /review-plan + human)
- Local file (
C:\Users\konar\.claude-ecotrans\CLAUDE.md) — outside the repo/sandbox; the worker cannot edit it. Manual follow-up for the user/architect, needed in the same sitting as the p24-infra cutover so the generic ALL-PROJECTS rule stops telling sessions to hand-edit the now-generated file. - Schema (
devopsvspublic) andset_updated_athelper existence — resolve by reading an existingdev_r_*migration before writing DDL (§2 note). - Live
typeCHECK ondev_r_agent_sessions— verify via Management API before deciding whether a widening migration is needed (§1 delta #1). - Pending-Human-Actions rows — confirm
tier='human-action'modelling vs leaving that section in git.
11. Dependencies
docs/playbooks/supabase-management-api-sql-windows.md(SQL execution pattern — no raw psql).docs/playbooks/supabase-migrations.md(migration apply + thedev_r_services-is-a-VIEW hazard).SUPABASE_ACCESS_TOKEN(Management API PAT — canonical homesecrets/administration.env.sops, per CLAUDE.md SOPS map). Referenced by NAME only; asecret-manager/sys-adminpath supplies it at run time — not this dev-coder plan’s concern.
12. Not in scope (phase 1)
Migrating the other 5 repos’ docs/priorities.md; any UI beyond the rendered markdown.