diff --git a/docs/ledger/rows/NAV-QUEST-VISIBILITY.json b/docs/ledger/rows/NAV-QUEST-VISIBILITY.json new file mode 100644 index 000000000..4e40ae91b --- /dev/null +++ b/docs/ledger/rows/NAV-QUEST-VISIBILITY.json @@ -0,0 +1,19 @@ +{ + "id": "NAV-QUEST-VISIBILITY", + "title": "Restore My Quest access for every authenticated member by repairing its exact stale page setting", + "lane": "live", + "priority": "P1", + "size": "S", + "status": "open", + "source": { + "file": "scripts/ops/repair-root-quest-page-visibility.sql", + "ref": "Read-only Frequency Community root page_settings metadata and current admission source, 2026-11-04." + }, + "detail": "Actual authenticated /crew redirected to /feed. Production metadata confirms exactly one root Space and its /crew page_settings row is published, visibility_role=crew, updated_at=2026-06-22 15:21:37.887+00. The community crew rung is retired and separate from paid Crew entitlement. Current Quest matrix and docs/ROLES.md grant every signed-in member full Quest access, while the shared layout still honors this stale operator visibility gate. PROPOSAL ONLY: exact operator transaction resolves one root, locks only /crew, refuses unless status/visibility/updated_at still match this reviewed snapshot, sets native visibility_role NULL (any authenticated member) and updated_at, verifies one row and unchanged remaining columns. No generic page gate, auth, RLS, role or unrelated route changes. The exact SQL passed eleven disposable PGlite 0.4.0 / PostgreSQL 17.5-compatible fixture cases, including unchanged unrelated rows and rollback/refusal cases; this is not hosted Supabase equivalence. Fresh compare-and-set preflight, exact main-committed SQL hash review/authorization and actual production postconditions are still required before any production execution. Never execute twice; an already-null or changed row refuses. Acceptance: authenticated member /crew reaches My Quest, guest remains signed out, other host-only/draft routes and other Space settings stay enforced.", + "verify": { + "kind": "manual", + "checked": "2026-11-04", + "evidence": "Read-only production gate/schema metadata confirms PK(space_id,route), nullable text visibility_role, published root /crew with crew restriction and exact updated_at. Unchanged committed operator SQL SHA-256 258956ee13d656f2b67959fc9a4c3c15690dc3e6a1740d0ced9d2127e1e43cec passes eleven isolated PGlite/PostgreSQL 17.5-compatible scenarios: valid one-row repair with other tenant/host/draft rows unchanged; missing/stale/wrong/already-cleared/ambiguous targets and unexpected target-column trigger changes refuse/roll back. Nine actual-source gate/nav controls keep other draft/host gates and Admin/preview framing intact. This is fixture/source proof, not hosted Supabase/browser/session evidence. Production operator SQL NOT EXECUTED and no setting changed. Keep open until fresh authorized repair/postconditions and member/guest production proof." + }, + "wave": "W7" +} diff --git a/scripts/ops/repair-root-quest-page-visibility.sql b/scripts/ops/repair-root-quest-page-visibility.sql new file mode 100644 index 000000000..0cfa18e26 --- /dev/null +++ b/scripts/ops/repair-root-quest-page-visibility.sql @@ -0,0 +1,62 @@ +-- PROPOSAL ONLY. Do not run before this exact file is reviewed, committed on main and authorized. +-- Restore the existing everyone-plays contract for authenticated members at root /crew only. +-- Native visibility_role=NULL means any signed-in member; it does not make /crew public. +-- Fresh metadata before execution must still match the observed compare-and-set token below. +-- A second execution, a changed row, missing/duplicate root or any unexpected side effect REFUSES. +begin; +set local lock_timeout = '3s'; +set local statement_timeout = '15s'; +do $repair$ +declare + target_space uuid; + root_count integer; + target_count integer; + before_row jsonb; + after_row jsonb; + affected integer; + expected_updated_at constant timestamptz := '2026-06-22 15:21:37.887+00'; +begin + select count(*) into root_count from public.spaces where type = 'root'; + if root_count <> 1 then + raise exception 'QUEST-PAGE-VISIBILITY: expected one root Space, found %; refusing', root_count; + end if; + select id into strict target_space from public.spaces where type = 'root'; + + select count(*) into target_count from public.page_settings + where space_id = target_space and route = '/crew'; + if target_count <> 1 then + raise exception 'QUEST-PAGE-VISIBILITY: expected one root /crew row, found %; refusing', target_count; + end if; + + select to_jsonb(p) into strict before_row + from public.page_settings p + where p.space_id = target_space and p.route = '/crew' + for update; + if before_row is null + or before_row->>'status' is distinct from 'published' + or before_row->>'visibility_role' is distinct from 'crew' + or (before_row->>'updated_at')::timestamptz is distinct from expected_updated_at then + raise exception 'QUEST-PAGE-VISIBILITY: reviewed root /crew preconditions changed; refusing'; + end if; + + update public.page_settings + set visibility_role = null, updated_at = now() + where space_id = target_space and route = '/crew' + and status = 'published' and visibility_role = 'crew' + and updated_at = expected_updated_at; + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'QUEST-PAGE-VISIBILITY: changed % rows, expected one; rolling back', affected; + end if; + + select to_jsonb(p) into after_row from public.page_settings p + where p.space_id = target_space and p.route = '/crew'; + if after_row->>'status' is distinct from 'published' + or after_row->>'visibility_role' is not null + or (after_row - 'visibility_role' - 'updated_at') is distinct from + (before_row - 'visibility_role' - 'updated_at') then + raise exception 'QUEST-PAGE-VISIBILITY: unexpected postcondition; rolling back'; + end if; +end +$repair$; +commit;