docs: pass-56 second-audit touchups, lockdown note + style nit
DATABASE_SCHEMA: the migration 047 row now records the REVOKE/GRANT lockdown, with one-line rationale for why it differs from user_has_permission (RLS policies depend on anon/authenticated being able to call user_has_permission, so locking it would break policy evaluation; the new function has no such constraint). HANDOFF: replaces "Next-most-leverage" with "Highest-priority" in my pass-56 backlog header. "Leverage" is on the CLAUDE.md style-bans list.
This commit is contained in:
@@ -727,7 +727,7 @@ migration is mirrored as a section in it. Tables/columns added:
|
||||
| 044 | `prevent_member_self_role_change` extended to also block a non-privileged member changing their own `payment_status`, `payment_note`, `dues_paid_until`, `last_paid_at`, `last_payment_at`, `stripe_customer_id`, `joined_at` (RLS lets a member UPDATE their own row, so these were self-settable to forge a dues-good signal). Service-client (`auth.uid()` NULL) and privileged-on-another-member writes still bypass. No new table/column |
|
||||
| 045 | `class_signup_tx(p_session_id,p_space_id,p_member_id)` + `class_cancel_tx(...)` SECURITY DEFINER, `search_path=public`. Each takes `pg_advisory_xact_lock(hashtext(session_id))` then does the capacity-decision+dup-check+insert (signup) or cancel+conditional waitlist-promote (cancel) atomically. Closes the concurrent over-enroll / double-promote races (dynamic capacity can't be a constraint). Mirrors `computeSignupStatus`/`pickPromotion`; the SQL is the runtime authority. No new table/column/policy |
|
||||
| 046 | `user_has_role_in_space` and `user_has_permission` extended with `status IN ('current','late')` (mirrors `lib/permissions` `PRIVILEGE_STATUSES`). RLS-layer defense-in-depth for D2 (the require_approval / unverified-admin escalation): a non-privilege-eligible member who somehow bypassed the app guards still cannot exercise a role or permission. `get_user_space_ids` and `user_effective_roles` are deliberately NOT gated so unverified members keep their own /me + onboarding reads (the legitimate require_approval flow). No new table/column/policy |
|
||||
| 047 | `members_with_permission(sid, perm)` SECURITY DEFINER, `search_path=public`. Inverted, set-returning form of `user_has_permission`: returns `member_id` for every member of `sid` who holds `perm` (same current/late status gate as 046, same admin shortcut, same `space_role_permissions` + `user_effective_roles` fallback). Lets the Phase 4 notification fan-outs (e.g. `form_submission_admin`) find permission holders in one query instead of N round-trips. Additive; existing `user_has_permission` callers unchanged. No new table/column/policy |
|
||||
| 047 | `members_with_permission(sid, perm)` SECURITY DEFINER, `search_path=public`. Inverted, set-returning form of `user_has_permission`: returns `member_id` for every member of `sid` who holds `perm` (same current/late status gate as 046, same admin shortcut, same `space_role_permissions` + `user_effective_roles` fallback). Lets the Phase 4 notification fan-outs (e.g. `form_submission_admin`) find permission holders in one query instead of N round-trips. EXECUTE is locked: `REVOKE EXECUTE FROM PUBLIC`, `GRANT EXECUTE TO service_role` so the set-returning shape cannot be used by a PostgREST client to enumerate "who has permission X in space Y" across spaces (the rationale for not locking `user_has_permission` is that RLS policies depend on anon/authenticated being able to call it). Additive; existing `user_has_permission` callers unchanged. No new table/column/policy |
|
||||
|
||||
New enum: `comment_entity_type` = `forum_thread | proposal | incident | policy`.
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ Branch `main`. Took item 3 from the pass-55 backlog: booking, class signup, and
|
||||
- **No volume governor.** Every form submission fans out one row per `forms.manage` holder; a popular waiver could create N rows per submission. Acceptable today (the unique index makes it idempotent); a digest / throttle is a separate phase (lines up with member preferences and an in-app inbox).
|
||||
|
||||
### Backlog after this
|
||||
The pass-55 list minus item 3. Next-most-leverage items remain:
|
||||
The pass-55 list minus item 3. Highest-priority items remaining:
|
||||
1. Owner-gated end-to-end spine validation (the shipped≠proven gap).
|
||||
2. Production observability (Sentry-equivalent for the webhook + dispatcher + actions).
|
||||
3. Phase 4-adjacent: member notification preferences (per-type opt-in/out; the dispatcher checks prefs before sending; natural next pass now that breadth exists).
|
||||
|
||||
Reference in New Issue
Block a user