feat(door): P3-A door_card_slots per-connection slot allocation map

Migration 036 + schema.sql Section 23: per-connection integer-slot
allocation table for controllers that key cards by slot (HeatSync 0-200).
UNIQUE(connection_id,slot) lets the DB arbitrate concurrent grants;
UNIQUE(connection_id,card_id) makes re-grant idempotent. RLS additive,
default-deny: SELECT = door.manage/operate, no client write policy
(service-client executor only). Types + DATABASE_SCHEMA + DB_SCHEMA_MAP.
This commit is contained in:
Moheeb Zara
2026-05-16 21:06:21 -07:00
parent 7ea3dea697
commit 3985419157
5 changed files with 113 additions and 1 deletions
+3 -1
View File
@@ -401,10 +401,11 @@ Default channels created by trigger on space INSERT: `general`, `announcements`,
| 033 | `equipment`, `equipment_reservations` + `equipment.manage` permission. Additive default-deny RLS; reserve/cancel via validated service-client action (no INSERT/DELETE policy) enforcing status + no-overlap + required-cert with manager override |
| 034 | `member_cards` + `door.manage` / `door.operate` permissions. Card UID is a credential: door.manage-only RLS, no member SELECT; masked (count+last4) self-view via server action. Door epic phase 1, no controller calls |
| 035 | `door_connections`, `door_access_log` (Door epic P2). Password via secrets vault (secret_ref), `pinned_host` SSRF pin, hardened executor. Connections CRUD = door.manage; log SELECT = door.manage/operate, service-client-only writes |
| 036 | `door_card_slots` (Door epic P3). Per-connection integer-slot allocation map (HeatSync keys cards by slot 0-200). UNIQUE(connection_id,slot) + UNIQUE(connection_id,card_id); lowest-free policy in pure logic. SELECT = door.manage/operate, service-client-only writes |
---
## Tables added by migrations 016-035 (quick map)
## Tables added by migrations 016-036 (quick map)
| Table | Key columns | Purpose |
|-------|-------------|---------|
@@ -433,6 +434,7 @@ Default channels created by trigger on space INSERT: `general`, `announcements`,
| `member_cards` | space_id, member_id, card_uid (credential), card_type rfid/nfc, label, is_active | RFID/NFC card association. SELECT/writes = `door.manage` only; member self-view is masked (server action, never raw UID) |
| `door_connections` | space_id, name, adapter (native_heatsync/generic_http), base_url, pinned_host (SSRF pin), auth_mode, secret_ref -> secrets, verbs jsonb, allow_member_self_entry, is_enabled | Door controller integration. CRUD = `door.manage`. Password lives in the encrypted secrets vault, never here |
| `door_access_log` | space_id, connection_id, actor_member_id, target_member_id, action, success, detail (redacted), occurred_at | Append-only door audit. SELECT = `door.manage`/`door.operate`; NO write policy (validated service-client executor only; immutable) |
| `door_card_slots` | space_id, connection_id -> door_connections, card_id -> member_cards, slot int, created_by | Per-connection integer-slot allocation map. UNIQUE(connection_id,slot) + UNIQUE(connection_id,card_id). SELECT = `door.manage`/`door.operate`; NO write policy (service-client executor only, in lockstep with the controller) |
Column additions: `space_members.tier_id`, `onboarding_completed_at`, `onboarding_progress`; `secrets.encrypted_value`, `encryption_version`; `knowledge_base.render_markdown`, `is_meeting_minutes`, `meeting_date`. New enum `comment_entity_type`.