docs: pass-17 CHANGELOG / HANDOFF / schema migration table

Records the UX polish tranche and the incident-RLS diagnosis (incl. the
discriminating production SQL) and adds migration 025 to the schema
migration table.
This commit is contained in:
Moheeb Zara
2026-05-15 22:13:03 -07:00
parent f885ac73d3
commit 64d7d626db
3 changed files with 42 additions and 2 deletions
+6
View File
@@ -16,6 +16,12 @@ All notable changes to this project are documented here. Format follows [Keep a
- `types/database.ts` brought in sync with the schema (8 tables, the `comment_entity_type` enum, and the new columns); docs (`DATABASE_SCHEMA.md`, `DB_SCHEMA_MAP.md`, `API_REFERENCE.md`) refreshed for migrations 016-024. - `types/database.ts` brought in sync with the schema (8 tables, the `comment_entity_type` enum, and the new columns); docs (`DATABASE_SCHEMA.md`, `DB_SCHEMA_MAP.md`, `API_REFERENCE.md`) refreshed for migrations 016-024.
- The member directory has a per-row "make area lead" assignment for admin/board (mirrors Customize -> Area leads). - The member directory has a per-row "make area lead" assignment for admin/board (mirrors Customize -> Area leads).
### Fixed
- Production report: a basic member filing an incident hit `new row violates row-level security policy for table "incidents"`, including the anonymous path. Not reproducible from source (the code and the 016/017 policy both permit it). Migration 025 idempotently re-asserts the hardened `incidents_insert` policy verbatim so the deployed policy converges to the known-good definition (access-neutral, no schema change). The file-incident action now maps an RLS rejection to an actionable message and logs the technical detail server-side. If the report persists after deploy, the root cause is not policy drift; see `docs/HANDOFF.md` for the discriminating queries.
### UX
- Accessibility and consistency polish pass (Tier 1 systemic, in progress): distinct Ops nav icon (was a duplicate of Settings), `aria-current` on active nav, accessible mobile drawer (role/aria-modal, Escape, scroll lock, focus management), skip-to-content link, shared `BrandMark` in the sidebar, a shared `PageTitle`/`SectionTitle` primitive adopted app-wide, and a single shared `Empty` primitive replacing ~18 ad-hoc empty states (projects gains a real empty state with a CTA).
### Security ### Security
- Closed two member self-escalation paths: a non-privileged member can no longer change their own `tier_id` or `onboarding_completed_at` on the `space_members` row (migration 024 extends the self-change trigger). Onboarding completion now goes through the service client after server-side validation. - Closed two member self-escalation paths: a non-privileged member can no longer change their own `tier_id` or `onboarding_completed_at` on the `space_members` row (migration 024 extends the self-change trigger). Onboarding completion now goes through the service client after server-side validation.
- Added in-code space scoping to forum/comment/channel mutations (defence-in-depth on top of RLS). - Added in-code space scoping to forum/comment/channel mutations (defence-in-depth on top of RLS).
+3 -2
View File
@@ -6,7 +6,7 @@
> **Source of Truth**: `scripts/schema.sql` (canonical, idempotent); numbered > **Source of Truth**: `scripts/schema.sql` (canonical, idempotent); numbered
> migrations `scripts/0NN_*.sql` upgrade existing deployments. The 13-table > migrations `scripts/0NN_*.sql` upgrade existing deployments. The 13-table
> reference below is the original baseline; tables added by migrations > reference below is the original baseline; tables added by migrations
> 014-024 are summarized in the "Migrations 014-024" section at the end. > 014-025 are summarized in the "Migrations 014-025" section at the end.
--- ---
@@ -686,7 +686,7 @@ CREATE INDEX idx_activity_space ON activity_log(space_id, created_at);
--- ---
## Migrations 014-024 (additions since the 13-table baseline) ## Migrations 014-025 (additions since the 13-table baseline)
`scripts/schema.sql` is the canonical idempotent schema; each numbered `scripts/schema.sql` is the canonical idempotent schema; each numbered
migration is mirrored as a section in it. Tables/columns added: migration is mirrored as a section in it. Tables/columns added:
@@ -704,6 +704,7 @@ migration is mirrored as a section in it. Tables/columns added:
| 022 | `space_onboarding_steps`; `space_members.onboarding_completed_at/onboarding_progress`; seed default steps | | 022 | `space_onboarding_steps`; `space_members.onboarding_completed_at/onboarding_progress`; seed default steps |
| 023 | `space_role_permissions`, `ops_acl`; `user_effective_roles()`, `user_has_permission()` (SECURITY DEFINER); `secrets`/`knowledge_base` SELECT rewritten as `existing-rule OR ops_acl-match` (additive) | | 023 | `space_role_permissions`, `ops_acl`; `user_effective_roles()`, `user_has_permission()` (SECURITY DEFINER); `secrets`/`knowledge_base` SELECT rewritten as `existing-rule OR ops_acl-match` (additive) |
| 024 | `prevent_member_self_role_change()` extended to also block self-change of `tier_id` and `onboarding_completed_at` | | 024 | `prevent_member_self_role_change()` extended to also block self-change of `tier_id` and `onboarding_completed_at` |
| 025 | Re-asserts the hardened `incidents_insert` policy verbatim (production convergence; access-neutral, no schema change) |
New enum: `comment_entity_type` = `forum_thread | proposal | incident | policy`. New enum: `comment_entity_type` = `forum_thread | proposal | incident | policy`.
+33
View File
@@ -4,6 +4,39 @@ Append-only. Newest entries on top. Keep each entry to one screen.
--- ---
## 2026-05-15 (pass 17) — UX polish (Tiers 1-3) + production incident-RLS report
Branch: `main`. Migration: 025 (applies on next deploy). Not pushed.
### UX polish (in progress, Tiers 1-3 of the audit)
- Full UI/UX audit done (5 partitioned agents, code-level; no live browser, auth routes need seed). Findings report delivered to the user, grouped Tier 1 systemic / Tier 2 first-run / Tier 3 security-adjacent / Tier 4 brand / Tier 5 per-surface. User chose: implement Tiers 1-3 this pass, pause before 4-5; sidebar nav stays as-is (no role-filtering); comments get one-level nesting + edit (Tier 5, deferred to post-pause).
- Committed (local, build+test green 271/271 each): (1) accessible sidebar/drawer + distinct Ops icon + skip link + BrandMark; (2) shared `PageTitle`/`SectionTitle` primitive adopted across 21 files; (3) shared `Empty` primitive replacing ~18 ad-hoc empty states (projects gains a real empty state).
- Still open in Tier 1: Dialog/AlertDialog adoption (hand-rolled modals, `confirm()` x13), focus-visible, tap targets, label associations, silent-failure toasts. Tier 2/3 not started.
### Production incident-filing RLS report (priority interrupt)
User screenshot: a basic active member filing an incident at `/incidents/new` (anonymous checked) gets `new row violates row-level security policy for table "incidents"`. Production is on the OLD deployed commit (sidebar still `{hs}`), so this is pre-existing, not from this session.
- Not reproducible from source: `fileIncident` sets `reporter_id=NULL` when anonymous and `space_id=member.space_id`; `getAuthMember` resolves by `user_id=auth.uid()`; the 016/017 `incidents_insert` policy allows exactly this. So it is a production deploy/data divergence.
- Three candidate root causes: (1) `auth.uid()` NULL inside the DB request (SSR session not reaching PostgREST); (2) deployed `incidents_insert` policy differs from 016/017; (3) acting `space_members.user_id` != JWT sub on production (data drift).
- Defensive fixes prepared (user-approved, blind, NOT pushed): `scripts/025` idempotently re-asserts the hardened `incidents_insert` policy verbatim (access-neutral; fixes cause #2 only); `fileIncident` maps RLS rejection to an actionable message and logs detail server-side. `scripts/schema.sql` already matches (no change).
### Pending decision / next step
Run these on the production DB (service role) to disambiguate before relying on 025:
```sql
SELECT polname, pg_get_expr(polwithcheck, polrelid) AS with_check
FROM pg_policy WHERE polrelid = 'public.incidents'::regclass AND polcmd = 'a';
SELECT id, space_id, user_id, status, role FROM public.space_members WHERE user_id = '<auth_uid>';
SELECT public.get_user_space_ids('<auth_uid>'::uuid);
```
If query 1 already shows the 016/017 expression and query 2 shows a matching `user_id` with the space present, 025 will NOT fix it (cause #1 or #3); investigate the SSR Supabase client / membership data instead.
---
## 2026-05-15 — SESSION CLOSE: state of the app ## 2026-05-15 — SESSION CLOSE: state of the app
One-screen status for the next session. Detailed history in the pass entries below. One-screen status for the next session. Detailed history in the pass entries below.