# Kadi-v2 — Clean-Setup Guide for Vendor Engineers

> Audience: vendor implementation team setting up Kadi-v2 from a fresh GitHub clone.
> Goal: get a working local development environment running in under 30 minutes.
> Verified on: 2026-05-09 (Postgres 17, Docker, Node 20).

---

## Prerequisites

- **Docker** + Docker Compose (any recent version)
- **Node.js 20.x** + npm
- **Git** with access to `KADiCon/Kadi-v2`
- **Postgres 17** (provided by Docker image; Azure Flexible Server PG 17 is the production target)

---

## Path A — Local Postgres only (recommended for first run)

This path stands up the Kadi-v2 schema on a clean local Postgres without touching any cloud service. Best for verifying that the migrations apply cleanly and for offline development.

```bash
# 1. Clone and enter the repo
git clone https://github.com/KADiCon/Kadi-v2.git
cd Kadi-v2

# 2. Start the local Postgres stack
docker compose up -d

# 3. Watch init progress (~30 seconds)
docker compose logs -f postgres
# Wait for: "PostgreSQL init process complete; ready for start up."
# Press Ctrl-C to exit logs.

# 4. Verify schema
docker compose exec postgres psql -U postgres -d kadi_v2_local -c "
  SELECT
    (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public') AS tables,
    (SELECT COUNT(*) FROM pg_policies WHERE schemaname = 'public') AS rls_policies,
    (SELECT COUNT(*) FROM information_schema.routines WHERE routine_schema = 'public' AND routine_type = 'FUNCTION') AS functions;
"
# Expected: tables ~66, rls_policies ~159, functions ~90
```

Connection from the Next.js app:

```bash
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/kadi_v2_local
```

**Limitations of Path A**:
- No Supabase Auth (login flow won't work locally)
- No Supabase Storage (file uploads stub)
- No Edge Functions
- Use **Path B** for full-stack development.

---

## Path B — Real Supabase project (for full-stack development)

This path uses a real (free-tier) Supabase project as the backend. Use this when you need to test login, signup, file upload, or auth-protected RLS policies.

### Setup

```bash
# 1. Create a new Supabase project at https://supabase.com/dashboard
#    Region: pick what matches BMW (eu-central-1 / Frankfurt)
#    DB password: pick a strong password, save it

# 2. Note the project ref (in URL: https://supabase.com/dashboard/project/<ref>)
#    Note the anon key + service-role key from Settings > API
```

### Apply the schema

In the Supabase project's SQL Editor, run in order:

1. **`supabase/bootstrap/supabase-bootstrap-prerequisites.sql`** — extensions, roles, auth-schema mock (if not Supabase-hosted skip; on Supabase the platform provides this)
2. **`supabase/bootstrap/supabase-bootstrap-from-prod.sql`** — full schema (66 tables, 159 RLS, 90 functions, 217 indexes)
3. **Section 7a closures** (5 files in repo root):
   - `supabase/migrations/supabase-migration-kadi-v2-rls-pta-ps.sql`
   - `supabase/migrations/supabase-migration-kadi-v2-rls-deferred-lax.sql`
   - `supabase/migrations/supabase-migration-kadi-v2-rls-oee.sql`
   - `supabase/migrations/supabase-migration-kadi-v2-rls-planning.sql`
   - `supabase/migrations/supabase-migration-kadi-v2-key-vault-column.sql`
4. **Section 4 seed migrations** (see `MIGRATIONS.md` § 4)

### Set up env vars

```bash
cp .env.example .env.local
# Edit .env.local with your Supabase URL + keys
```

Required (at minimum):
- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`
- `SUPABASE_SERVICE_ROLE_KEY`

### Run the app

```bash
npm install
npm run dev
# Open http://localhost:3000
```

---

## Path C — Azure Flexible Server (production target)

This path targets BMW's intended production stack: PostgreSQL Flexible Server on Azure. Use only after Path A or B works and the Azure resources are provisioned.

```bash
# 1. Provision PG Flexible Server (Postgres 17, Burstable B1ms or higher)

# 2. Apply files in order via Azure Cloud Shell or psql:
psql "<azure-connection-string>" \
  -f supabase/bootstrap/supabase-bootstrap-prerequisites.sql \
  -f supabase/bootstrap/supabase-bootstrap-from-prod.sql \
  -f supabase/migrations/supabase-migration-kadi-v2-rls-pta-ps.sql \
  -f supabase/migrations/supabase-migration-kadi-v2-rls-deferred-lax.sql \
  -f supabase/migrations/supabase-migration-kadi-v2-rls-oee.sql \
  -f supabase/migrations/supabase-migration-kadi-v2-rls-planning.sql \
  -f supabase/migrations/supabase-migration-kadi-v2-key-vault-column.sql

# 3. Apply Section 4 seeds and tenant-specific data per MIGRATIONS.md.
```

**Azure-specific differences from Supabase**:
- No `auth.users` from Supabase Auth — use Entra ID adapter (see `lib/auth/entra-adapter.ts` after KAR-A1 lands)
- No Supabase Storage — use Azure Blob (see `lib/storage/azure-blob-adapter.ts` after KAR-A3 lands)
- `auth.uid()` etc. are mocked — real implementation must read from Azure auth context

These adapters are stubbed in the codebase; vendor fills in the Azure-specific implementations.

---

## Troubleshooting

### Migration fails on `transaction_timeout`

The bootstrap was generated by `pg_dump` from a Postgres 17 instance. On Postgres 15/16 this will fail. Solution: use Postgres 17.

### `role "authenticated" does not exist`

You skipped `supabase/bootstrap/supabase-bootstrap-prerequisites.sql`. On vanilla Postgres (Path A or C) this is required. Re-apply it before the bootstrap.

### `operator class "public.gin_trgm_ops" does not exist`

`pg_trgm` extension is not installed. The prerequisites file installs it; ensure it ran successfully before the bootstrap.

### `schema "public" already exists`

Bootstrap was patched in 2026-05-09 to use `CREATE SCHEMA IF NOT EXISTS`. If you're on an older bootstrap file, pull the latest main.

---

## Verification Checklist

After setup, run these to confirm a clean install:

```sql
-- 1. Schema-Stand
SELECT
    (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public') AS tables,
    (SELECT COUNT(*) FROM pg_policies WHERE schemaname = 'public') AS rls,
    (SELECT COUNT(*) FROM information_schema.routines WHERE routine_schema = 'public') AS functions,
    (SELECT COUNT(*) FROM pg_indexes WHERE schemaname = 'public') AS indexes;
-- Expected: ~66 tables, ~159 RLS, ~90 functions, ~217 indexes

-- 2. The 6 once-missing tables exist
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
  AND table_name IN ('master_data_types', 'master_data_values', 'holiday_calendars', 'holiday_entries', 'user_holiday_preferences', 'app_settings')
ORDER BY table_name;
-- Expected: 6 rows

-- 3. Forward-compat column for Azure Key Vault
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'cp_tenant_environments' AND column_name = 'key_vault_secret_name';
-- Expected: 1 row
```

---

## Next Steps for the Vendor

1. **Read the architecture docs**:
   - `CLAUDE.md` — project conventions
   - `docs/adr/` — 18 ADRs for major design decisions
   - `MIGRATIONS.md` — full migration order

2. **Read the vendor handover bundle**:
   - `docs/vendor-handover/SupplierPulse_Vendor_Handover_Clean.md`
   - `docs/vendor-handover/SupplierPulse_KADi_V2_Open_Findings.md`

3. **Run the test suite**:
   ```bash
   npm run test
   npm run test:coverage
   npm run check:portability
   ```

4. **Ask** before touching:
   - `app/api/admin/*` (auto-loaded skill `service-role-audit` will flag)
   - `lib/supabase/admin.ts` (privileged client — see service-role-intent register)
   - Any RLS migration (must follow ADR 017 + 020)

---

## Document History

| Date | Change |
|------|--------|
| 2026-05-09 | Initial draft. Verified Path A end-to-end on local Docker. KAR-41 + KAR-42 + KAR-45 closure. |
