docs: fix doc drift from D/F/G work — migration 046, harness, subsystem-level facts

DATABASE_SCHEMA + DB_SCHEMA_MAP: 046 migration row (was missing — added
last turn but not table-rowed; CLAUDE.md doc-with-change rule).
README Features: Stripe recurring dues, transactional notifications,
member self-serve portal (Phase 1/2/3 — months of shipped product
spine were absent from the first thing a reader sees).
ARCHITECTURE: Known Limitations #1 now describes the DB-backed
integration harness (the previous 'no harness' claim was misleading);
Classes section notes the advisory-lock RPCs (045); Equipment notes
the GiST exclusion constraint + btree_gist (042); Door notes the
DNS-rebind defense (resolve-once, pin validated IP) + no-raw-reason
client policy + auth-param-aware redaction; Stripe webhook notes the
out-of-order monotonic period guard (D5), zero-decimal currency
handling (D7), and generic error responses (E3); Notifications notes
the per-space fair drain + per-attempt Idempotency-Key + .eq('status',
'pending') re-entrancy guard; Authorization adds the RLS-layer
privilege-eligible status gate (046).
This commit is contained in:
Moheeb Zara
2026-05-19 14:19:44 -07:00
parent 3703c2a76c
commit 19dc6321be
4 changed files with 60 additions and 8 deletions
+1
View File
@@ -726,6 +726,7 @@ migration is mirrored as a section in it. Tables/columns added:
| 043 | `members_update` RLS policy gains a `WITH CHECK` mirroring its `USING` (`user_id = auth.uid()` OR admin/board/treasurer in `space_id`). Defense-in-depth: the post-image is re-validated so a privileged caller cannot move a member row into a space they do not administer. No new table/column |
| 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 |
New enum: `comment_entity_type` = `forum_thread | proposal | incident | policy`.