@ojokesusu/lintasai 1.1.2

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 (86) hide show
  1. package/.github/workflows/publish-npm.yml +40 -0
  2. package/.github/workflows/validate.yml +93 -0
  3. package/AUDIT_POST_SETUP_PROMPT_v1.md +280 -0
  4. package/BOOTSTRAP_PROJECT_DOCS_PROMPT_v1.md +3 -0
  5. package/CHANGELOG.md +313 -0
  6. package/CLAUDE_universal_v1.md +1021 -0
  7. package/CONTRIBUTING.md +101 -0
  8. package/FIRST_SESSION_PROMPT_v1.md +7 -0
  9. package/JALANKAN_KIT.md +188 -0
  10. package/LICENSE +21 -0
  11. package/MULAI_DI_SINI.md +145 -0
  12. package/PROJECT_KICKOFF_PROMPT_v1.md +3 -0
  13. package/PROJECT_LIFECYCLE_PROMPT_v1.md +536 -0
  14. package/PROJECT_MIGRATION_PROMPT_v1.md +3 -0
  15. package/README.md +505 -0
  16. package/SETUP_POLA_B_PROMPT_v1.md +5 -0
  17. package/SPLIT_REPO_MIGRATION_PROMPT_v1.md +485 -0
  18. package/TEAM_ROLLOUT_GUIDE_v1.md +172 -0
  19. package/UPDATE_DOCS_PROMPT_v1.md +3 -0
  20. package/UPDATE_KIT_PROMPT_v1.md +213 -0
  21. package/bin/lintasai.js +81 -0
  22. package/docs/SIGNED_RELEASE.md +162 -0
  23. package/install-windows.ps1 +225 -0
  24. package/kit.ps1 +508 -0
  25. package/lib/agents-md.ps1 +174 -0
  26. package/lib/git-helpers.ps1 +104 -0
  27. package/lib/kit-files.psd1 +133 -0
  28. package/lib/manifest-signing.ps1 +65 -0
  29. package/lib/manifest.ps1 +267 -0
  30. package/lib/rollback.ps1 +241 -0
  31. package/lib/safety.ps1 +193 -0
  32. package/lib/template-deploy.ps1 +242 -0
  33. package/lib/version-detect.ps1 +161 -0
  34. package/package.json +36 -0
  35. package/setup-pola-b.ps1 +687 -0
  36. package/templates/ANALOGI_LIBRARY.md +7 -0
  37. package/templates/CLAUDE_TEAM_GUIDE.md +505 -0
  38. package/templates/CROSS_REPO_TYPES_PIPELINE.md +473 -0
  39. package/templates/DB_SCHEMA_SCAN_PROMPT.md +194 -0
  40. package/templates/DISCORD_BOT_INTEGRATION.md +187 -0
  41. package/templates/GLOSSARY_NON_PROGRAMMER.md +361 -0
  42. package/templates/INDEX.md +157 -0
  43. package/templates/MCP_SETUP.md +1145 -0
  44. package/templates/MIGRATE_TO_SUBFOLDER_PROMPT_v1.md +220 -0
  45. package/templates/ONBOARDING.md +172 -0
  46. package/templates/PROJECT_STARTER_TEMPLATES.md +264 -0
  47. package/templates/PROMPT_LIBRARY.md +790 -0
  48. package/templates/RLS_SETUP_PROMPT.md +167 -0
  49. package/templates/SECURITY_INCIDENT_PLAYBOOK.md +191 -0
  50. package/templates/SPLIT_REPO_AGENTS_TEMPLATES.md +32 -0
  51. package/templates/SPLIT_REPO_NON_PROGRAMMER_PROMPTS.md +604 -0
  52. package/templates/SPLIT_REPO_TOOLS_SETUP.md +388 -0
  53. package/templates/STACK_DETECTION_PATTERN.md +261 -0
  54. package/templates/STACK_GUIDE.md +564 -0
  55. package/templates/STACK_MIGRATION_GUIDE.md +154 -0
  56. package/templates/STACK_VERSIONS.md +31 -0
  57. package/templates/UPDATE_GUIDE.md +246 -0
  58. package/templates/_EXAMPLE.md +110 -0
  59. package/templates/_PATTERNS.md +173 -0
  60. package/templates/architecture.md +180 -0
  61. package/templates/architecture_auto.md +61 -0
  62. package/templates/decisions/README.md +108 -0
  63. package/templates/decisions/_TEMPLATE.md +84 -0
  64. package/templates/feature-flags-advanced.md +171 -0
  65. package/templates/github/CODEOWNERS.template +61 -0
  66. package/templates/github/GENERATE_TYPES_SCRIPT.md +77 -0
  67. package/templates/github/PUBLISH_SHARED_WORKFLOW.yml +52 -0
  68. package/templates/github/RECEIVE_BACKEND_UPDATE.yml +106 -0
  69. package/templates/github/RENOVATE_FRONTEND.json +28 -0
  70. package/templates/github/TRIGGER_FRONTEND_UPDATE.yml +29 -0
  71. package/templates/github/pull_request_template.md +44 -0
  72. package/templates/github/scripts/ai-review.js +153 -0
  73. package/templates/github/workflows/ai-review.yml +61 -0
  74. package/templates/github/workflows/backup-schemas.yml +169 -0
  75. package/templates/glossary.md +110 -0
  76. package/templates/split-agents/BACKEND.md +149 -0
  77. package/templates/split-agents/FRONTEND.md +141 -0
  78. package/templates/split-agents/SHARED.md +82 -0
  79. package/templates/split-agents/TOOLS.md +77 -0
  80. package/tests/Run-Tests.ps1 +19 -0
  81. package/tests/lib-safety.Tests.ps1 +66 -0
  82. package/tests/rollback.Tests.ps1 +66 -0
  83. package/tests/uninstall.Tests.ps1 +265 -0
  84. package/tests/update-kit.Tests.ps1 +78 -0
  85. package/uninstall.ps1 +794 -0
  86. package/update-kit.ps1 +907 -0
@@ -0,0 +1,1145 @@
1
+ # templates/MCP_SETUP.md — Setup MCP Servers untuk Tim AI-first
2
+
3
+ > Versi 1 · 2026-06-01
4
+
5
+ ---
6
+
7
+ ## Daftar Isi
8
+
9
+ - [0. Decision Tree — Pilih MCP yang Mana?](#0-decision-tree--pilih-mcp-yang-mana)
10
+ - [1. Pengantar](#1-pengantar)
11
+ - [2. PostgreSQL MCP dengan Schema Isolation](#2-postgresql-mcp-dengan-schema-isolation)
12
+ - [2.1 Phase-Based DB Environment Strategy](#21-phase-based-db-environment-strategy-pilih-berdasarkan-stage-project)
13
+ - [2.2 Seed Data Strategy](#22-seed-data-strategy--penting-untuk-ai-context-quality)
14
+ - [2.3 Multi-Layer Backup Strategy](#23-multi-layer-backup-strategy-defense-in-depth)
15
+ - [2.4 Pilih Multi-Schema Strategy](#24-pilih-multi-schema-strategy--sesuai-stage-project)
16
+ - [2.5 Setup PostgreSQL Role per-User — Option A](#25-setup-postgresql-role-per-user--option-a-owner-only-step)
17
+ - [2.6 Setup Per-Staff Isolated Schema — Option B](#26-setup-per-staff-isolated-schema--option-b-untuk-development)
18
+ - [2.7 Setup Hybrid Strategy — Option C](#27-setup-hybrid-strategy--option-c-sandbox--read-prod)
19
+ - [2.8 Backup Plan untuk Option B & C](#28-backup-plan-untuk-option-b--c-wajib)
20
+ - [2.9 Restore Flow](#29-restore-flow-kalau-staff-accidentally-drop-table)
21
+ - [2.10 Promote Sandbox Schema → Production Schema](#210-promote-sandbox-schema--production-schema-saat-fitur-ready)
22
+ - [2.11 Verifikasi Schema Isolation](#211-verifikasi-schema-isolation)
23
+ - [2.12 Connection String per-Dev](#212-connection-string-per-dev-format-benar--supabase-pooler)
24
+ - [2.13 RLS Setup Workflow](#213-rls-setup-workflow-wajib-sebelum-data-produksi-masuk)
25
+ - [2.14 MCP Config di Claude Code](#214-mcp-config-di-claude-code)
26
+ - [2.15 Audit Trail](#215-audit-trail)
27
+ - [3. Supabase MCP (Owner Only — WARNING)](#3-supabase-mcp-owner-only--warning)
28
+ - [4. GitHub MCP](#4-github-mcp)
29
+ - [5. Filesystem MCP (Default Claude Code)](#5-filesystem-mcp-default-claude-code)
30
+ - [6. Anti-Pattern (Jangan Lakukan)](#6-anti-pattern-jangan-lakukan)
31
+ - [7. Audit + Rotation](#7-audit--rotation)
32
+ - [8. Troubleshooting](#8-troubleshooting)
33
+
34
+ ---
35
+
36
+ ## 0. Decision Tree — Pilih MCP yang Mana?
37
+
38
+ ```
39
+ Kamu siapa?
40
+ ├─ Staff IT / Developer schema-scoped
41
+ │ → Pakai PostgreSQL MCP (Section 2) ⭐ DEFAULT
42
+ │ → Username: creative_<dev>.<project-ref> via Supabase pooler
43
+ │ → Akses HANYA schema kamu (terisolasi dari tenant lain)
44
+ │ → WAJIB setup RLS di schema kamu sebelum data masuk (Section 2.13)
45
+
46
+ └─ Owner / Admin database
47
+ → Boleh PostgreSQL MCP (Section 2) untuk operasi sehari-hari
48
+ → Boleh Supabase MCP (Section 3) untuk debug owner-level
49
+ ⚠️ Supabase MCP pakai service_role_key = BYPASS RLS = SUPERUSER
50
+ ⚠️ JANGAN share key ini ke staff IT
51
+ ⚠️ Hanya pakai saat butuh akses lintas-schema (audit, migrasi global)
52
+ ```
53
+
54
+ **Aturan ketat untuk Staff IT**:
55
+ - ❌ JANGAN pakai username `postgres.<ref>` (= superuser, akses SEMUA tenant)
56
+ - ❌ JANGAN pakai `service_role_key` (= bypass RLS, akses data user lain)
57
+ - ✅ PAKAI username `creative_<dev>.<project-ref>` dengan password kamu sendiri
58
+ - ✅ PAKAI Supabase pooler URL (port 6543 untuk app, 5432 untuk DDL/migrasi)
59
+
60
+ ---
61
+
62
+ ## 1. Pengantar
63
+
64
+ ### Apa itu MCP?
65
+
66
+ **MCP (Model Context Protocol)** = standar protokol yang dibikin Anthropic untuk hubungkan AI (Claude Code) ke **tool eksternal**: database, GitHub, filesystem, API custom, dll.
67
+
68
+ Analogi: kalau Claude Code = otak, MCP server = tangan & mata. Tanpa MCP, AI cuma bisa baca yang user paste manual. Dengan MCP, AI bisa query DB sendiri, baca issue GitHub, edit file di workspace.
69
+
70
+ ### Kenapa standardize?
71
+
72
+ Kalau tiap dev setup MCP beda-beda → AI assistant tiap orang punya "power" beda → hasil kerja inconsistent + risiko keamanan beda.
73
+
74
+ Standar tim AI-first:
75
+
76
+ - **Semua dev** pakai MCP yang sama (PostgreSQL + GitHub + Filesystem).
77
+ - **Tiap dev** punya kredensial sendiri (PostgreSQL role per-user, GitHub PAT per-user).
78
+ - **Tidak ada** akses superuser via MCP (cegah privilege escalation).
79
+ - **Audit trail** aktif (siapa query apa, kapan).
80
+
81
+ > *Privilege escalation* = naik level akses tanpa izin. Mis. user biasa bisa jadi admin lewat bug → cegah dengan least-privilege di awal.
82
+
83
+ ---
84
+
85
+ ## 2. PostgreSQL MCP dengan Schema Isolation
86
+
87
+ ### Latar belakang security
88
+
89
+ **Jangan pakai Supabase MCP** (yang official) untuk staff IT. Alasan:
90
+
91
+ - Supabase MCP butuh **service_role_key** → bypass RLS (Row-Level Security) → AI bisa baca/edit data user lain.
92
+ - Service_role_key = full superuser → tidak bisa di-scope per-orang.
93
+ - Audit trail terbatas (semua query muncul atas nama service_role).
94
+
95
+ **Pakai `@modelcontextprotocol/server-postgres`** dengan **PostgreSQL role per-user** + **schema isolation**. Tiap dev cuma bisa akses schema yang relevan dengan kerjaannya.
96
+
97
+ ### 2.1. Phase-Based DB Environment Strategy (PILIH BERDASARKAN STAGE PROJECT)
98
+
99
+ **SEBELUM milih multi-schema option**, tentukan dulu **DB environment strategy** sesuai stage project kamu. Ini KRITIKAL — salah pilih = staff bisa accidentally rusak data prod.
100
+
101
+ #### 🚦 Aturan Emas (Conditional, BUKAN Absolut)
102
+
103
+ **TIDAK ADA staff non-programmer yang boleh punya WRITE access ke schema PROD — KALAU ada user real / payment masuk / PII customer.**
104
+
105
+ Aturan ini BUKAN absolut. Untuk **early-stage project tanpa user real**, aturan ini TIDAK berlaku karena:
106
+ - Belum ada PII customer yang harus dilindungi (compliance N/A)
107
+ - Belum ada revenue stream yang loss kalau down
108
+ - Belum ada reputation risk (no users tahu kalau ada incident)
109
+ - Backup berlapis cukup mitigasi risk DROP TABLE (recoverable <1 hari)
110
+ - Velocity early-stage > Safety overhead
111
+
112
+ Backup mengurangi DAMPAK kalau bencana terjadi. Access control CEGAH bencana terjadi. **Kedua-duanya perlu saat ada user real**. Sebelum itu — backup berlapis sudah cukup.
113
+
114
+ #### Phase 0 — Pre-Launch (Project Belum Punya User Real) ⭐ KAMU SEKARANG
115
+
116
+ **Setup**: **1 Supabase project = single environment** (technically called "DEV" tapi sebenarnya satu-satunya DB yang ada)
117
+
118
+ ```
119
+ Supabase Project: akses (single environment)
120
+ ├── Schema creative_a (Bagus owner — full CREATE/SELECT/INSERT/UPDATE/DELETE)
121
+ ├── Schema creative_b (Andi owner — full akses)
122
+ ├── Schema creative_c (Citra owner — full akses)
123
+ ├── Schema bigseo (shared "main" schema — semua staff FULL akses sementara)
124
+ └── Schema public (default PostgreSQL)
125
+ ```
126
+
127
+ **Staff IT dapat full WRITE access ke schema bigseo**, plus schema mereka sendiri. Aturan emas "no write prod" **TIDAK berlaku** di phase ini.
128
+
129
+ **Strategy**:
130
+ - Belum ada user real = belum ada PROD secara konsep
131
+ - Staff full akses Option B atau C (per-staff schema isolated) + shared bigseo
132
+ - Faker library untuk seed data realistic (penting untuk AI context, lihat 2.2 di bawah)
133
+ - Backup 3-5 layer aktif (Supabase Daily + per-schema pg_dump + Cloudflare R2 + BackBlaze B2)
134
+ - Owner masih kontrol Prisma migration via Git PR (gatekeeper untuk DDL structure)
135
+
136
+ **Kapan TRANSISI ke Phase 1 (split DEV + PROD)?**
137
+
138
+ Trigger transisi (ANY of these → IMMEDIATELY split):
139
+ - 🔴 **First real user signup** (bukan owner/staff IT)
140
+ - 🔴 **First payment / transaction masuk** (revenue stream aktif)
141
+ - 🔴 **First PII customer data masuk** (email/NPWP/alamat real)
142
+ - 🔴 **Compliance requirement aktif** (GDPR, UU PDP, SOC2, klien minta audit)
143
+ - 🔴 **SLA dengan klien eksternal** (uptime/response time contract)
144
+
145
+ Saat salah satu trigger di atas terjadi → IMMEDIATELY setup Phase 1 (split DEV + PROD).
146
+
147
+ **Cocok untuk akses sekarang (5% progress)**. Pakai Phase 0 sampai salah satu trigger di atas.
148
+
149
+ #### Phase 1 — Soon Launch / Early Users (Salah Satu Trigger Phase 0 Terpenuhi)
150
+
151
+ **Setup**: **2 Supabase project terpisah** (free tier cukup, max 2 project)
152
+
153
+ ```
154
+ Supabase Project: akses-dev (existing, downgrade dari "akses" yang dulu)
155
+ └── Staff full access (creative_a/b/c + bigseo)
156
+
157
+ Supabase Project: akses-prod (BARU, dispawn saat trigger Phase 0 hit)
158
+ └── Owner only — staff TIDAK punya akses
159
+ ```
160
+
161
+ **Transisi Phase 0 → Phase 1 step-by-step**:
162
+ 1. Spin up Supabase project baru `akses-prod`
163
+ 2. Export schema dari `akses` (current) via `pg_dump --schema-only`
164
+ 3. Import schema-only ke `akses-prod` (no data, fresh start)
165
+ 4. Setup user real signup di app pakai env var DATABASE_URL → `akses-prod`
166
+ 5. Restrict staff: revoke akses ke `akses-prod`, mereka stay di `akses-dev` (rename `akses` → `akses-dev`)
167
+ 6. Vercel preview deploy → connect `akses-dev` DB
168
+ Vercel production deploy → connect `akses-prod` DB (env var per-environment)
169
+ 7. Setup migration workflow: owner approve PR yang ubah schema → Vercel build run `prisma migrate deploy` ke `akses-prod`
170
+
171
+ **Aturan emas WAJIB berlaku** mulai Phase 1: zero staff write access ke `akses-prod`.
172
+
173
+ #### Phase 2 — Post-Launch Scale (>1000 Users atau Multiple Domain)
174
+
175
+ **Setup**: 3 environment (DEV + STAGING + PROD) **ATAU** Supabase Pro Branching
176
+
177
+ ```
178
+ Option A: 3 environment manual
179
+ - DEV: staff develop
180
+ - STAGING: owner test merged code dengan data near-prod (sanitized snapshot)
181
+ - PROD: live users
182
+
183
+ Option B: Supabase Pro ($25/bulan) + Branching
184
+ - Per-PR auto-create DB branch (ephemeral)
185
+ - Staff develop di branch DB-nya sendiri
186
+ - Merge to main = auto-apply migration ke prod
187
+ ```
188
+
189
+ **Untuk akses spesifik**: belum perlu Phase 2 sampai user 1000+ atau monthly active users tinggi.
190
+
191
+ ### 2.2. Seed Data Strategy — Penting untuk AI Context Quality
192
+
193
+ **Masalah**: Kalau DEV environment cuma punya "Test User 1-5", AI tidak dapat context realistic → bisa miss edge cases (nama dengan apostrophe, panjang nama, format NPWP, distribusi performa real).
194
+
195
+ **Solusi**: Seed data realistic via Faker library (Indonesian locale).
196
+
197
+ ```typescript
198
+ // prisma/seed.ts (jalankan: npx prisma db seed)
199
+ import { PrismaClient } from '@prisma/client'
200
+ import { faker } from '@faker-js/faker/locale/id_ID'
201
+
202
+ const prisma = new PrismaClient()
203
+
204
+ async function main() {
205
+ // 1000 user Indonesia dengan distribusi realistic
206
+ for (let i = 0; i < 1000; i++) {
207
+ await prisma.user.create({
208
+ data: {
209
+ nama: faker.person.fullName(), // "Bagus Setiawan", "Sri Wahyuni"
210
+ email: faker.internet.email(),
211
+ npwp: faker.numerics({ length: 16 }),
212
+ alamat: faker.location.streetAddress(),
213
+ created_at: faker.date.past({ years: 2 }),
214
+ }
215
+ })
216
+ }
217
+
218
+ // 10000 transaksi dengan range tanggal & amount realistic
219
+ // dst untuk relasi yang penting
220
+ }
221
+
222
+ main()
223
+ ```
224
+
225
+ **Setup `package.json`**:
226
+
227
+ ```json
228
+ {
229
+ "prisma": {
230
+ "seed": "tsx prisma/seed.ts"
231
+ }
232
+ }
233
+ ```
234
+
235
+ **Jalankan**: `npx prisma db seed` setiap kali setup DEV environment baru atau reset data.
236
+
237
+ **Hasil**: AI dapat context realistic → catch edge cases yang mirip prod → fitur lebih robust saat naik ke PROD.
238
+
239
+ **Phase 2+ Alternative**: Sanitized snapshot prod → DEV (monthly basis). Owner export prod, jalankan script masking (mask email, hash NPWP, ganti nama Faker), restore ke DEV.
240
+
241
+ ### 2.3. Multi-Layer Backup Strategy (Defense in Depth)
242
+
243
+ Backup berlapis untuk redundancy + selective restore:
244
+
245
+ | Layer | Tool | Frekuensi | Retention | Selective Restore? | Cost |
246
+ |---|---|---|---|---|---|
247
+ | **L1: Supabase Daily Backup** | Built-in (free tier) | Daily otomatis | 7 hari (free) / 30 hari (Pro) | ❌ Full project only | Gratis |
248
+ | **L2: Manual Snapshot** | Owner via Supabase Dashboard | Pre-migration / risky op | Sampai owner delete | ❌ Full project only | Gratis |
249
+ | **L3: Per-Schema pg_dump** ⭐ | GitHub Action cron 03:00 WIB | Daily otomatis | 30 hari | ✅ **Per-schema selective** | ~$1/bulan storage |
250
+ | **L4: Off-site Backup R2** | Cloudflare R2 cron sync | Daily mirror dari L3 | 90 hari | ✅ Per-schema | ~$0.5/bulan |
251
+ | **L5: BackBlaze B2 Archive** | Monthly cold storage | Monthly | 1 tahun | ✅ Per-schema | ~$0.2/bulan |
252
+
253
+ **Total backup cost**: ~$2/bulan untuk redundancy berlapis. Insurance terbaik.
254
+
255
+ **Konfigurasi**: Lihat `.github/workflows/backup-schemas.yml` (L3) + setup manual untuk L4 + L5.
256
+
257
+ ### 2.4. Pilih Multi-Schema Strategy — Sesuai Stage Project
258
+
259
+ **Sebelum lanjut setup**, pilih strategi yang cocok dengan stage project + level trust tim:
260
+
261
+ #### Option A — Shared Schema, Restricted CREATE (Production / Mature Project)
262
+
263
+ **Setup**: Semua staff pakai 1 schema bersama (mis. `pbn` atau `bigseo`). Owner kontrol DDL via Prisma migration di Git.
264
+
265
+ | Aspek | Detail |
266
+ |---|---|
267
+ | Schema | `pbn` (1 schema shared) |
268
+ | Staff permission | SELECT, INSERT, UPDATE, DELETE (CRUD data) — **TIDAK** punya CREATE TABLE |
269
+ | Schema change | Lewat Git PR + Prisma migrate, owner approve |
270
+ | Risk | Low — staff tidak bisa rusak struktur DB accidentally |
271
+ | Bottleneck owner | Code review PR (sama dengan code change biasa) |
272
+ | **Cocok untuk** | Project mature, data sudah live, butuh stabilitas |
273
+
274
+ → Setup detail di section 2.5 di bawah.
275
+
276
+ #### Option B — Per-Staff Isolated Schema (Development / Early Stage) ⭐ RECOMMENDED untuk 5-30% progress
277
+
278
+ **Setup**: Tiap staff dapat **schema terisolasi sendiri**, full owner di schema-nya (CREATE TABLE bebas, eksperimen bebas).
279
+
280
+ | Aspek | Detail |
281
+ |---|---|
282
+ | Schema | `creative_a` (untuk creative_a), `creative_b`, `creative_c`, dst. |
283
+ | Staff permission | **OWNER schema sendiri** → full CREATE/ALTER/DROP TABLE |
284
+ | Cross-schema access | **BLOCKED** — creative_a tidak bisa lihat data creative_b |
285
+ | Schema change | Staff lakukan langsung di schema-nya. Tidak perlu owner. |
286
+ | Risk | Medium — staff bisa drop table own schema (mitigasi via backup) |
287
+ | Bottleneck owner | **Minimal** — cuma untuk promote ke schema main saat ready |
288
+ | **Cocok untuk** | Eksplorasi domain (mis. A ngerjain namecheap API, B setup security login), early-stage development |
289
+
290
+ → Setup detail di section 2.6 di bawah.
291
+
292
+ #### Option C — Hybrid (Best of Both)
293
+
294
+ **Setup**: Staff punya sandbox schema sendiri (`creative_a` dst.) + read-only access ke schema shared (`bigseo` prod) + sandbox-only write.
295
+
296
+ → Setup detail di section 2.7.
297
+
298
+ ---
299
+
300
+ ### 2.5. Setup PostgreSQL Role per-User — Option A (Owner-only step)
301
+
302
+ **Catatan**: Hanya OWNER yang jalankan SQL berikut di Supabase SQL Editor (login sebagai superuser `postgres`). Staff IT cukup terima username + password dari owner — tidak perlu jalankan ini.
303
+
304
+ **Asumsi**: ada schema multi-tenant di Supabase project (`<project-ref>` = ID project Supabase, mis. `vhsridhwjbqypwummrtp`):
305
+
306
+ - `public` — schema umum (semua bisa baca metadata)
307
+ - `pbn` — schema project PBN (untuk tim creative)
308
+ - `internal_data` — schema sensitif (cuma admin)
309
+ - `analytics` — schema laporan (read-only untuk semua)
310
+
311
+ ```sql
312
+ -- ============================================
313
+ -- 1. Bikin role per-dev (sesuaikan nama)
314
+ -- Nama role di PostgreSQL = `creative_<dev>` (TANPA project-ref)
315
+ -- Project-ref disisipkan otomatis Supabase pooler saat koneksi.
316
+ -- ============================================
317
+ CREATE ROLE creative_a LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_1';
318
+ CREATE ROLE creative_b LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_2';
319
+ CREATE ROLE creative_c LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_3';
320
+ CREATE ROLE creative_f LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_4';
321
+ CREATE ROLE admin_dev LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_5';
322
+
323
+ -- ============================================
324
+ -- 2. Default: REVOKE semua dari semua schema
325
+ -- (clean slate, baru kasih akses yang perlu)
326
+ -- ============================================
327
+ REVOKE ALL ON SCHEMA public FROM creative_a, creative_b, creative_c, creative_f;
328
+ REVOKE ALL ON SCHEMA pbn FROM creative_a, creative_b, creative_c, creative_f;
329
+ REVOKE ALL ON SCHEMA internal_data FROM creative_a, creative_b, creative_c, creative_f;
330
+ REVOKE ALL ON SCHEMA analytics FROM creative_a, creative_b, creative_c, creative_f;
331
+
332
+ -- ============================================
333
+ -- 3. Beri akses schema pbn ke tim creative
334
+ -- ============================================
335
+ GRANT USAGE ON SCHEMA pbn TO creative_a, creative_b, creative_c, creative_f;
336
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA pbn
337
+ TO creative_a, creative_b, creative_c, creative_f;
338
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA pbn
339
+ TO creative_a, creative_b, creative_c, creative_f;
340
+
341
+ -- Default privilege untuk tabel baru yang dibuat di pbn nanti:
342
+ ALTER DEFAULT PRIVILEGES IN SCHEMA pbn
343
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES
344
+ TO creative_a, creative_b, creative_c, creative_f;
345
+
346
+ -- ============================================
347
+ -- 4. Beri akses analytics READ-ONLY ke semua dev
348
+ -- ============================================
349
+ GRANT USAGE ON SCHEMA analytics TO creative_a, creative_b, creative_c, creative_f;
350
+ GRANT SELECT ON ALL TABLES IN SCHEMA analytics
351
+ TO creative_a, creative_b, creative_c, creative_f;
352
+
353
+ ALTER DEFAULT PRIVILEGES IN SCHEMA analytics
354
+ GRANT SELECT ON TABLES
355
+ TO creative_a, creative_b, creative_c, creative_f;
356
+
357
+ -- ============================================
358
+ -- 5. PASTIKAN internal_data TIDAK ter-akses
359
+ -- ============================================
360
+ -- (sudah di-REVOKE di step 2, ini cuma verifikasi)
361
+ -- Test: login sebagai creative_a → SELECT * FROM internal_data.users
362
+ -- Harus error "permission denied for schema internal_data"
363
+
364
+ -- ============================================
365
+ -- 6. Cegah privilege escalation
366
+ -- ============================================
367
+ -- Cegah dev bikin tabel sendiri di schema yang bukan miliknya
368
+ REVOKE CREATE ON SCHEMA public FROM creative_a, creative_b, creative_c, creative_f;
369
+ REVOKE CREATE ON SCHEMA pbn FROM creative_a, creative_b, creative_c, creative_f;
370
+
371
+ -- Cegah dev kasih grant ke orang lain (chain privilege)
372
+ -- Default: GRANT tanpa WITH GRANT OPTION (sudah aman), tapi verify:
373
+ -- Jangan PERNAH jalankan: GRANT ... WITH GRANT OPTION
374
+ ```
375
+
376
+ ### 2.6. Setup Per-Staff Isolated Schema — Option B (Untuk Development)
377
+
378
+ **Tujuan**: Tiap staff punya schema isolated, full CREATE permission di schema-nya sendiri. Owner TIDAK jadi bottleneck untuk DDL.
379
+
380
+ ---
381
+
382
+ ## ⚠️ WARNING — JANGAN TULIS PASSWORD DI FILE INI
383
+
384
+ > SQL di bawah adalah **TEMPLATE**. `GANTI_PASSWORD_KUAT_N` = placeholder text, **BUKAN password real**.
385
+ >
386
+ > **OWNER WAJIB**:
387
+ > 1. **JANGAN PERNAH** tulis password real di file `MCP_SETUP.md` (atau file `.md` apapun di repo).
388
+ > 2. Run SQL **langsung di Supabase SQL Editor** (browser), ganti placeholder INLINE di editor — bukan di file repo.
389
+ > 3. **Share password via secure DM** (Signal/Telegram encrypted), bukan email atau channel chat publik.
390
+ > 4. **Hapus pesan DM** setelah staff konfirm sudah simpan ke password manager.
391
+ >
392
+ > **STAFF WAJIB**:
393
+ > 1. Simpan password ke **password manager** (1Password, Bitwarden) — bukan Notepad atau file teks biasa.
394
+ > 2. Paste connection string ke `.env.local` di laptop sendiri — file ini sudah ter-gitignore default.
395
+ > 3. **JANGAN paste password** di chat publik / Slack channel / email / commit message.
396
+ > 4. Kalau owner kirim password via email (BUKAN best practice), pakai password manager untuk auto-fill, jangan copy-paste manual.
397
+
398
+ ### Owner Workflow Secure Password Sharing (Step-by-Step)
399
+
400
+ ```
401
+ Step 1: Owner generate password kuat di password manager
402
+ (1Password "Generate Password" → 24 char, mixed case + symbol)
403
+
404
+ Step 2: Owner login Supabase Dashboard → SQL Editor (sebagai postgres superuser)
405
+
406
+ Step 3: Paste SQL template di bawah, EDIT INLINE di SQL Editor browser:
407
+ CREATE ROLE creative_a LOGIN PASSWORD 'aB9$xY2!mN7@kQ4...';
408
+ ↑ EDIT INI DI BROWSER, BUKAN DI FILE REPO
409
+
410
+ Step 4: Run SQL → role terbentuk
411
+
412
+ Step 5: Owner buat connection string lengkap:
413
+ DATABASE_URL=postgresql://creative_a.<project-ref>:aB9$xY2!mN7@kQ4...@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres
414
+
415
+ Step 6: Owner DM ke staff via Signal/Telegram (encrypted):
416
+ "Connection string DB kamu (jangan share ke siapapun):
417
+ DATABASE_URL=postgresql://creative_a.<ref>:aB9$xY2!mN7@kQ4...@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres
418
+
419
+ Simpan di password manager + paste ke .env.local kamu.
420
+ Konfirm kalau sudah."
421
+
422
+ Step 7: Tunggu staff konfirm "OK, sudah simpan"
423
+
424
+ Step 8: Owner HAPUS pesan DM dari Signal/Telegram (paranoia level)
425
+ ```
426
+
427
+ ### Staff Workflow Secure Password Storage
428
+
429
+ ```
430
+ Step 1: Buka password manager (1Password/Bitwarden)
431
+ Step 2: Tambah entry baru:
432
+ - Title: "bigseo-project1 DB - creative_a"
433
+ - Username: creative_a.<ref>
434
+ - Password: aB9$xY2!mN7@kQ4...
435
+ - URL: aws-1-ap-southeast-1.pooler.supabase.com:6543
436
+ - Notes: "Project akses - dev DB. Jangan share."
437
+ Step 3: Konfirm ke owner via DM: "OK sudah simpan ke 1Password"
438
+ Step 4: Buka project di laptop:
439
+ cd /path/to/project
440
+ cp .env.example .env.local
441
+ notepad .env.local (atau VS Code)
442
+ Step 5: Paste connection string DARI password manager (bukan dari DM lagi)
443
+ Step 6: Save .env.local
444
+ Step 7: Verifikasi .gitignore:
445
+ cat .gitignore | grep "env.local"
446
+ Harus ada: ".env.local" atau ".env*"
447
+ Step 8: Test:
448
+ npx prisma generate
449
+ pnpm dev → http://localhost:3000 jalan tanpa error "database unreachable"
450
+ ```
451
+
452
+ ### Pre-commit Hook untuk Mencegah Accidental Password Commit (Optional)
453
+
454
+ Owner setup `.husky/pre-commit` di project untuk block commit kalau detect .env file atau pattern password:
455
+
456
+ ```bash
457
+ #!/bin/sh
458
+ # .husky/pre-commit (require husky installed: pnpm add -D husky && pnpm husky init)
459
+
460
+ # Block kalau .env* file ke-stage
461
+ if git diff --cached --name-only | grep -E '^\.env'; then
462
+ echo "🚨 BLOCKED: jangan commit .env files. Pakai .env.example untuk template saja."
463
+ exit 1
464
+ fi
465
+
466
+ # Block kalau ada pattern password hardcoded di file yang ter-stage
467
+ if git diff --cached | grep -E "PASSWORD\s*=\s*['\"][^'\"]{6,}['\"]"; then
468
+ echo "🚨 BLOCKED: terdeteksi hardcoded password di staged files."
469
+ echo "Pakai env var dari .env.local sebagai gantinya."
470
+ exit 1
471
+ fi
472
+
473
+ # Block kalau ada pattern token sensitif (sk-ant-, eyJ, dst.)
474
+ if git diff --cached | grep -E "sk-ant-|eyJ[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}"; then
475
+ echo "🚨 BLOCKED: terdeteksi token sensitif (Anthropic API key / JWT / GitHub PAT)."
476
+ echo "Pindahkan ke .env.local. Lihat SECURITY_INCIDENT_PLAYBOOK.md."
477
+ exit 1
478
+ fi
479
+
480
+ exit 0
481
+ ```
482
+
483
+ Setup once per project, semua dev otomatis dapat protection ini saat clone + `pnpm install`.
484
+
485
+ ---
486
+
487
+ ### SQL Template (RUN DI SUPABASE SQL EDITOR, ISI PASSWORD INLINE DI BROWSER)
488
+
489
+ ```sql
490
+ -- ============================================
491
+ -- 1. Bikin role per-staff (sama seperti Option A)
492
+ -- ============================================
493
+ CREATE ROLE creative_a LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_1';
494
+ CREATE ROLE creative_b LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_2';
495
+ CREATE ROLE creative_c LOGIN PASSWORD 'GANTI_PASSWORD_KUAT_3';
496
+
497
+ -- ============================================
498
+ -- 2. Bikin schema per-staff, staff = OWNER schema
499
+ -- ============================================
500
+ CREATE SCHEMA creative_a AUTHORIZATION creative_a;
501
+ CREATE SCHEMA creative_b AUTHORIZATION creative_b;
502
+ CREATE SCHEMA creative_c AUTHORIZATION creative_c;
503
+
504
+ -- AUTHORIZATION = staff jadi OWNER schema → full DDL (CREATE/ALTER/DROP).
505
+ -- Tidak perlu GRANT manual untuk DDL — sudah include di OWNER privilege.
506
+
507
+ -- ============================================
508
+ -- 3. BLOCK cross-schema access (creative_a tidak bisa lihat creative_b)
509
+ -- ============================================
510
+ REVOKE ALL ON SCHEMA creative_b FROM creative_a, creative_c;
511
+ REVOKE ALL ON SCHEMA creative_c FROM creative_a, creative_b;
512
+ REVOKE ALL ON SCHEMA creative_a FROM creative_b, creative_c;
513
+
514
+ -- ============================================
515
+ -- 4. BLOCK staff akses ke schema 'bigseo' atau schema prod lainnya
516
+ -- (kalau ada, sesuaikan nama)
517
+ -- ============================================
518
+ REVOKE ALL ON SCHEMA public FROM creative_a, creative_b, creative_c;
519
+ -- REVOKE ALL ON SCHEMA bigseo FROM creative_a, creative_b, creative_c; -- uncomment kalau ada
520
+
521
+ -- ============================================
522
+ -- 5. Set search_path default ke schema sendiri
523
+ -- Tujuan: tiap staff query `SELECT * FROM users` otomatis cari di schema-nya
524
+ -- ============================================
525
+ ALTER USER creative_a SET search_path TO creative_a;
526
+ ALTER USER creative_b SET search_path TO creative_b;
527
+ ALTER USER creative_c SET search_path TO creative_c;
528
+
529
+ -- ============================================
530
+ -- 6. Test isolation (login sebagai creative_a)
531
+ -- ============================================
532
+ -- SELECT * FROM creative_b.users; -- harus error "permission denied for schema creative_b"
533
+ -- CREATE TABLE creative_a.test_table (id int); -- harus sukses
534
+ -- DROP TABLE creative_a.test_table; -- harus sukses
535
+ ```
536
+
537
+ ### 2.7. Setup Hybrid Strategy — Option C (Sandbox + Read Prod)
538
+
539
+ Kombinasi Option A + B:
540
+ - Staff punya schema sandbox sendiri (full CREATE)
541
+ - Staff bisa READ schema prod shared (mis. `bigseo`) untuk reference
542
+ - Staff TIDAK bisa WRITE ke schema prod
543
+
544
+ ```sql
545
+ -- Step 1-3 sama seperti Option B (per-staff schema dengan OWNER)
546
+ -- (lihat 2.6 di atas)
547
+
548
+ -- Plus: kasih READ access ke schema prod
549
+ GRANT USAGE ON SCHEMA bigseo TO creative_a, creative_b, creative_c;
550
+ GRANT SELECT ON ALL TABLES IN SCHEMA bigseo TO creative_a, creative_b, creative_c;
551
+
552
+ -- Default privilege: tabel baru di bigseo otomatis read-only untuk staff
553
+ ALTER DEFAULT PRIVILEGES IN SCHEMA bigseo
554
+ GRANT SELECT ON TABLES TO creative_a, creative_b, creative_c;
555
+
556
+ -- BLOCK write ke bigseo
557
+ REVOKE INSERT, UPDATE, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA bigseo FROM creative_a, creative_b, creative_c;
558
+ REVOKE CREATE ON SCHEMA bigseo FROM creative_a, creative_b, creative_c;
559
+ ```
560
+
561
+ ### 2.8. Backup Plan untuk Option B & C (WAJIB)
562
+
563
+ Karena staff punya full CREATE/DROP di schema mereka = risiko accidentally drop table. **3 layer backup**:
564
+
565
+ #### Layer 1: Supabase Daily Backup (Default, Gratis)
566
+ - Aktif by default di Supabase free tier
567
+ - Retention: 7 hari (free tier) / 30 hari (Pro tier)
568
+ - Restore: Dashboard → Database → Backups → Restore
569
+ - ⚠️ **Caveat**: full project restore = wipe semua schema. **Tidak selective per-schema**.
570
+
571
+ #### Layer 2: Manual Snapshot Sebelum Risky Operation
572
+ - Owner trigger via Supabase Dashboard → Database → Backups → "Create Manual Backup"
573
+ - Kapan: sebelum staff lakukan migrasi besar, sebelum approve PR yang touch schema
574
+ - Retention: sampai owner delete manual
575
+ - Cost: gratis
576
+
577
+ #### Layer 3: Per-Schema pg_dump Daily (Otomatis via GitHub Action)
578
+ - Cronjob daily ~03:00 WIB
579
+ - Dump tiap schema staff terpisah: `pg_dump --schema=creative_a --no-owner --clean`
580
+ - Upload ke Supabase Storage / S3 / Google Drive
581
+ - Retention: 30 hari (configurable)
582
+ - Restore selective per-schema: `psql < creative_a-2026-06-15.sql`
583
+
584
+ GitHub Action template: `.github/workflows/backup-schemas.yml` (auto-copy oleh setup script).
585
+
586
+ ### 2.9. Restore Flow (Kalau Staff Accidentally Drop Table)
587
+
588
+ ```
589
+ Skenario: creative_a tidak sengaja jalankan `DROP TABLE creative_a.namecheap_logs;`
590
+
591
+ 1. Staff lapor owner via DM (per SECURITY_INCIDENT_PLAYBOOK)
592
+ 2. Owner cek backup terbaru sebelum incident:
593
+ - Layer 3 (pg_dump): biasanya 03:00 WIB tadi malam
594
+ - Layer 2 (manual): kalau ada snapshot belakangan
595
+ 3. Owner restore selective:
596
+ - Login Supabase SQL Editor sebagai postgres superuser
597
+ - Run: psql < backup-creative_a-2026-06-15.sql
598
+ - Verify: SELECT count(*) FROM creative_a.namecheap_logs;
599
+ 4. Owner DM staff: "Restored, lanjut kerja"
600
+ 5. Post-mortem dalam 24 jam (per SECURITY_INCIDENT_PLAYBOOK)
601
+ ```
602
+
603
+ ⚠️ **JANGAN pakai Layer 1 (full project restore) untuk restore selective per-staff** — bisa wipe progress staff lain. Pakai Layer 3 dengan `pg_dump` per-schema.
604
+
605
+ ### 2.10. Promote Sandbox Schema → Production Schema (Saat Fitur Ready)
606
+
607
+ Saat staff selesai develop di sandbox dan fitur ready untuk prod:
608
+
609
+ ```
610
+ 1. Staff lapor owner: "Fitur namecheap API ready di creative_a, mau promote ke bigseo"
611
+ 2. Owner review schema staff (DDL + sample data)
612
+ 3. Owner buat Prisma migration di branch:
613
+ - Edit prisma/schema.prisma → tambah model yang sudah di-test di creative_a
614
+ - npx prisma migrate dev --name add_namecheap_models
615
+ 4. PR review + AI Reviewer check
616
+ 5. Merge → Prisma migrate deploy ke bigseo (prod)
617
+ 6. Owner optional: pindahkan data dari creative_a.namecheap_logs ke bigseo.namecheap_logs
618
+ (INSERT INTO bigseo.namecheap_logs SELECT * FROM creative_a.namecheap_logs;)
619
+ 7. Staff lanjut develop fitur baru di sandbox-nya
620
+ ```
621
+
622
+ ---
623
+
624
+ ### 2.11. Verifikasi Schema Isolation
625
+
626
+ Login pakai role baru, test akses:
627
+
628
+ ```sql
629
+ -- Login sebagai creative_a (pakai psql atau Supabase SQL Editor dengan SET ROLE)
630
+ SET ROLE creative_a;
631
+
632
+ -- Harus BERHASIL:
633
+ SELECT * FROM pbn.projects LIMIT 5;
634
+ SELECT * FROM analytics.daily_stats LIMIT 5;
635
+
636
+ -- Harus GAGAL ("permission denied"):
637
+ SELECT * FROM internal_data.users LIMIT 5;
638
+ CREATE TABLE pbn.test_table (id int);
639
+ CREATE TABLE public.test_table (id int);
640
+
641
+ -- Reset role:
642
+ RESET ROLE;
643
+ ```
644
+
645
+ ### 2.12. Connection String per-Dev (Format BENAR — Supabase Pooler)
646
+
647
+ **Format Supabase Pooler** (yang dipakai produksi): username harus disuffix dengan project-ref `creative_<dev>.<project-ref>`. Project-ref bisa dilihat di Supabase Dashboard → Project Settings → General → Reference ID (mis. `vhsridhwjbqypwummrtp`).
648
+
649
+ ```text
650
+ # App/runtime (port 6543, transaction mode pooler) — DEFAULT untuk MCP + Next.js app
651
+ postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres
652
+
653
+ # DDL/migrasi (port 5432, session mode pooler) — Prisma migrate / psql command
654
+ postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:5432/postgres?sslmode=require
655
+ ```
656
+
657
+ **Kapan pakai port mana?**
658
+
659
+ | Port | Mode pooler | Pakai untuk |
660
+ |-------|--------------|----------------------------------------------------------|
661
+ | 6543 | Transaction | App runtime (Next.js Prisma client, MCP server-postgres) — short-lived queries |
662
+ | 5432 | Session | DDL (`CREATE TABLE`, `ALTER`), `prisma migrate deploy`, psql interactive, full session |
663
+
664
+ **Region pooler**: sesuaikan dengan project Supabase. Contoh region:
665
+ - `aws-1-ap-southeast-1.pooler.supabase.com` (Singapore)
666
+ - `aws-0-us-east-1.pooler.supabase.com` (US East)
667
+
668
+ Cek di Supabase Dashboard → Connect (pojok kanan atas) untuk URL persis project kamu.
669
+
670
+ **LARANGAN KERAS** (sesuai update keamanan tim):
671
+
672
+ - ❌ JANGAN pakai username `postgres.<ref>` (= superuser, akses SEMUA tenant) — **DITUTUP** demi keamanan.
673
+ - ❌ JANGAN pakai `service_role_key` Supabase (REST API bypass RLS, full superuser).
674
+ - ❌ JANGAN share connection string di chat / Slack / Discord — kirim via password manager (1Password, Bitwarden) atau encrypted DM auto-delete.
675
+ - ✅ PAKAI `creative_<dev>.<project-ref>` dengan password kamu sendiri.
676
+ - ✅ Kalau password lupa, minta owner generate ulang (owner: `ALTER USER creative_x PASSWORD 'NEW';`).
677
+
678
+ ### 2.13. RLS Setup Workflow (WAJIB sebelum data produksi masuk)
679
+
680
+ **Konteks**: setelah dapat akses schema sendiri, staff IT WAJIB setup Row-Level Security (RLS) untuk mencegah pemegang `anon` / `publishable_key` (yang PUBLIK) baca/tulis data atau melewati cek role app.
681
+
682
+ **ATURAN RLS WAJIB DIPATUHI**:
683
+
684
+ 1. **JANGAN ENABLE RLS sebelum bikin policy dulu** — RLS tanpa policy = app error semua query gagal.
685
+ 2. **Tes lewat APP asli** (login tiap role di UI), BUKAN psql / SQL Editor — koneksi langsung dari owner tabel **bypass RLS** karena PostgreSQL role rule.
686
+ 3. **JANGAN pakai `USING(true)` untuk operasi TULIS** — policy tulis HARUS cek role app yang authoritative.
687
+ 4. **HANYA sentuh schema kamu sendiri** — jangan touch tabel di schema lain.
688
+
689
+ **Workflow per-tabel** (ulangi tiap tabel di schema kamu):
690
+
691
+ ```sql
692
+ -- ========== Langkah 1: Bikin policy DULU (sebelum enable RLS) ==========
693
+ -- Contoh tabel `posts` di schema `pbn`, app punya role `admin_access`.
694
+
695
+ -- Policy READ: cuma admin_access yang boleh baca
696
+ CREATE POLICY posts_read_policy ON pbn.posts
697
+ FOR SELECT
698
+ USING (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
699
+
700
+ -- Policy INSERT: cuma admin_access boleh insert
701
+ CREATE POLICY posts_insert_policy ON pbn.posts
702
+ FOR INSERT
703
+ WITH CHECK (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
704
+
705
+ -- Policy UPDATE: cuma admin_access boleh update
706
+ CREATE POLICY posts_update_policy ON pbn.posts
707
+ FOR UPDATE
708
+ USING (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access')
709
+ WITH CHECK (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
710
+
711
+ -- Policy DELETE: cuma admin_access boleh delete
712
+ CREATE POLICY posts_delete_policy ON pbn.posts
713
+ FOR DELETE
714
+ USING (current_setting('request.jwt.claims', true)::json->>'role' = 'admin_access');
715
+
716
+ -- ========== Langkah 2: ENABLE RLS setelah policy lengkap ==========
717
+ ALTER TABLE pbn.posts ENABLE ROW LEVEL SECURITY;
718
+
719
+ -- ========== Langkah 3: REVOKE anon (kalau app wajib login) ==========
720
+ REVOKE ALL ON pbn.posts FROM anon;
721
+ -- Catatan: kalau app punya endpoint public (login page, marketing), JANGAN revoke anon — gunakan policy yang lebih spesifik.
722
+
723
+ -- ========== Langkah 4: TEST via APP asli ==========
724
+ -- Login UI sebagai admin → buka halaman yang baca/tulis pbn.posts → harus berhasil.
725
+ -- Logout → coba akses tanpa login → harus error 401/403 (bukan 500).
726
+ -- Login sebagai role lain (kalau ada) → harus block sesuai matrix RBAC.
727
+
728
+ -- ========== Langkah 5: VERIFIKASI anon terblokir ==========
729
+ -- Pakai Supabase Dashboard "API" tab atau curl dengan publishable_key:
730
+ -- curl -H "apikey: <publishable_key>" <project-url>/rest/v1/posts
731
+ -- Harus return 401 / 403 / empty array (depending on policy).
732
+ ```
733
+
734
+ **Prompt siap-paste untuk staff IT**: lihat `RLS_SETUP_PROMPT.md` di kit (`./.claude-kit/templates/RLS_SETUP_PROMPT.md`).
735
+
736
+ ### 2.14. MCP Config di Claude Code
737
+
738
+ File: `%APPDATA%\Claude\claude_desktop_config.json` (Windows) atau `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac).
739
+
740
+ Untuk Claude Code CLI: `~/.claude/settings.json` atau `.claude/settings.local.json` per-proyek.
741
+
742
+ ```json
743
+ {
744
+ "mcpServers": {
745
+ "postgres-pbn": {
746
+ "command": "npx",
747
+ "args": [
748
+ "-y",
749
+ "@modelcontextprotocol/server-postgres",
750
+ "postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres"
751
+ ]
752
+ }
753
+ }
754
+ }
755
+ ```
756
+
757
+ **Penting**:
758
+ - Username pakai format `creative_<dev>.<project-ref>` (BUKAN `creative_a` saja).
759
+ - Port `6543` (transaction mode) — cocok untuk MCP read query + sebagian besar Prisma query.
760
+ - Untuk MCP yang butuh DDL (mis. `ALTER TABLE` via AI), pakai port `5432?sslmode=require`.
761
+ - Region pooler match dengan project kamu (cek Dashboard → Connect).
762
+
763
+ Untuk Windows + WSL, atau kalau npx lambat, install global:
764
+
765
+ ```bash
766
+ npm install -g @modelcontextprotocol/server-postgres
767
+ ```
768
+
769
+ Lalu config:
770
+
771
+ ```json
772
+ {
773
+ "mcpServers": {
774
+ "postgres-pbn": {
775
+ "command": "mcp-server-postgres",
776
+ "args": ["postgresql://creative_a.vhsridhwjbqypwummrtp:GANTI_PASSWORD_KUAT_1@aws-1-ap-southeast-1.pooler.supabase.com:6543/postgres"]
777
+ }
778
+ }
779
+ }
780
+ ```
781
+
782
+ ### 2.15. Audit Trail
783
+
784
+ #### Cek aktif siapa sekarang
785
+
786
+ ```sql
787
+ SELECT pid, usename, application_name, client_addr, state, query_start, query
788
+ FROM pg_stat_activity
789
+ WHERE usename LIKE 'creative_%' OR usename = 'admin_dev'
790
+ ORDER BY query_start DESC;
791
+ ```
792
+
793
+ #### Aktifkan pgaudit (lanjutan, optional)
794
+
795
+ Extension `pgaudit` log semua DDL + DML per role. Di Supabase:
796
+
797
+ ```sql
798
+ -- Cek apakah pgaudit tersedia
799
+ SELECT * FROM pg_available_extensions WHERE name = 'pgaudit';
800
+
801
+ -- Kalau tersedia:
802
+ CREATE EXTENSION IF NOT EXISTS pgaudit;
803
+
804
+ -- Config (perlu superuser):
805
+ ALTER SYSTEM SET pgaudit.log = 'write, ddl';
806
+ ALTER SYSTEM SET pgaudit.log_relation = on;
807
+ SELECT pg_reload_conf();
808
+ ```
809
+
810
+ Log muncul di Supabase Dashboard → Logs → Postgres Logs.
811
+
812
+ #### Audit GRANT/REVOKE (manual quarterly)
813
+
814
+ ```sql
815
+ -- Lihat semua privilege per role
816
+ SELECT grantee, table_schema, table_name, privilege_type
817
+ FROM information_schema.role_table_grants
818
+ WHERE grantee LIKE 'creative_%'
819
+ ORDER BY grantee, table_schema, table_name;
820
+
821
+ -- Lihat siapa punya akses ke schema sensitif
822
+ SELECT nspname AS schema, r.rolname, has_schema_privilege(r.rolname, n.nspname, 'USAGE') AS has_usage
823
+ FROM pg_namespace n
824
+ CROSS JOIN pg_roles r
825
+ WHERE n.nspname IN ('internal_data', 'pbn', 'analytics')
826
+ AND r.rolname NOT LIKE 'pg_%'
827
+ AND r.rolcanlogin = true
828
+ ORDER BY schema, rolname;
829
+ ```
830
+
831
+ ---
832
+
833
+ ## 3. Supabase MCP (Owner Only — WARNING)
834
+
835
+ > ⚠️ **PERINGATAN KERAS**: Section ini **HANYA untuk OWNER database** (orang yang punya `service_role_key`). Staff IT / developer schema-scoped **TIDAK BOLEH** pakai konfigurasi ini. Pakai PostgreSQL MCP (Section 2) sebagai gantinya.
836
+
837
+ ### 3.1. Kenapa Supabase MCP berisiko untuk staff IT?
838
+
839
+ Supabase MCP official pakai `service_role_key` — yang artinya:
840
+
841
+ - **BYPASS RLS** (Row-Level Security) — AI bisa baca/edit data SEMUA user di SEMUA schema.
842
+ - **FULL SUPERUSER** — bisa `DROP TABLE`, `REVOKE` siapapun, ubah policy.
843
+ - **TIDAK ADA isolation per-tenant** — semua orang yang pegang key punya akses identik.
844
+ - **Audit trail buruk** — semua query muncul "atas nama service_role", tidak bisa trace per-dev.
845
+
846
+ Konsekuensi kalau key bocor: data SEMUA tenant kompromi sekaligus.
847
+
848
+ ### 3.2. Kapan OWNER boleh pakai Supabase MCP?
849
+
850
+ Hanya untuk kasus spesifik yang PostgreSQL MCP tidak bisa cover:
851
+
852
+ | Use case | Justifikasi |
853
+ |---|---|
854
+ | **Migrasi schema lintas-tenant** | Butuh ALTER SCHEMA di banyak schema sekaligus (mis. tambah audit column ke semua tenant) |
855
+ | **Investigasi keamanan** | Audit "siapa baca apa" lintas tenant saat suspected breach |
856
+ | **Setup awal Supabase** | Bikin role + policy template untuk tenant baru |
857
+ | **Backup / restore** | Snapshot full database (`pg_dump` superuser) |
858
+
859
+ Untuk operasi OWNER sehari-hari (cek data sendiri, debug Prisma), tetap pakai PostgreSQL MCP dengan role `admin_dev` (Section 2.5) — bukan Supabase MCP.
860
+
861
+ ### 3.3. Setup Supabase MCP (kalau benar-benar perlu)
862
+
863
+ Install Supabase MCP server:
864
+
865
+ ```bash
866
+ npm install -g @supabase/mcp-server-supabase
867
+ ```
868
+
869
+ Atau cek paket resmi terbaru di https://github.com/supabase/mcp-server-supabase.
870
+
871
+ Config (`~/.claude/settings.local.json` — **JANGAN** commit):
872
+
873
+ ```json
874
+ {
875
+ "mcpServers": {
876
+ "supabase-owner": {
877
+ "command": "npx",
878
+ "args": ["-y", "@supabase/mcp-server-supabase"],
879
+ "env": {
880
+ "SUPABASE_URL": "https://<project-ref>.supabase.co",
881
+ "SUPABASE_SERVICE_ROLE_KEY": "${SUPABASE_SERVICE_ROLE_KEY}"
882
+ }
883
+ }
884
+ }
885
+ }
886
+ ```
887
+
888
+ `SUPABASE_SERVICE_ROLE_KEY` ambil dari OS env var (Windows: System Properties → Environment Variables), JANGAN tulis literal di JSON.
889
+
890
+ ### 3.4. Aturan saat pakai Supabase MCP
891
+
892
+ - ⚠️ **JANGAN pernah share `service_role_key`** ke siapapun (termasuk staff IT, ditanya pun jangan).
893
+ - ⚠️ **JANGAN commit `service_role_key`** ke repo / Slack / Discord / email / WhatsApp.
894
+ - ⚠️ **JANGAN aktifkan Supabase MCP saat sesi screen-share** — AI bisa execute query yang ter-display.
895
+ - ✅ **Rotation tiap 3 bulan minimum** — generate key baru di Supabase Dashboard → Settings → API → Reset service_role.
896
+ - ✅ **Disable Supabase MCP saat tidak butuh** — buka `settings.local.json`, comment out section, restart Claude Code.
897
+ - ✅ **Audit setelah pakai** — cek `pg_stat_activity` 24 jam terakhir, lihat query apa yang dijalankan.
898
+
899
+ ### 3.5. Kalau staff IT minta Supabase MCP
900
+
901
+ Tolak. Kasih PostgreSQL MCP (Section 2) + schema-scoped role mereka. Kalau mereka argue butuh akses lintas-schema, eskalasi ke owner — **owner yang jalankan query**, BUKAN delegate key.
902
+
903
+ ---
904
+
905
+ ## 4. GitHub MCP
906
+
907
+ ### 4.1. Setup PAT (Personal Access Token)
908
+
909
+ 1. Buka https://github.com/settings/tokens → **Generate new token (classic)** atau **fine-grained**.
910
+ 2. **Fine-grained** (recommended):
911
+ - Repository access: pilih repo yang relevan (jangan "All repositories").
912
+ - Permissions:
913
+ - **Contents**: Read & write (untuk commit + read code)
914
+ - **Issues**: Read & write
915
+ - **Pull requests**: Read & write
916
+ - **Metadata**: Read (auto)
917
+ - **Workflows**: Read (untuk lihat CI status)
918
+ 3. Expiration: **90 hari** (paksa rotation tiap quarter).
919
+ 4. Copy token, simpan di password manager. **JANGAN** commit ke repo.
920
+
921
+ ### 4.2. MCP Config
922
+
923
+ ```json
924
+ {
925
+ "mcpServers": {
926
+ "github": {
927
+ "command": "npx",
928
+ "args": ["-y", "@modelcontextprotocol/server-github"],
929
+ "env": {
930
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxx"
931
+ }
932
+ }
933
+ }
934
+ }
935
+ ```
936
+
937
+ ### 4.3. Use Case
938
+
939
+ Dengan GitHub MCP aktif, AI bisa:
940
+
941
+ - Buka issue: "Buka issue baru di repo `<your-project>` dengan judul X dan body Y."
942
+ - Review PR: "Lihat PR #42, ringkas perubahan, kasih saran."
943
+ - Cek CI: "Cek status workflow di branch feat/login."
944
+ - Search code: "Cari semua occurrence `service_role_key` di organisasi."
945
+
946
+ ---
947
+
948
+ ## 5. Filesystem MCP (Default Claude Code)
949
+
950
+ Claude Code sudah bundled filesystem access ke working directory. Tidak perlu config tambahan untuk kasus normal.
951
+
952
+ ### Limit Access ke Workspace Folder
953
+
954
+ Kalau pakai Claude Desktop (bukan CLI), perlu config eksplisit:
955
+
956
+ ```json
957
+ {
958
+ "mcpServers": {
959
+ "filesystem": {
960
+ "command": "npx",
961
+ "args": [
962
+ "-y",
963
+ "@modelcontextprotocol/server-filesystem",
964
+ "C:\\workspace\\<project-folder>"
965
+ ]
966
+ }
967
+ }
968
+ }
969
+ ```
970
+
971
+ **JANGAN** kasih access ke root `C:\` atau `/` — AI bisa baca file sistem / kredensial OS lain.
972
+
973
+ **JANGAN** kasih access ke folder yang berisi `.env`, password manager export, atau key SSH.
974
+
975
+ ---
976
+
977
+ ## 6. Anti-Pattern (Jangan Lakukan)
978
+
979
+ ### 6.1. Pakai `service_role_key` Supabase untuk staff IT
980
+
981
+ ```json
982
+ // SALAH — bypass RLS, full superuser, audit trail buruk
983
+ {
984
+ "mcpServers": {
985
+ "supabase": {
986
+ "env": {
987
+ "SUPABASE_SERVICE_ROLE_KEY": "eyJxxx..."
988
+ }
989
+ }
990
+ }
991
+ }
992
+ ```
993
+
994
+ **Kenapa salah**: service_role_key bypass RLS → AI bisa edit data user mana saja → kalau token bocor, semua data kompromi. Plus tidak ada audit per-dev (semua query "atas nama service_role").
995
+
996
+ **Yang benar**: PostgreSQL role per-dev (section 2.5).
997
+
998
+ ### 6.2. Share connection string di chat
999
+
1000
+ ```text
1001
+ // SALAH — di Slack channel #dev:
1002
+ "Tolong test pake connection ini ya: postgresql://creative_a:Pass123@..."
1003
+ ```
1004
+
1005
+ **Kenapa salah**: Slack di-index, history nyimpen. Kalau Slack workspace di-breach atau ada eks-member, kredensial bocor.
1006
+
1007
+ **Yang benar**: kirim via password manager shared vault (1Password, Bitwarden Organizations), atau encrypted DM yang auto-delete.
1008
+
1009
+ ### 6.3. Pakai role `postgres` (superuser) sebagai default
1010
+
1011
+ ```json
1012
+ // SALAH — superuser, bisa DROP TABLE, REVOKE, dll
1013
+ {
1014
+ "command": "mcp-server-postgres",
1015
+ "args": ["postgresql://postgres:xxxxx@db.xxx.supabase.co:5432/postgres"]
1016
+ }
1017
+ ```
1018
+
1019
+ **Kenapa salah**: AI bisa jalankan `DROP TABLE users;` tanpa konfirmasi. Sekali typo command, data hilang.
1020
+
1021
+ **Yang benar**: role per-dev dengan privilege terbatas (no CREATE, no DROP di schema sensitif).
1022
+
1023
+ ### 6.4. Commit MCP config ke repo
1024
+
1025
+ ```bash
1026
+ # SALAH — config dengan token ter-commit
1027
+ git add .claude/settings.json
1028
+ git commit -m "add mcp config"
1029
+ git push
1030
+ ```
1031
+
1032
+ **Kenapa salah**: token di history Git permanent. Walaupun di-revert, history tetap ada (kecuali rewrite force-push).
1033
+
1034
+ **Yang benar**: pakai `.claude/settings.local.json` (sudah di `.gitignore` default Claude Code) untuk config yang ada secret. Atau pakai env var dari OS:
1035
+
1036
+ ```json
1037
+ {
1038
+ "mcpServers": {
1039
+ "github": {
1040
+ "command": "npx",
1041
+ "args": ["-y", "@modelcontextprotocol/server-github"],
1042
+ "env": {
1043
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PAT}"
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+ ```
1049
+
1050
+ Lalu set `GITHUB_PAT` di env OS user (Windows: System Properties → Environment Variables).
1051
+
1052
+ ---
1053
+
1054
+ ## 7. Audit + Rotation
1055
+
1056
+ ### 7.1. Jadwal Rotation
1057
+
1058
+ | Kredensial | Frekuensi rotation | Triggered by |
1059
+ |-----------------------------|--------------------|----------------------------------------|
1060
+ | GitHub PAT | Tiap 3 bulan | Calendar reminder + token expiry |
1061
+ | PostgreSQL password role | Tiap 3 bulan | Calendar reminder |
1062
+ | Semua kredensial dev keluar | Immediate | Hari H+0 staff keluar / di-PHK |
1063
+ | Suspected leak | Immediate | Saat dicurigai bocor (push accident) |
1064
+
1065
+ ### 7.2. Quarterly Audit Checklist
1066
+
1067
+ Tiap Q1 (Maret), Q2 (Juni), Q3 (September), Q4 (Desember):
1068
+
1069
+ - [ ] Cek `pg_stat_activity` 7 hari terakhir — siapa query schema sensitif?
1070
+ - [ ] Cek `information_schema.role_table_grants` — ada privilege escalation tidak terdeteksi?
1071
+ - [ ] Cek GitHub audit log — ada PAT dengan scope berlebih?
1072
+ - [ ] Rotate semua password role PostgreSQL.
1073
+ - [ ] Generate ulang GitHub PAT yang akan expire.
1074
+ - [ ] Review MCP config tiap dev — apakah masih relevan dengan kerjaan sekarang?
1075
+ - [ ] Hapus role PostgreSQL untuk dev yang sudah keluar (sebelumnya: pastikan tidak ada object yang owned by role tsb).
1076
+
1077
+ ### 7.3. Cara Hapus Role Dev yang Keluar
1078
+
1079
+ ```sql
1080
+ -- 1. Cek apakah role punya object
1081
+ SELECT n.nspname AS schema, c.relname AS object, c.relkind
1082
+ FROM pg_class c
1083
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1084
+ JOIN pg_roles r ON r.oid = c.relowner
1085
+ WHERE r.rolname = 'creative_x';
1086
+
1087
+ -- 2. Kalau ada, transfer ownership dulu
1088
+ REASSIGN OWNED BY creative_x TO admin_dev;
1089
+
1090
+ -- 3. Drop semua privilege
1091
+ DROP OWNED BY creative_x;
1092
+
1093
+ -- 4. Drop role
1094
+ DROP ROLE creative_x;
1095
+ ```
1096
+
1097
+ ### 7.4. Incident Response: Token Bocor
1098
+
1099
+ Kalau PAT GitHub atau password DB ke-commit / ke-post di publik:
1100
+
1101
+ 1. **Immediate (5 menit)**:
1102
+ - GitHub: revoke token di https://github.com/settings/tokens (klik token → Delete).
1103
+ - PostgreSQL: `ALTER USER creative_a PASSWORD 'NEW_STRONG_PASSWORD';`
1104
+ 2. **Short-term (1 jam)**:
1105
+ - Cek audit log (`pg_stat_activity`, GitHub audit log) — ada akses suspicious 7 hari terakhir?
1106
+ - Cek apakah ada commit / push yang tidak diakui di repo.
1107
+ 3. **Cleanup (24 jam)**:
1108
+ - Update MCP config semua dev yang affected.
1109
+ - Notify tim via channel resmi (jangan via channel yang sama dengan leak source).
1110
+ - Post-mortem: kenapa bisa bocor? Update process supaya tidak terulang.
1111
+
1112
+ ---
1113
+
1114
+ ## 8. Troubleshooting
1115
+
1116
+ ### MCP server tidak nyala
1117
+
1118
+ - Cek log Claude Code: `~/.claude/logs/` (Linux/Mac) atau `%APPDATA%\Claude\logs\` (Windows).
1119
+ - Test connection string manual: `psql "postgresql://creative_a:...@host:5432/postgres"`.
1120
+ - Kalau error "FATAL: password authentication failed" → password salah / role belum dibikin.
1121
+
1122
+ ### "permission denied for schema X"
1123
+
1124
+ - Role belum di-GRANT USAGE ke schema tsb. Cek section 2.5 step 3.
1125
+ - Atau memang sengaja (mis. `internal_data` untuk creative_a) — switch ke role yang punya akses.
1126
+
1127
+ ### MCP terlalu lambat
1128
+
1129
+ - Pakai pooled connection (port 6543) untuk Supabase, bukan direct (5432) — overhead lebih rendah.
1130
+ - Atau install MCP server global (`npm i -g @modelcontextprotocol/server-postgres`) supaya tidak perlu npx download tiap kali.
1131
+
1132
+ ---
1133
+
1134
+ ## Referensi
1135
+
1136
+ - MCP spec: https://modelcontextprotocol.io
1137
+ - PostgreSQL MCP server: https://github.com/modelcontextprotocol/servers/tree/main/src/postgres
1138
+ - GitHub MCP server: https://github.com/modelcontextprotocol/servers/tree/main/src/github
1139
+ - Filesystem MCP server: https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem
1140
+ - PostgreSQL GRANT/REVOKE docs: https://www.postgresql.org/docs/current/sql-grant.html
1141
+ - pgaudit extension: https://www.pgaudit.org
1142
+
1143
+ ---
1144
+
1145
+ > **Update file ini** tiap kali ada MCP server baru yang di-standardize, atau ada perubahan policy security (mis. rotation period berubah). Catat di `CHANGELOG.md` kit + bump versi.