qualia-framework 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +50 -0
  2. package/bin/cli.js +519 -0
  3. package/framework/agents/architecture-strategist.md +53 -0
  4. package/framework/agents/backend-agent.md +150 -0
  5. package/framework/agents/code-simplicity-reviewer.md +86 -0
  6. package/framework/agents/frontend-agent.md +111 -0
  7. package/framework/agents/kieran-typescript-reviewer.md +96 -0
  8. package/framework/agents/performance-oracle.md +111 -0
  9. package/framework/agents/qualia-codebase-mapper.md +760 -0
  10. package/framework/agents/qualia-debugger.md +1203 -0
  11. package/framework/agents/qualia-executor.md +881 -0
  12. package/framework/agents/qualia-integration-checker.md +423 -0
  13. package/framework/agents/qualia-phase-researcher.md +453 -0
  14. package/framework/agents/qualia-plan-checker.md +699 -0
  15. package/framework/agents/qualia-planner.md +1241 -0
  16. package/framework/agents/qualia-project-researcher.md +602 -0
  17. package/framework/agents/qualia-research-synthesizer.md +236 -0
  18. package/framework/agents/qualia-roadmapper.md +605 -0
  19. package/framework/agents/qualia-verifier.md +685 -0
  20. package/framework/agents/team-orchestrator.md +228 -0
  21. package/framework/agents/teams/full-stack-team.md +48 -0
  22. package/framework/agents/teams/optimize-team.md +53 -0
  23. package/framework/agents/teams/review-team.md +62 -0
  24. package/framework/agents/teams/ship-team.md +86 -0
  25. package/framework/agents/test-agent.md +182 -0
  26. package/framework/askpass.sh +2 -0
  27. package/framework/commands/design.md +53 -0
  28. package/framework/commands/quick-db.md +22 -0
  29. package/framework/config/retention.json +35 -0
  30. package/framework/core/PRINCIPLES.md +77 -0
  31. package/framework/hooks/auto-format.sh +45 -0
  32. package/framework/hooks/block-env-edit.sh +42 -0
  33. package/framework/hooks/branch-guard.sh +46 -0
  34. package/framework/hooks/confirm-delete.sh +56 -0
  35. package/framework/hooks/migration-validate.sh +68 -0
  36. package/framework/hooks/notification-speak.sh +15 -0
  37. package/framework/hooks/pre-commit.sh +80 -0
  38. package/framework/hooks/pre-compact.sh +55 -0
  39. package/framework/hooks/pre-deploy-gate.sh +151 -0
  40. package/framework/hooks/qualia-colors.sh +32 -0
  41. package/framework/hooks/retention-cleanup.sh +43 -0
  42. package/framework/hooks/save-session-state.sh +153 -0
  43. package/framework/hooks/session-context-loader.sh +28 -0
  44. package/framework/hooks/session-learn.sh +30 -0
  45. package/framework/knowledge/claudecode-bible.md +1384 -0
  46. package/framework/knowledge/client-prefs.md +22 -0
  47. package/framework/knowledge/common-fixes.md +25 -0
  48. package/framework/knowledge/deployment-map.md +35 -0
  49. package/framework/knowledge/email-signature.html +1 -0
  50. package/framework/knowledge/employees.md +8 -0
  51. package/framework/knowledge/learned-patterns.md +51 -0
  52. package/framework/knowledge/optimization-research-2026.md +137 -0
  53. package/framework/knowledge/qualia-context.md +67 -0
  54. package/framework/knowledge/supabase-patterns.md +50 -0
  55. package/framework/knowledge/voice-agent-patterns.md +46 -0
  56. package/framework/qualia-engine/VERSION +1 -0
  57. package/framework/qualia-engine/bin/qualia-tools.js +2160 -0
  58. package/framework/qualia-engine/bin/qualia-tools.test.js +1054 -0
  59. package/framework/qualia-engine/references/checkpoints.md +775 -0
  60. package/framework/qualia-engine/references/continuation-format.md +249 -0
  61. package/framework/qualia-engine/references/decimal-phase-calculation.md +65 -0
  62. package/framework/qualia-engine/references/design-quality.md +56 -0
  63. package/framework/qualia-engine/references/git-integration.md +254 -0
  64. package/framework/qualia-engine/references/git-planning-commit.md +50 -0
  65. package/framework/qualia-engine/references/model-profile-resolution.md +32 -0
  66. package/framework/qualia-engine/references/model-profiles.md +73 -0
  67. package/framework/qualia-engine/references/phase-argument-parsing.md +61 -0
  68. package/framework/qualia-engine/references/planning-config.md +195 -0
  69. package/framework/qualia-engine/references/questioning.md +141 -0
  70. package/framework/qualia-engine/references/tdd.md +263 -0
  71. package/framework/qualia-engine/references/ui-brand.md +160 -0
  72. package/framework/qualia-engine/references/verification-patterns.md +612 -0
  73. package/framework/qualia-engine/templates/DEBUG.md +159 -0
  74. package/framework/qualia-engine/templates/DESIGN.md +81 -0
  75. package/framework/qualia-engine/templates/UAT.md +247 -0
  76. package/framework/qualia-engine/templates/codebase/architecture.md +255 -0
  77. package/framework/qualia-engine/templates/codebase/concerns.md +310 -0
  78. package/framework/qualia-engine/templates/codebase/conventions.md +307 -0
  79. package/framework/qualia-engine/templates/codebase/integrations.md +280 -0
  80. package/framework/qualia-engine/templates/codebase/stack.md +186 -0
  81. package/framework/qualia-engine/templates/codebase/structure.md +285 -0
  82. package/framework/qualia-engine/templates/codebase/testing.md +480 -0
  83. package/framework/qualia-engine/templates/config.json +35 -0
  84. package/framework/qualia-engine/templates/context.md +283 -0
  85. package/framework/qualia-engine/templates/continue-here.md +78 -0
  86. package/framework/qualia-engine/templates/debug-subagent-prompt.md +91 -0
  87. package/framework/qualia-engine/templates/discovery.md +146 -0
  88. package/framework/qualia-engine/templates/milestone-archive.md +123 -0
  89. package/framework/qualia-engine/templates/milestone.md +115 -0
  90. package/framework/qualia-engine/templates/phase-prompt.md +567 -0
  91. package/framework/qualia-engine/templates/planner-subagent-prompt.md +117 -0
  92. package/framework/qualia-engine/templates/project.md +184 -0
  93. package/framework/qualia-engine/templates/projects/ai-agent.md +156 -0
  94. package/framework/qualia-engine/templates/projects/mobile-app.md +181 -0
  95. package/framework/qualia-engine/templates/projects/voice-agent.md +134 -0
  96. package/framework/qualia-engine/templates/projects/website.md +137 -0
  97. package/framework/qualia-engine/templates/requirements.md +231 -0
  98. package/framework/qualia-engine/templates/research-project/ARCHITECTURE.md +204 -0
  99. package/framework/qualia-engine/templates/research-project/FEATURES.md +147 -0
  100. package/framework/qualia-engine/templates/research-project/PITFALLS.md +200 -0
  101. package/framework/qualia-engine/templates/research-project/STACK.md +120 -0
  102. package/framework/qualia-engine/templates/research-project/SUMMARY.md +170 -0
  103. package/framework/qualia-engine/templates/research.md +552 -0
  104. package/framework/qualia-engine/templates/roadmap.md +202 -0
  105. package/framework/qualia-engine/templates/state.md +176 -0
  106. package/framework/qualia-engine/templates/summary-complex.md +59 -0
  107. package/framework/qualia-engine/templates/summary-minimal.md +41 -0
  108. package/framework/qualia-engine/templates/summary-standard.md +48 -0
  109. package/framework/qualia-engine/templates/summary.md +246 -0
  110. package/framework/qualia-engine/templates/user-setup.md +311 -0
  111. package/framework/qualia-engine/templates/verification-report.md +322 -0
  112. package/framework/qualia-engine/workflows/add-phase.md +179 -0
  113. package/framework/qualia-engine/workflows/add-todo.md +157 -0
  114. package/framework/qualia-engine/workflows/audit-milestone.md +241 -0
  115. package/framework/qualia-engine/workflows/check-todos.md +176 -0
  116. package/framework/qualia-engine/workflows/complete-milestone.md +858 -0
  117. package/framework/qualia-engine/workflows/diagnose-issues.md +219 -0
  118. package/framework/qualia-engine/workflows/discovery-phase.md +289 -0
  119. package/framework/qualia-engine/workflows/discuss-phase.md +534 -0
  120. package/framework/qualia-engine/workflows/execute-phase.md +559 -0
  121. package/framework/qualia-engine/workflows/execute-plan.md +438 -0
  122. package/framework/qualia-engine/workflows/help.md +470 -0
  123. package/framework/qualia-engine/workflows/insert-phase.md +220 -0
  124. package/framework/qualia-engine/workflows/list-phase-assumptions.md +178 -0
  125. package/framework/qualia-engine/workflows/map-codebase.md +327 -0
  126. package/framework/qualia-engine/workflows/new-milestone.md +363 -0
  127. package/framework/qualia-engine/workflows/new-project.md +1037 -0
  128. package/framework/qualia-engine/workflows/pause-work.md +122 -0
  129. package/framework/qualia-engine/workflows/plan-milestone-gaps.md +256 -0
  130. package/framework/qualia-engine/workflows/plan-phase.md +422 -0
  131. package/framework/qualia-engine/workflows/progress.md +354 -0
  132. package/framework/qualia-engine/workflows/quick.md +252 -0
  133. package/framework/qualia-engine/workflows/remove-phase.md +326 -0
  134. package/framework/qualia-engine/workflows/research-phase.md +74 -0
  135. package/framework/qualia-engine/workflows/resume-project.md +306 -0
  136. package/framework/qualia-engine/workflows/set-profile.md +80 -0
  137. package/framework/qualia-engine/workflows/settings.md +145 -0
  138. package/framework/qualia-engine/workflows/transition.md +556 -0
  139. package/framework/qualia-engine/workflows/update.md +197 -0
  140. package/framework/qualia-engine/workflows/verify-phase.md +195 -0
  141. package/framework/qualia-engine/workflows/verify-work.md +625 -0
  142. package/framework/rules/context7.md +11 -0
  143. package/framework/rules/deployment.md +29 -0
  144. package/framework/rules/frontend.md +33 -0
  145. package/framework/rules/security.md +12 -0
  146. package/framework/rules/speed.md +20 -0
  147. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  148. package/framework/scripts/apply-retention.sh +120 -0
  149. package/framework/scripts/bootstrap-pop-os.sh +354 -0
  150. package/framework/scripts/claude-voice +13 -0
  151. package/framework/scripts/cleanup.sh +131 -0
  152. package/framework/scripts/cowork-mode.sh +141 -0
  153. package/framework/scripts/generate-project-claude-md.sh +153 -0
  154. package/framework/scripts/load-test-webhook.js +172 -0
  155. package/framework/scripts/say.py +236 -0
  156. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +167 -0
  157. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +216 -0
  158. package/framework/scripts/speak.py +55 -0
  159. package/framework/scripts/speak.sh +18 -0
  160. package/framework/scripts/status.sh +138 -0
  161. package/framework/scripts/sync-to-framework.sh +65 -0
  162. package/framework/scripts/voice-hotkey.py +227 -0
  163. package/framework/scripts/voice-input.sh +51 -0
  164. package/framework/skills/animate/SKILL.md +202 -0
  165. package/framework/skills/bolder/SKILL.md +144 -0
  166. package/framework/skills/browser-qa/SKILL.md +536 -0
  167. package/framework/skills/clarify/SKILL.md +179 -0
  168. package/framework/skills/colorize/SKILL.md +170 -0
  169. package/framework/skills/critique/SKILL.md +126 -0
  170. package/framework/skills/deep-research/SKILL.md +271 -0
  171. package/framework/skills/delight/SKILL.md +329 -0
  172. package/framework/skills/deploy/SKILL.md +261 -0
  173. package/framework/skills/deploy-verify/SKILL.md +377 -0
  174. package/framework/skills/deploy-verify/scripts/canary-check.sh +206 -0
  175. package/framework/skills/deploy-verify/scripts/check-console-errors.js +147 -0
  176. package/framework/skills/deploy-verify/scripts/check-cwv.js +139 -0
  177. package/framework/skills/deploy-verify/scripts/project-detect.sh +84 -0
  178. package/framework/skills/deploy-verify/scripts/verify.sh +548 -0
  179. package/framework/skills/design-quieter/SKILL.md +130 -0
  180. package/framework/skills/distill/SKILL.md +149 -0
  181. package/framework/skills/docs-lookup/SKILL.md +78 -0
  182. package/framework/skills/fcm-notifications/SKILL.md +125 -0
  183. package/framework/skills/financial-ledger/SKILL.md +1039 -0
  184. package/framework/skills/frontend-master/NOTICE.md +4 -0
  185. package/framework/skills/frontend-master/SKILL.md +127 -0
  186. package/framework/skills/frontend-master/reference/color-and-contrast.md +132 -0
  187. package/framework/skills/frontend-master/reference/interaction-design.md +123 -0
  188. package/framework/skills/frontend-master/reference/motion-design.md +99 -0
  189. package/framework/skills/frontend-master/reference/responsive-design.md +114 -0
  190. package/framework/skills/frontend-master/reference/spatial-design.md +100 -0
  191. package/framework/skills/frontend-master/reference/typography.md +131 -0
  192. package/framework/skills/frontend-master/reference/ux-writing.md +107 -0
  193. package/framework/skills/harden/SKILL.md +357 -0
  194. package/framework/skills/i18n-rtl/SKILL.md +752 -0
  195. package/framework/skills/learn/SKILL.md +71 -0
  196. package/framework/skills/memory/SKILL.md +50 -0
  197. package/framework/skills/mobile-expo/SKILL.md +864 -0
  198. package/framework/skills/mobile-expo/references/store-checklist.md +550 -0
  199. package/framework/skills/nestjs-backend/README.md +73 -0
  200. package/framework/skills/nestjs-backend/SKILL.md +446 -0
  201. package/framework/skills/nestjs-backend/references/templates.md +1173 -0
  202. package/framework/skills/normalize/SKILL.md +79 -0
  203. package/framework/skills/onboard/SKILL.md +242 -0
  204. package/framework/skills/polish/SKILL.md +209 -0
  205. package/framework/skills/pr/SKILL.md +66 -0
  206. package/framework/skills/qualia/SKILL.md +153 -0
  207. package/framework/skills/qualia-add-todo/SKILL.md +68 -0
  208. package/framework/skills/qualia-audit-milestone/SKILL.md +92 -0
  209. package/framework/skills/qualia-check-todos/SKILL.md +55 -0
  210. package/framework/skills/qualia-complete-milestone/SKILL.md +108 -0
  211. package/framework/skills/qualia-debug/SKILL.md +149 -0
  212. package/framework/skills/qualia-design/SKILL.md +203 -0
  213. package/framework/skills/qualia-discuss-phase/SKILL.md +72 -0
  214. package/framework/skills/qualia-execute-phase/SKILL.md +86 -0
  215. package/framework/skills/qualia-help/SKILL.md +67 -0
  216. package/framework/skills/qualia-idk/SKILL.md +352 -0
  217. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +67 -0
  218. package/framework/skills/qualia-new-milestone/SKILL.md +72 -0
  219. package/framework/skills/qualia-new-project/SKILL.md +92 -0
  220. package/framework/skills/qualia-optimize/SKILL.md +417 -0
  221. package/framework/skills/qualia-pause-work/SKILL.md +96 -0
  222. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +57 -0
  223. package/framework/skills/qualia-plan-phase/SKILL.md +101 -0
  224. package/framework/skills/qualia-progress/SKILL.md +53 -0
  225. package/framework/skills/qualia-quick/SKILL.md +89 -0
  226. package/framework/skills/qualia-research-phase/SKILL.md +88 -0
  227. package/framework/skills/qualia-resume-work/SKILL.md +62 -0
  228. package/framework/skills/qualia-review/SKILL.md +263 -0
  229. package/framework/skills/qualia-start/SKILL.md +182 -0
  230. package/framework/skills/qualia-verify-work/SKILL.md +105 -0
  231. package/framework/skills/qualia-workflow/SKILL.md +130 -0
  232. package/framework/skills/rag/SKILL.md +750 -0
  233. package/framework/skills/responsive/SKILL.md +231 -0
  234. package/framework/skills/retro/SKILL.md +284 -0
  235. package/framework/skills/sakani-conventions/SKILL.md +136 -0
  236. package/framework/skills/sakani-conventions/evals/evals.json +23 -0
  237. package/framework/skills/sakani-conventions/references/entities.md +365 -0
  238. package/framework/skills/sakani-conventions/references/error-codes.md +95 -0
  239. package/framework/skills/seo-master/SKILL.md +490 -0
  240. package/framework/skills/seo-master/references/checklist.md +199 -0
  241. package/framework/skills/seo-master/references/structured-data.md +609 -0
  242. package/framework/skills/ship/SKILL.md +202 -0
  243. package/framework/skills/stack-researcher/SKILL.md +215 -0
  244. package/framework/skills/status/SKILL.md +154 -0
  245. package/framework/skills/status/scripts/health-check.sh +562 -0
  246. package/framework/skills/subscription-payments/SKILL.md +250 -0
  247. package/framework/skills/supabase/SKILL.md +973 -0
  248. package/framework/skills/supabase/references/templates.md +159 -0
  249. package/framework/skills/team/SKILL.md +67 -0
  250. package/framework/skills/test-runner/SKILL.md +202 -0
  251. package/framework/skills/voice-agent/SKILL.md +407 -0
  252. package/framework/skills/zoho-workflow/SKILL.md +51 -0
  253. package/framework/statusline-command.sh +117 -0
  254. package/package.json +24 -0
  255. package/profiles/fawzi.json +16 -0
  256. package/profiles/hasan.json +16 -0
  257. package/profiles/moayad.json +16 -0
  258. package/templates/CLAUDE-owner.md +52 -0
  259. package/templates/CLAUDE.md.hbs +58 -0
  260. package/templates/env.claude.template +12 -0
  261. package/templates/settings.json +141 -0
@@ -0,0 +1,973 @@
1
+ ---
2
+ name: supabase
3
+ description: Complete Supabase operations via CLI + Management API. Full replacement for 32 MCP tools with zero context overhead. Schema, migrations, auth, RLS, edge functions, SQL execution, debugging, storage, branching.
4
+ tags: [supabase, database, auth, rls, edge-functions, postgres]
5
+ ---
6
+
7
+ # Supabase (CLI-First, MCP-Free)
8
+
9
+ Full `supabase` CLI + Management API access. Every MCP tool is covered.
10
+
11
+ ## Project Discovery (ALWAYS DO FIRST)
12
+
13
+ ```bash
14
+ # 1. Check linked project
15
+ supabase projects list # → MCP: list_projects
16
+ supabase status # → MCP: get_project (shows URL, keys, DB URL)
17
+
18
+ # 2. Link if needed
19
+ supabase link --project-ref <ref>
20
+
21
+ # 3. Get full schema (ALWAYS read before modifying)
22
+ supabase db dump --schema public # → MCP: list_tables
23
+
24
+ # 4. Generate types
25
+ supabase gen types typescript --linked > types/supabase.ts # → MCP: generate_typescript_types
26
+ ```
27
+
28
+ ## Complete MCP Tool → CLI/API Map (All 32 Tools)
29
+
30
+ ### Account Tools (9)
31
+ | MCP Tool | Replacement |
32
+ |----------|-------------|
33
+ | `list_projects` | `supabase projects list` |
34
+ | `get_project` | `supabase status` or `supabase projects list` |
35
+ | `create_project` | `supabase projects create <name> --org-id <id> --db-password <pw> --region <region>` |
36
+ | `pause_project` | `curl -X POST "$SUPA_API/v1/projects/$REF/pause" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
37
+ | `restore_project` | `curl -X POST "$SUPA_API/v1/projects/$REF/restore" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
38
+ | `list_organizations` | `supabase orgs list` |
39
+ | `get_organization` | `supabase orgs list` |
40
+ | `get_cost` | `curl "$SUPA_API/v1/organizations/$ORG/billing/costs" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
41
+ | `confirm_cost` | Not needed — MCP-specific UX flow |
42
+
43
+ ### Database Tools (5)
44
+ | MCP Tool | Replacement |
45
+ |----------|-------------|
46
+ | `list_tables` | `supabase db dump --schema public` or `supabase inspect db table-stats` |
47
+ | `list_extensions` | Execute SQL: `SELECT extname, extversion FROM pg_extension` |
48
+ | `list_migrations` | `supabase migration list` |
49
+ | `apply_migration` | `supabase migration new <name>` → edit SQL → `supabase db push` |
50
+ | `execute_sql` | **See "Execute SQL" section below** |
51
+
52
+ ### Development Tools (3)
53
+ | MCP Tool | Replacement |
54
+ |----------|-------------|
55
+ | `get_project_url` | `supabase status` (shows API URL) |
56
+ | `get_publishable_keys` | `supabase projects api-keys` |
57
+ | `generate_typescript_types` | `supabase gen types typescript --linked` |
58
+
59
+ ### Edge Functions Tools (3)
60
+ | MCP Tool | Replacement |
61
+ |----------|-------------|
62
+ | `list_edge_functions` | `supabase functions list` |
63
+ | `get_edge_function` | `supabase functions download <name>` |
64
+ | `deploy_edge_function` | `supabase functions deploy <name>` |
65
+
66
+ ### Debugging Tools (2)
67
+ | MCP Tool | Replacement |
68
+ |----------|-------------|
69
+ | `get_logs` | `supabase functions logs <name> --tail` (functions) or Management API for other services |
70
+ | `get_advisors` | `supabase inspect db` (13 commands — BETTER than MCP advisors) |
71
+
72
+ ### Branching Tools (6)
73
+ | MCP Tool | Replacement |
74
+ |----------|-------------|
75
+ | `create_branch` | `supabase branches create <name>` |
76
+ | `list_branches` | `supabase branches list` |
77
+ | `delete_branch` | `supabase branches delete <name>` |
78
+ | `merge_branch` | `curl -X POST "$SUPA_API/v1/branches/$BRANCH_ID/merge" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
79
+ | `reset_branch` | `curl -X POST "$SUPA_API/v1/branches/$BRANCH_ID/reset" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
80
+ | `rebase_branch` | `curl -X POST "$SUPA_API/v1/branches/$BRANCH_ID/rebase" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
81
+
82
+ ### Storage Tools (3)
83
+ | MCP Tool | Replacement |
84
+ |----------|-------------|
85
+ | `list_storage_buckets` | Execute SQL: `SELECT * FROM storage.buckets` or `supabase storage ls` |
86
+ | `get_storage_config` | `curl "$SUPA_API/v1/projects/$REF/config/storage" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"` |
87
+ | `update_storage_config` | `curl -X PATCH "$SUPA_API/v1/projects/$REF/config/storage" -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" -d '{...}'` |
88
+
89
+ ### Knowledge Base (1)
90
+ | MCP Tool | Replacement |
91
+ |----------|-------------|
92
+ | `search_docs` | WebSearch or `WebFetch: https://supabase.com/llms/cli.txt` — see `docs-lookup` skill |
93
+
94
+ ## Execute SQL (Critical — MCP's Most Used Tool)
95
+
96
+ Three methods, in order of preference:
97
+
98
+ ### Method 1: Management API (recommended for remote)
99
+ ```bash
100
+ # Set once per session
101
+ SUPA_API="https://api.supabase.com"
102
+ REF="your-project-ref" # Get from `supabase status` or `supabase projects list`
103
+
104
+ # Execute any SQL
105
+ curl -s -X POST "$SUPA_API/v1/projects/$REF/database/query" \
106
+ -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
107
+ -H "Content-Type: application/json" \
108
+ -d '{"query": "SELECT * FROM users LIMIT 10"}'
109
+
110
+ # Read-only query (safer)
111
+ curl -s -X POST "$SUPA_API/v1/projects/$REF/database/query" \
112
+ -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
113
+ -H "Content-Type: application/json" \
114
+ -d '{"query": "SELECT * FROM users LIMIT 10", "read_only": true}'
115
+ ```
116
+
117
+ ### Method 2: psql (if connection string available)
118
+ ```bash
119
+ # Connection string from supabase status or .env
120
+ psql "postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres" \
121
+ -c "SELECT * FROM users LIMIT 10"
122
+ ```
123
+
124
+ ### Method 3: Migration (for DDL/schema changes)
125
+ ```bash
126
+ supabase migration new my_change
127
+ # Edit the SQL file, then:
128
+ supabase db push
129
+ ```
130
+
131
+ ## Service Logs (Beyond Edge Functions)
132
+
133
+ ```bash
134
+ # Edge function logs (CLI)
135
+ supabase functions logs <name> --tail
136
+
137
+ # Other service logs (API)
138
+ curl -s "$SUPA_API/v1/projects/$REF/analytics/endpoints/logs.all?iso_timestamp_start=$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
139
+ -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
140
+ ```
141
+
142
+ ## Database Inspection (13 commands — MCP had 0)
143
+
144
+ ```bash
145
+ supabase inspect db table-stats # Row counts, sizes
146
+ supabase inspect db index-stats # Index usage
147
+ supabase inspect db cache-hit # Buffer cache hit rates
148
+ supabase inspect db outliers # Slowest queries
149
+ supabase inspect db blocking # Lock contention
150
+ supabase inspect db locks # Exclusive locks
151
+ supabase inspect db long-running-queries # Queries > 5min
152
+ supabase inspect db bloat # Table/index bloat
153
+ supabase inspect db vacuum-stats # Autovacuum stats
154
+ supabase inspect db calls # Query frequency
155
+ supabase inspect db db-stats # Database stats
156
+ supabase inspect db replication-slots # Replication health
157
+ supabase inspect db traffic-profile # Read/write ratios
158
+ supabase inspect report # Full CSV report
159
+ ```
160
+
161
+ ## Environment Variable
162
+
163
+ The Management API requires `SUPABASE_ACCESS_TOKEN`. Set it:
164
+ ```bash
165
+ export SUPABASE_ACCESS_TOKEN="sbp_..." # Personal access token from supabase.com/dashboard/account/tokens
166
+ ```
167
+
168
+ Shorthand used above: `SUPA_API="https://api.supabase.com"`, `REF="project-ref"`
169
+
170
+ ## Key Rules
171
+
172
+ - ALWAYS enable RLS on every table
173
+ - ALWAYS write policies checking `auth.uid()`
174
+ - Never expose `service_role` key client-side
175
+ - Use `maybeSingle()` not `single()` when row might not exist
176
+ - Validate with Zod. Use PKCE auth flow for SSR/mobile.
177
+
178
+ ## References
179
+
180
+ - Code templates: read `@.claude/skills/supabase/references/templates.md`
181
+ - Latest CLI docs: `WebFetch: https://supabase.com/llms/cli.txt`
182
+ - Management API: `WebFetch: https://supabase.com/docs/reference/api/introduction`
183
+
184
+ ## Trigger Phrases
185
+
186
+ - "create table" / "debug RLS" / "optimize queries" / "inspect db"
187
+ - "supabase auth" / "edge function" / "deploy function" / "migration"
188
+ - "run SQL" / "query database" / "check logs" / "storage"
189
+
190
+ ---
191
+
192
+ ## When Things Go Wrong
193
+
194
+ ### Migration Fails
195
+
196
+ **Syntax error in SQL:**
197
+ ```bash
198
+ supabase db push 2>&1 # Read the exact Postgres error
199
+ # Fix the SQL file in supabase/migrations/, then retry:
200
+ supabase db push
201
+ ```
202
+
203
+ **Conflicting schema (column/table already exists):**
204
+ ```bash
205
+ # Check what's actually in the remote database
206
+ supabase db dump --schema public --project-ref <ref> | grep "table_or_column_name"
207
+ # If migration is stale, mark it as applied without running:
208
+ supabase migration repair <version> --status applied
209
+ ```
210
+
211
+ **RLS blocks the migration (permission denied during ALTER):**
212
+ Migrations run as the `postgres` superuser, so RLS shouldn't block DDL. If you see permission errors:
213
+ ```bash
214
+ # Check if the migration is trying DML (INSERT/UPDATE) on an RLS-enabled table
215
+ # Fix: Use service_role or temporarily disable RLS in the migration:
216
+ ALTER TABLE my_table DISABLE ROW LEVEL SECURITY;
217
+ -- ... do the data migration ...
218
+ ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
219
+ ```
220
+
221
+ ### Migration Applied But Needs Rollback
222
+
223
+ Supabase has no built-in `migrate down`. The process is manual:
224
+
225
+ ```bash
226
+ # 1. Create a reverse migration
227
+ supabase migration new rollback_my_change
228
+
229
+ # 2. Write the inverse SQL
230
+ # Added a column? → ALTER TABLE x DROP COLUMN y;
231
+ # Created a table? → DROP TABLE x;
232
+ # Changed a type? → ALTER TABLE x ALTER COLUMN y TYPE old_type;
233
+
234
+ # 3. Push the reverse migration
235
+ supabase db push --project-ref <ref>
236
+ ```
237
+
238
+ For data-destructive rollbacks (dropped column with data):
239
+ - If you have a backup: restore from Supabase dashboard → Database → Backups
240
+ - If no backup: the data is gone. This is why you test migrations on a branch first:
241
+ ```bash
242
+ supabase branches create test-migration
243
+ # Test there, then merge or delete
244
+ ```
245
+
246
+ ### RLS Policy Silently Blocking Queries
247
+
248
+ Symptoms: Query returns empty results, no error. The data exists but RLS filters it out.
249
+
250
+ **Step 1: Confirm it's RLS (bypass with service_role):**
251
+ ```bash
252
+ # This uses service_role which bypasses RLS
253
+ curl -s "https://<ref>.supabase.co/rest/v1/my_table?select=*&limit=5" \
254
+ -H "apikey: <service_role_key>" \
255
+ -H "Authorization: Bearer <service_role_key>"
256
+ # If this returns data but your app doesn't → it's RLS
257
+ ```
258
+
259
+ **Step 2: Check existing policies:**
260
+ ```sql
261
+ SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check
262
+ FROM pg_policies
263
+ WHERE tablename = 'my_table';
264
+ ```
265
+
266
+ **Step 3: Test the policy logic:**
267
+ ```sql
268
+ -- Simulate as a specific user
269
+ SET request.jwt.claim.sub = 'user-uuid-here';
270
+ SET role authenticated;
271
+ SELECT * FROM my_table; -- Does this return the expected rows?
272
+ RESET role;
273
+ ```
274
+
275
+ **Common RLS bugs:**
276
+ - Policy uses `auth.uid()` but the user isn't authenticated (anon request)
277
+ - Policy checks a column that's NULL (NULL != NULL in Postgres, use `IS NOT DISTINCT FROM`)
278
+ - Missing policy for the specific operation (e.g., SELECT policy exists but not INSERT)
279
+ - `FOR ALL` policy doesn't cover all operations — it's actually `USING` only, add `WITH CHECK` for writes
280
+
281
+ ### Edge Function Deployment Fails
282
+
283
+ **Import errors (module not found in Deno):**
284
+ ```bash
285
+ supabase functions deploy my-function 2>&1
286
+ # Common fix: use full URLs for Deno imports
287
+ # Wrong: import { z } from "zod"
288
+ # Right: import { z } from "https://deno.land/x/zod/mod.ts"
289
+ # Or use import_map.json in supabase/functions/
290
+ ```
291
+
292
+ **Missing env vars in Edge Functions:**
293
+ ```bash
294
+ # List secrets
295
+ supabase secrets list
296
+ # Set missing secrets
297
+ supabase secrets set MY_VAR=my_value
298
+ # Required for most functions:
299
+ supabase secrets set SUPABASE_URL=https://<ref>.supabase.co
300
+ supabase secrets set SUPABASE_SERVICE_ROLE_KEY=<key>
301
+ ```
302
+
303
+ **Cold start timeout (function takes > 10s on first call):**
304
+ - Keep functions small — large bundles = slow cold starts
305
+ - Avoid heavy imports at module level (lazy-load if possible)
306
+ - Use `supabase functions serve` locally to test performance
307
+ - For critical paths: set up a cron to warm the function every 5 min
308
+
309
+ **Deployment hangs or times out:**
310
+ ```bash
311
+ # Check function size
312
+ du -sh supabase/functions/my-function/
313
+ # If > 10MB, you're importing too much. Trim dependencies.
314
+ # Retry deployment:
315
+ supabase functions deploy my-function --no-verify-jwt # If JWT verify is blocking
316
+ ```
317
+
318
+ ### Connection Pool Exhausted
319
+
320
+ Symptoms: `FATAL: too many connections for role "postgres"` or queries timing out intermittently.
321
+
322
+ **Diagnose:**
323
+ ```bash
324
+ # Check active connections
325
+ supabase inspect db connections # If available
326
+ # Or via SQL:
327
+ # SELECT count(*) FROM pg_stat_activity WHERE state = 'active';
328
+ # SELECT count(*) FROM pg_stat_activity;
329
+ ```
330
+
331
+ **Fix:**
332
+ 1. Use the **pooler** connection string (port 6543) instead of direct (port 5432)
333
+ 2. In your app: ensure you're using a single Supabase client instance, not creating one per request
334
+ 3. Close idle connections:
335
+ ```sql
336
+ SELECT pg_terminate_backend(pid)
337
+ FROM pg_stat_activity
338
+ WHERE state = 'idle'
339
+ AND query_start < NOW() - INTERVAL '5 minutes';
340
+ ```
341
+ 4. If on a small plan: upgrade compute or reduce max connections per service
342
+
343
+ ### Type Generation Fails After Migration
344
+
345
+ Symptoms: `supabase gen types typescript` returns stale types or errors.
346
+
347
+ ```bash
348
+ # Force refresh (skip cache)
349
+ supabase gen types typescript --linked > types/supabase.ts
350
+
351
+ # If it still shows old schema, the migration might not be applied:
352
+ supabase migration list --project-ref <ref>
353
+
354
+ # If migration is applied but types are stale:
355
+ supabase db push --project-ref <ref> # Ensure remote is synced
356
+ supabase gen types typescript --project-id <ref> > types/supabase.ts
357
+
358
+ # Nuclear option: regenerate from scratch
359
+ rm types/supabase.ts
360
+ supabase gen types typescript --linked > types/supabase.ts
361
+ npx tsc --noEmit # Verify types compile
362
+ ```
363
+
364
+ ### Management API Timeout or 500
365
+
366
+ The Management API (`api.supabase.com`) occasionally has issues.
367
+
368
+ **Retry strategy:**
369
+ ```bash
370
+ # Simple retry with backoff
371
+ for i in 1 2 3; do
372
+ result=$(curl -s -w "%{http_code}" -o /tmp/supa-response \
373
+ "$SUPA_API/v1/projects/$REF/database/query" \
374
+ -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
375
+ -H "Content-Type: application/json" \
376
+ -d '{"query": "SELECT 1"}')
377
+ if [[ "$result" == "200" ]]; then cat /tmp/supa-response; break; fi
378
+ echo "Attempt $i failed ($result), retrying..."
379
+ sleep $((i * 5))
380
+ done
381
+ ```
382
+
383
+ **Fallback to CLI:**
384
+ ```bash
385
+ # If Management API is down, use CLI directly (uses different endpoints)
386
+ supabase db execute --project-ref <ref> "SELECT 1"
387
+ # Or connect via psql if you have the connection string
388
+ psql "$DATABASE_URL" -c "SELECT 1"
389
+ ```
390
+
391
+ **Check Supabase status:** https://status.supabase.com
392
+
393
+ ## Multi-Role RLS Policies
394
+
395
+ Sakani-style multi-role RBAC where roles are stored in separate tables (not JWT claims). This pattern supports 8 distinct roles:
396
+
397
+ **Building-scoped roles** (in `building_role_assignment` table):
398
+ - `BM_VERIFIED` — verified building manager
399
+ - `BM_CANDIDATE` — manager in review
400
+ - `GUARD` — building security guard
401
+ - `DEV_ADMIN` — staff admin (platform level)
402
+
403
+ **Unit-scoped roles** (in `unit_membership` table):
404
+ - `OWNER_VERIFIED` — verified unit owner
405
+ - `TENANT` — active tenant
406
+
407
+ All policies check `auth.uid()` and require an active role status. **Deny by default**: no permissive policies without explicit role validation.
408
+
409
+ ### Unit-Scoped Read (Owner/Tenant)
410
+
411
+ Unit members can read data for their own unit:
412
+
413
+ ```sql
414
+ CREATE POLICY "unit_members_read_own" ON target_table
415
+ FOR SELECT USING (
416
+ EXISTS (
417
+ SELECT 1 FROM unit_membership um
418
+ WHERE um.unit_id = target_table.unit_id
419
+ AND um.user_id = auth.uid()
420
+ AND um.status = 'ACTIVE'
421
+ )
422
+ );
423
+ ```
424
+
425
+ ### Building-Scoped Read (Building Manager)
426
+
427
+ Building managers (verified or candidate) can read all data in their assigned building:
428
+
429
+ ```sql
430
+ CREATE POLICY "bm_read_building" ON target_table
431
+ FOR SELECT USING (
432
+ EXISTS (
433
+ SELECT 1 FROM building_role_assignment bra
434
+ WHERE bra.building_id = target_table.building_id
435
+ AND bra.user_id = auth.uid()
436
+ AND bra.role IN ('BM_VERIFIED', 'BM_CANDIDATE')
437
+ AND bra.status = 'ACTIVE'
438
+ )
439
+ );
440
+ ```
441
+
442
+ ### Guard-Scoped Read (Assigned Guard)
443
+
444
+ Guards can read only items explicitly assigned to them, within their building:
445
+
446
+ ```sql
447
+ CREATE POLICY "guard_read_assigned" ON target_table
448
+ FOR SELECT USING (
449
+ target_table.assigned_to = auth.uid()
450
+ AND EXISTS (
451
+ SELECT 1 FROM building_role_assignment bra
452
+ WHERE bra.building_id = target_table.building_id
453
+ AND bra.user_id = auth.uid()
454
+ AND bra.role = 'GUARD'
455
+ AND bra.status = 'ACTIVE'
456
+ )
457
+ );
458
+ ```
459
+
460
+ ### Staff Admin Global Read
461
+
462
+ Platform staff admins can read all data across all buildings:
463
+
464
+ ```sql
465
+ CREATE POLICY "staff_read_all" ON target_table
466
+ FOR SELECT USING (
467
+ EXISTS (
468
+ SELECT 1 FROM building_role_assignment bra
469
+ WHERE bra.user_id = auth.uid()
470
+ AND bra.role = 'DEV_ADMIN'
471
+ AND bra.status = 'ACTIVE'
472
+ )
473
+ );
474
+ ```
475
+
476
+ ### Write Policies
477
+
478
+ Write operations are more restrictive. Examples:
479
+
480
+ **Owners create for their own unit:**
481
+ ```sql
482
+ CREATE POLICY "owner_create_own_unit" ON target_table
483
+ FOR INSERT WITH CHECK (
484
+ EXISTS (
485
+ SELECT 1 FROM unit_membership um
486
+ WHERE um.unit_id = target_table.unit_id
487
+ AND um.user_id = auth.uid()
488
+ AND um.role = 'OWNER_VERIFIED'
489
+ AND um.status = 'ACTIVE'
490
+ )
491
+ );
492
+ ```
493
+
494
+ **Building managers create for their building:**
495
+ ```sql
496
+ CREATE POLICY "bm_create_building" ON target_table
497
+ FOR INSERT WITH CHECK (
498
+ EXISTS (
499
+ SELECT 1 FROM building_role_assignment bra
500
+ WHERE bra.building_id = target_table.building_id
501
+ AND bra.user_id = auth.uid()
502
+ AND bra.role IN ('BM_VERIFIED', 'BM_CANDIDATE')
503
+ AND bra.status = 'ACTIVE'
504
+ )
505
+ );
506
+ ```
507
+
508
+ **Guards update their assigned items:**
509
+ ```sql
510
+ CREATE POLICY "guard_update_assigned" ON target_table
511
+ FOR UPDATE USING (
512
+ target_table.assigned_to = auth.uid()
513
+ AND EXISTS (
514
+ SELECT 1 FROM building_role_assignment bra
515
+ WHERE bra.building_id = target_table.building_id
516
+ AND bra.user_id = auth.uid()
517
+ AND bra.role = 'GUARD'
518
+ AND bra.status = 'ACTIVE'
519
+ )
520
+ ) WITH CHECK (
521
+ target_table.assigned_to = auth.uid()
522
+ );
523
+ ```
524
+
525
+ ### Composite Multi-Role Example
526
+
527
+ A ticket table where owners create for their unit, building managers read all in building, guards read assigned, staff reads all:
528
+
529
+ ```sql
530
+ -- READ: Unit members (owners/tenants)
531
+ CREATE POLICY "ticket_unit_read" ON ticket
532
+ FOR SELECT USING (
533
+ EXISTS (
534
+ SELECT 1 FROM unit_membership um
535
+ WHERE um.unit_id = ticket.unit_id
536
+ AND um.user_id = auth.uid()
537
+ AND um.status = 'ACTIVE'
538
+ )
539
+ );
540
+
541
+ -- READ: Building managers
542
+ CREATE POLICY "ticket_bm_read" ON ticket
543
+ FOR SELECT USING (
544
+ EXISTS (
545
+ SELECT 1 FROM building_role_assignment bra
546
+ WHERE bra.building_id = (
547
+ SELECT building_id FROM unit WHERE id = ticket.unit_id
548
+ )
549
+ AND bra.user_id = auth.uid()
550
+ AND bra.role IN ('BM_VERIFIED', 'BM_CANDIDATE')
551
+ AND bra.status = 'ACTIVE'
552
+ )
553
+ );
554
+
555
+ -- READ: Assigned guards
556
+ CREATE POLICY "ticket_guard_read" ON ticket
557
+ FOR SELECT USING (
558
+ ticket.assigned_guard = auth.uid()
559
+ AND EXISTS (
560
+ SELECT 1 FROM building_role_assignment bra
561
+ WHERE bra.building_id = (
562
+ SELECT building_id FROM unit WHERE id = ticket.unit_id
563
+ )
564
+ AND bra.user_id = auth.uid()
565
+ AND bra.role = 'GUARD'
566
+ AND bra.status = 'ACTIVE'
567
+ )
568
+ );
569
+
570
+ -- READ: Staff admin global
571
+ CREATE POLICY "ticket_staff_read" ON ticket
572
+ FOR SELECT USING (
573
+ EXISTS (
574
+ SELECT 1 FROM building_role_assignment bra
575
+ WHERE bra.user_id = auth.uid()
576
+ AND bra.role = 'DEV_ADMIN'
577
+ AND bra.status = 'ACTIVE'
578
+ )
579
+ );
580
+
581
+ -- CREATE: Unit owners only
582
+ CREATE POLICY "ticket_owner_create" ON ticket
583
+ FOR INSERT WITH CHECK (
584
+ EXISTS (
585
+ SELECT 1 FROM unit_membership um
586
+ WHERE um.unit_id = ticket.unit_id
587
+ AND um.user_id = auth.uid()
588
+ AND um.role = 'OWNER_VERIFIED'
589
+ AND um.status = 'ACTIVE'
590
+ )
591
+ );
592
+
593
+ -- UPDATE: Assigned guards
594
+ CREATE POLICY "ticket_guard_update" ON ticket
595
+ FOR UPDATE USING (
596
+ ticket.assigned_guard = auth.uid()
597
+ ) WITH CHECK (
598
+ ticket.assigned_guard = auth.uid()
599
+ );
600
+ ```
601
+
602
+ ## Financial Table Patterns
603
+
604
+ Append-only financial ledger tables enforce immutability at the database level, preventing accidental or malicious modifications. This pattern ensures compliance and audit integrity.
605
+
606
+ ### Append-Only Table Setup
607
+
608
+ Create a financial table with NO update/delete permissions:
609
+
610
+ ```sql
611
+ CREATE TABLE ledger_entry (
612
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
613
+ building_id UUID NOT NULL REFERENCES building(id) ON DELETE RESTRICT,
614
+ unit_id UUID REFERENCES unit(id) ON DELETE RESTRICT,
615
+ account_id UUID NOT NULL REFERENCES account(id) ON DELETE RESTRICT,
616
+
617
+ -- Financial data
618
+ entry_type TEXT NOT NULL CHECK (entry_type IN ('CHARGE', 'PAYMENT', 'REVERSAL', 'ADJUSTMENT')),
619
+ amount NUMERIC NOT NULL,
620
+ currency TEXT NOT NULL DEFAULT 'JOD',
621
+ description TEXT,
622
+
623
+ -- Idempotency
624
+ request_id UUID NOT NULL UNIQUE,
625
+
626
+ -- Audit
627
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
628
+ created_by UUID NOT NULL REFERENCES auth.users(id),
629
+
630
+ -- Indexes for RLS and queries
631
+ CONSTRAINT positive_amount CHECK (amount > 0 OR entry_type = 'REVERSAL')
632
+ );
633
+
634
+ -- Enable RLS
635
+ ALTER TABLE ledger_entry ENABLE ROW LEVEL SECURITY;
636
+
637
+ -- Add indexes for common queries
638
+ CREATE INDEX idx_ledger_building_date ON ledger_entry(building_id, created_at DESC);
639
+ CREATE INDEX idx_ledger_account_date ON ledger_entry(account_id, created_at DESC);
640
+ CREATE INDEX idx_ledger_request_id ON ledger_entry(request_id);
641
+ ```
642
+
643
+ ### Prevent Mutations with Trigger
644
+
645
+ A trigger enforces append-only behavior even for the `service_role`:
646
+
647
+ ```sql
648
+ CREATE OR REPLACE FUNCTION prevent_ledger_mutation()
649
+ RETURNS TRIGGER AS $$
650
+ BEGIN
651
+ RAISE EXCEPTION 'ledger_entry is append-only. Use REVERSAL entries for corrections.';
652
+ END;
653
+ $$ LANGUAGE plpgsql;
654
+
655
+ CREATE TRIGGER enforce_append_only
656
+ BEFORE UPDATE OR DELETE ON ledger_entry
657
+ FOR EACH ROW EXECUTE FUNCTION prevent_ledger_mutation();
658
+ ```
659
+
660
+ ### Idempotency Key Pattern
661
+
662
+ Use `request_id` with a UNIQUE constraint to ensure duplicate requests produce the same entry:
663
+
664
+ ```sql
665
+ -- Insert with idempotency key
666
+ INSERT INTO ledger_entry (
667
+ building_id, account_id, entry_type, amount, currency,
668
+ description, request_id, created_by
669
+ )
670
+ VALUES (
671
+ 'bldg-123', 'acct-456', 'CHARGE', 150.00, 'JOD',
672
+ 'Monthly rent', 'req-789', auth.uid()
673
+ )
674
+ ON CONFLICT (request_id) DO NOTHING
675
+ RETURNING *;
676
+ ```
677
+
678
+ If the same `request_id` is submitted again, the row is silently skipped (idempotent). Check the result count to detect duplicates.
679
+
680
+ ### Standard Audit Columns
681
+
682
+ All financial tables should include:
683
+ - `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — exact timestamp of entry creation
684
+ - `created_by UUID NOT NULL REFERENCES auth.users(id)` — who created the entry
685
+ - `request_id UUID NOT NULL UNIQUE` — external transaction ID for idempotency
686
+
687
+ ### Monetary Value Conventions
688
+
689
+ - Use `NUMERIC` (not `FLOAT`) for all amounts to avoid floating-point errors:
690
+ ```sql
691
+ amount NUMERIC(10,2) NOT NULL
692
+ ```
693
+ - Always store currency explicitly (default to 'JOD' for Sakani):
694
+ ```sql
695
+ currency TEXT NOT NULL DEFAULT 'JOD'
696
+ ```
697
+ - Enforce positive amounts with CHECK constraints:
698
+ ```sql
699
+ CONSTRAINT amount_positive CHECK (amount > 0 OR entry_type IN ('REVERSAL', 'ADJUSTMENT'))
700
+ ```
701
+
702
+ ## Audit Logging
703
+
704
+ Immutable audit logs track all critical changes for compliance and forensic analysis.
705
+
706
+ ### Immutable Audit Table
707
+
708
+ ```sql
709
+ CREATE TABLE audit_log (
710
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
711
+ building_id UUID NOT NULL REFERENCES building(id) ON DELETE RESTRICT,
712
+
713
+ -- What changed
714
+ table_name TEXT NOT NULL,
715
+ record_id UUID NOT NULL,
716
+ event_type TEXT NOT NULL CHECK (event_type IN ('INSERT', 'UPDATE', 'DELETE', 'BULK_OPERATION')),
717
+
718
+ -- Who, when
719
+ user_id UUID NOT NULL REFERENCES auth.users(id),
720
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
721
+
722
+ -- Change details (JSON for flexibility)
723
+ old_values JSONB,
724
+ new_values JSONB,
725
+ change_summary TEXT,
726
+
727
+ -- Context
728
+ request_id UUID,
729
+ ip_address INET,
730
+ user_agent TEXT
731
+ );
732
+
733
+ -- Enable RLS (users see only their building's logs if managers)
734
+ ALTER TABLE audit_log ENABLE ROW LEVEL SECURITY;
735
+
736
+ -- Prevent mutations
737
+ CREATE TRIGGER audit_log_immutable
738
+ BEFORE UPDATE OR DELETE ON audit_log
739
+ FOR EACH ROW EXECUTE FUNCTION prevent_ledger_mutation();
740
+
741
+ -- Indexes for efficient querying
742
+ CREATE INDEX idx_audit_building_date ON audit_log(building_id, created_at DESC);
743
+ CREATE INDEX idx_audit_event_date ON audit_log(event_type, created_at DESC);
744
+ CREATE INDEX idx_audit_user ON audit_log(user_id, created_at DESC);
745
+ CREATE INDEX idx_audit_record ON audit_log(table_name, record_id, created_at DESC);
746
+ ```
747
+
748
+ ### Trigger-Based Logging
749
+
750
+ Automatically log changes to a critical table:
751
+
752
+ ```sql
753
+ CREATE OR REPLACE FUNCTION log_critical_changes()
754
+ RETURNS TRIGGER AS $$
755
+ BEGIN
756
+ INSERT INTO audit_log (
757
+ building_id, table_name, record_id, event_type,
758
+ user_id, old_values, new_values, change_summary
759
+ )
760
+ VALUES (
761
+ COALESCE(NEW.building_id, OLD.building_id),
762
+ TG_TABLE_NAME,
763
+ COALESCE(NEW.id, OLD.id),
764
+ TG_OP,
765
+ auth.uid(),
766
+ TO_JSONB(OLD),
767
+ TO_JSONB(NEW),
768
+ CASE TG_OP
769
+ WHEN 'DELETE' THEN 'Record deleted'
770
+ WHEN 'UPDATE' THEN 'Record updated: ' || (
771
+ SELECT STRING_AGG(key || ' changed', ', ')
772
+ FROM (SELECT key FROM JSONB_EACH(TO_JSONB(NEW)) n
773
+ WHERE NOT (TO_JSONB(OLD) -> key IS DISTINCT FROM n.value)) t
774
+ )
775
+ WHEN 'INSERT' THEN 'Record created'
776
+ END
777
+ );
778
+ RETURN COALESCE(NEW, OLD);
779
+ END;
780
+ $$ LANGUAGE plpgsql;
781
+
782
+ -- Attach to any critical table
783
+ CREATE TRIGGER audit_unit_changes
784
+ AFTER INSERT OR UPDATE OR DELETE ON unit
785
+ FOR EACH ROW EXECUTE FUNCTION log_critical_changes();
786
+ ```
787
+
788
+ ### Retention Policy
789
+
790
+ Financial and audit logs should be retained for **7 years** per most jurisdictions:
791
+
792
+ ```sql
793
+ -- Cleanup job (run via cron)
794
+ DELETE FROM audit_log
795
+ WHERE created_at < NOW() - INTERVAL '7 years'
796
+ AND building_id IN (
797
+ SELECT id FROM building WHERE status = 'ARCHIVED'
798
+ );
799
+ ```
800
+
801
+ ## Multi-Tenant Migration Patterns
802
+
803
+ Schema migrations for multi-tenant databases require careful planning to maintain data integrity and RLS security.
804
+
805
+ ### Enable RLS Immediately After CREATE TABLE
806
+
807
+ Never create a table without RLS:
808
+
809
+ ```sql
810
+ CREATE TABLE my_table (
811
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
812
+ building_id UUID NOT NULL REFERENCES building(id),
813
+ created_at TIMESTAMPTZ DEFAULT NOW()
814
+ );
815
+
816
+ -- IMMEDIATELY enable RLS
817
+ ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
818
+
819
+ -- Then define policies
820
+ CREATE POLICY "building_read" ON my_table
821
+ FOR SELECT USING (
822
+ EXISTS (
823
+ SELECT 1 FROM building_role_assignment bra
824
+ WHERE bra.building_id = my_table.building_id
825
+ AND bra.user_id = auth.uid()
826
+ AND bra.status = 'ACTIVE'
827
+ )
828
+ );
829
+ ```
830
+
831
+ ### Create Enum Types Before Tables
832
+
833
+ Define enums once, before any table references them:
834
+
835
+ ```sql
836
+ CREATE TYPE building_role AS ENUM (
837
+ 'BM_VERIFIED', 'BM_CANDIDATE', 'GUARD', 'DEV_ADMIN'
838
+ );
839
+
840
+ CREATE TYPE membership_status AS ENUM (
841
+ 'ACTIVE', 'INACTIVE', 'SUSPENDED'
842
+ );
843
+
844
+ -- Now create tables that use these enums
845
+ CREATE TABLE building_role_assignment (
846
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
847
+ building_id UUID NOT NULL REFERENCES building(id),
848
+ user_id UUID NOT NULL REFERENCES auth.users(id),
849
+ role building_role NOT NULL,
850
+ status membership_status NOT NULL DEFAULT 'ACTIVE',
851
+ UNIQUE(building_id, user_id, role)
852
+ );
853
+ ```
854
+
855
+ ### Index FK Columns Used in RLS Policies
856
+
857
+ RLS policies with subqueries require indexes on the join columns for acceptable performance:
858
+
859
+ ```sql
860
+ -- Without these indexes, every RLS check is a full table scan
861
+ CREATE INDEX idx_unit_membership_unit_user ON unit_membership(unit_id, user_id);
862
+ CREATE INDEX idx_building_role_assignment_building_user ON building_role_assignment(building_id, user_id);
863
+ CREATE INDEX idx_building_role_assignment_user ON building_role_assignment(user_id);
864
+
865
+ -- If status is checked in policies, include it in composite indexes
866
+ CREATE INDEX idx_building_role_status ON building_role_assignment(building_id, user_id, status);
867
+ ```
868
+
869
+ ### Complete Table Migration Example
870
+
871
+ A single migration creating a table with full RLS in one go:
872
+
873
+ ```sql
874
+ -- supabase/migrations/20240306_create_ticket_system.sql
875
+
876
+ -- 1. Enums
877
+ CREATE TYPE ticket_status AS ENUM ('OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED');
878
+ CREATE TYPE ticket_priority AS ENUM ('LOW', 'MEDIUM', 'HIGH', 'URGENT');
879
+
880
+ -- 2. Table with all constraints and defaults
881
+ CREATE TABLE ticket (
882
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
883
+ building_id UUID NOT NULL REFERENCES building(id) ON DELETE RESTRICT,
884
+ unit_id UUID NOT NULL REFERENCES unit(id) ON DELETE RESTRICT,
885
+ created_by UUID NOT NULL REFERENCES auth.users(id),
886
+ assigned_guard UUID REFERENCES auth.users(id),
887
+
888
+ title TEXT NOT NULL,
889
+ description TEXT,
890
+ status ticket_status NOT NULL DEFAULT 'OPEN',
891
+ priority ticket_priority NOT NULL DEFAULT 'MEDIUM',
892
+
893
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
894
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
895
+ resolved_at TIMESTAMPTZ,
896
+
897
+ CONSTRAINT title_not_empty CHECK (LENGTH(title) > 0)
898
+ );
899
+
900
+ -- 3. Indexes immediately (before RLS policies)
901
+ CREATE INDEX idx_ticket_building ON ticket(building_id);
902
+ CREATE INDEX idx_ticket_unit ON ticket(unit_id);
903
+ CREATE INDEX idx_ticket_created_by ON ticket(created_by);
904
+ CREATE INDEX idx_ticket_assigned_guard ON ticket(assigned_guard);
905
+ CREATE INDEX idx_ticket_status ON ticket(status, created_at DESC);
906
+
907
+ -- 4. Enable RLS
908
+ ALTER TABLE ticket ENABLE ROW LEVEL SECURITY;
909
+
910
+ -- 5. Define policies
911
+ CREATE POLICY "ticket_read_own_unit" ON ticket
912
+ FOR SELECT USING (
913
+ EXISTS (
914
+ SELECT 1 FROM unit_membership um
915
+ WHERE um.unit_id = ticket.unit_id
916
+ AND um.user_id = auth.uid()
917
+ AND um.status = 'ACTIVE'
918
+ )
919
+ );
920
+
921
+ CREATE POLICY "ticket_read_building" ON ticket
922
+ FOR SELECT USING (
923
+ EXISTS (
924
+ SELECT 1 FROM building_role_assignment bra
925
+ WHERE bra.building_id = ticket.building_id
926
+ AND bra.user_id = auth.uid()
927
+ AND bra.role IN ('BM_VERIFIED', 'BM_CANDIDATE')
928
+ AND bra.status = 'ACTIVE'
929
+ )
930
+ );
931
+
932
+ CREATE POLICY "ticket_read_assigned" ON ticket
933
+ FOR SELECT USING (
934
+ ticket.assigned_guard = auth.uid()
935
+ AND EXISTS (
936
+ SELECT 1 FROM building_role_assignment bra
937
+ WHERE bra.building_id = ticket.building_id
938
+ AND bra.user_id = auth.uid()
939
+ AND bra.role = 'GUARD'
940
+ AND bra.status = 'ACTIVE'
941
+ )
942
+ );
943
+
944
+ CREATE POLICY "ticket_create_owner" ON ticket
945
+ FOR INSERT WITH CHECK (
946
+ created_by = auth.uid()
947
+ AND EXISTS (
948
+ SELECT 1 FROM unit_membership um
949
+ WHERE um.unit_id = ticket.unit_id
950
+ AND um.user_id = auth.uid()
951
+ AND um.role = 'OWNER_VERIFIED'
952
+ AND um.status = 'ACTIVE'
953
+ )
954
+ );
955
+
956
+ CREATE POLICY "ticket_update_guard" ON ticket
957
+ FOR UPDATE USING (
958
+ ticket.assigned_guard = auth.uid()
959
+ ) WITH CHECK (
960
+ ticket.assigned_guard = auth.uid()
961
+ );
962
+
963
+ -- 6. Grant default permissions
964
+ GRANT SELECT, INSERT ON ticket TO authenticated;
965
+ GRANT UPDATE ON ticket TO authenticated;
966
+ ```
967
+
968
+ Then push with:
969
+ ```bash
970
+ supabase db push
971
+ ```
972
+
973
+ ---