Infrastructure Gotchas
Three durable, non-obvious infrastructure traps in the Levandor CRM monorepo, found during a 2026-06-09 codebase validation. Each contradicts a checked-in doc (mostly the root CLAUDE.md and supabase/CLAUDE.md), so an agent that trusts those docs will be misled.
For Agents — trust order
When the in-repo docs disagree:
web/CLAUDE.mdis correct; the root/CLAUDE.mdandsupabase/CLAUDE.mdare partially STALE. This note records exactly where, with ground-truth verified againstgit ls-tree HEAD,web/package.json, and the source files on 2026-06-09.
1. Migrations: two directories, only one is live
Put new migrations in
web/supabase/migrations/, timestamped — NOT insupabase/migrations/The live Supabase project (
mkofmdtdldxgmmolxxhc) tracks timestamped migrations. The numbered00N_files are a stale secondary copy.
| Directory | Naming | Status |
|---|---|---|
web/supabase/migrations/ | YYYYMMDDHHMMSS_<name>.sql (timestamped) | LIVE — what the remote project actually tracks (~79 files) |
supabase/migrations/ | 00N_<name>.sql (numbered: 001_client_portal_schema … 010_disclosure_schema) | STALE / secondary copy — bodies were re-applied under timestamped names |
- Migrations are applied to the remote via the Supabase MCP
apply_migrationtool (which records a timestamp version), orsupabase db push. - There is NO
supabase/config.tomlanywhere in the repo, and NO CI / GitHub Actions that auto-applies a migrations folder (.github/workflows/has nothing migration-related). Nothing links the numbered folder to the live DB. - The numbered
00N_files still get written inside feature commits (e.g.dfa7666 feat(disclosure): crm_vuln_lifecycle … tables,0748631 … promote_vuln RPCs) — so they look authoritative but are not the applied source.
Rule: New migration → web/supabase/migrations/<timestamp>_<name>.sql, apply via MCP apply_migration (or supabase db push). Do NOT add new numbered 00N_ files to supabase/migrations/.
Contradicts
supabase/CLAUDE.md
supabase/CLAUDE.mdshows migrations living insupabase/migrations/with only001_client_portal_schema.sqland says “Run migrations via Supabase CLI or dashboard.” Both the location emphasis and the file list are stale.
2. Generated DB types are duplicated in two files — and the regen script touches the wrong one
types.tsis the file most code actually imports, butgen-typesonly regeneratesdatabase.types.tsThe two files drift unless you regenerate both.
Two near-identical ~146K generated copies exist:
| File | Size | Who imports it |
|---|---|---|
packages/shared/src/types.ts | ~146K | The live one. db-mappings.ts imports Tables/Enums/TablesInsert from ./types; the @levandor/shared barrel (index.ts) re-exports Database/DashboardSummary from ./types. So nearly every @levandor/shared consumer transitively uses types.ts. |
packages/shared/src/database.types.ts | ~145K | A second copy; some components import it directly. |
The trap
The
gen-typesscript inweb/package.jsonwrites only todatabase.types.ts:"gen-types": "supabase gen types typescript --project-id mkofmdtdldxgmmolxxhc --schema public > ../packages/shared/src/database.types.ts"But
db-mappings.tsand the shared barrel read fromtypes.ts. Runninggen-typesalone updates the copy that most code does NOT import. After a schema change you must regenerate (or hand-sync) both files, or the livetypes.tssilently goes out of date.
- The script correctly targets the remote project (
--project-id mkofmdtdldxgmmolxxhc). - The
supabase/CLAUDE.mdsnippet showingsupabase gen types typescript --local > … database.types.tsis stale on two counts: it uses--local(no local stack is the source of truth here) and it only writes the one file.
3. Auth is Cloudflare ZTNA, NOT Clerk — the root docs are wrong
The admin CRM does NOT use Clerk. Root
/CLAUDE.mdandsupabase/CLAUDE.mdare wrong.
supabase/CLAUDE.mdliterally says “Clerk handles authentication.” This is FALSE for the live admin app.
Real flow (per web/CLAUDE.md, which is correct — see security for the full version):
- Cloudflare Zero Trust (ZTNA) authenticates at the edge → sets the
CF_Authorizationcookie. There is no in-app sign-in page, no Clerk inweb/. - The SPA lifts the cookie and POSTs it to the
cf-access-authedge function, which verifies the CF JWT against CF Access JWKS, looks uppersonby email, and mints a Supabase access token withsub = person.id(role = "authenticated", ~1h). - RLS keys off
auth.uid()/auth.jwt()->>'email'(via theget_my_person_id()helper).
RLS shape
Most RLS tables use
authenticated-role policies withUSING (true)— auth is enforced upstream at the Cloudflare edge, so the inner DB policies are permissive for any authenticated principal.service_role FOR ALLpolicies exist for edge-function workers. (Per-user tables likenotificationare the exception and lock down toauth.uid().)
Establishing migrations (in web/supabase/migrations/):
20260331000000_auth_cfztna_migration.sql20260401010000_ztna_open_all_rls.sql20260426100000_enforce_authenticated_rls.sql
Clerk is NOT entirely absent from the repo
The client portal (
userspace/) — a separate app for external clients — does use Clerk. The trap is specifically the root/supabase docs claiming Clerk for the admin CRM (web/), which is wrong.
Verification snapshot (2026-06-09)
For Agents
Ground-truth checks behind this note, in case you want to re-verify:
git ls-tree -r --name-only HEAD -- supabase/migrations→ lists001_…010_numbered files (the stale copy).ls web/supabase/migrations | wc -l→ ~79 timestamped files (the live copy).- No
supabase/config.toml;.github/workflows/has no migration-applying job.packages/shared/src/{types.ts,database.types.ts}→ both ~146K;db-mappings.ts+index.tsimport from./types;gen-types(web/package.json) writes onlydatabase.types.ts.supabase/CLAUDE.md:24→ “Clerk handles authentication.” (stale)
Related
- agent-context-crm - Agent quick reference (auth + data-layer)
- security - Full CF ZTNA → Supabase JWT auth architecture
- tech-debt - Other known debt
- levandor-crm - Project overview