Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions projects/extension-supabase/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Current state of the example:

**Non-blocking follow-on:** enum-typed `auth.*` columns (enums-as-domain-concept project) can attach to this slice or land later.

### Slice D — `service_role` queries Supabase-internal namespaces via a secondary `db.supabase` root
### Slice D — `service_role` queries Supabase-internal namespaces via a secondary `db.supabase` root ✅ merged (#845)

**Gate:** none — facade composition, independent of postgres-rls #771. (Reframed from "explicit `auth.users` query off the app db": that doesn't work and isn't meant to — cross-space *querying* off the app db was deliberately not built, and only `service_role` has `auth.*` grants. See decision [C15](../supabase-integration/decisions.md). Slice contract: [`slices/d-service-role-internal-namespaces/spec.md`](slices/d-service-role-internal-namespaces/spec.md).)

Expand All @@ -79,9 +79,36 @@ Current state of the example:
- [ ] Type-level test (against the app contract): `asServiceRole().supabase.{sql,orm}` carry `auth`/`storage`; the primary `asServiceRole().sql` does not; `asAnon()`/`asUser()` have no `.supabase`.
- [ ] `overview.md` + decision [C15](../supabase-integration/decisions.md) reflect the secondary-root surface (done in this slice).

### Slice F — Complete, faithful Supabase contract

**Gate:** native enums (in flight). Supabase's `auth` schema uses native Postgres enum types (`aal_level`, `factor_type`, `factor_status`, `code_challenge_method`, `one_time_token_type`) with enum-typed columns; the shipped contract can't faithfully represent those tables until native-enum support lands.

**Goal:** the extension ships a **complete, faithful** contract of everything it owns — all `auth`/`storage` (and any other owned) tables, the native enum types, and roles — not the 4-table minimum. This is the source of truth for *what the extension owns*, consumed by (a) `db verify` against a real Supabase DB, (b) the `db.supabase` admin surface, and (c) Slice G's infer-subtraction. Per decision [C8](../supabase-integration/decisions.md), generate it by **introspecting a reference Supabase project** and emitting `contract.json` (hand-authoring ~25 tables + enums is toil and drift-prone).

**DoD tasks:**
- [ ] Introspect a reference Supabase project; emit the full `contract.json` (all owned tables + native enum types + roles), `defaultControl: 'external'`.
- [ ] `db verify` against a real Supabase DB passes (declared shapes match; extras tolerated under `external`).
- [ ] The `db.supabase` admin surface exposes the full owned table set.
- [ ] Round-trip property holds: introspect → emit → re-introspect → diff empty.

**Shaping needed at pickup:** the introspection→emit pipeline for extension contracts, and how far "owned" extends (`auth`/`storage` certainly; `realtime`/`extensions`/`vault`/`pgsodium`?).

### Slice G — Extension-aware `contract infer` in a Supabase environment

**Gate:** Slice F (defines what the extension owns) + framework/CLI changes to `contract infer`.

**Goal:** running `contract infer` with the Supabase pack in the stack writes a **meaningful `contract.prisma` that omits every element the Supabase extension owns** — the app author gets only their own schema (`managed`); the pack supplies `auth`/`storage`/… (`external`) via `extensionPacks`. Today `contract infer` is neither control-policy- nor extension-aware: it introspects the default `public` schema and emits everything `managed`, blind to pack ownership (see decision [C16](../supabase-integration/decisions.md)).

**DoD tasks:**
- [ ] `contract infer --db <supabase-url>` with `extensionPacks: [supabasePack]` writes a `contract.prisma` containing only the app's own (un-owned) schema — no `auth`/`storage`/pack-owned tables, enum types, or roles.
- [ ] The inferred contract + the pack compose to the full picture and `db verify` passes clean.
- [ ] Integration test proving the omission against a shim/real Supabase DB.

**Shaping needed at pickup:** how `contract infer` learns the pack's owned set (from the stack's extension-pack contracts / Slice F), and the subtraction rule (by owned namespace; handling any pack-owned objects in shared schemas like `public`).

### Slice E — Docs + real-Supabase acceptance + close-out

**Gate:** B, C, D done; explicit-namespace-dsl project close-out.
**Gate:** B, C, D, F, G done; explicit-namespace-dsl project close-out.

**Goal:** the package is launch-ready.

Expand Down
2 changes: 2 additions & 0 deletions projects/supabase-integration/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ When a decision changes, **update this file**. When a new design hole surfaces,
- **Caveat (docs, not enforced):** Supabase-internal schema can drift across platform upgrades; for user *management* prefer the GoTrue Admin API. Direct `service_role` SQL is for ad-hoc/admin reads.
- **Future direction (interim → principled).** The two-runtimes-sharing-a-pool secondary root is the pragmatic interim. The principled end state is a single `Runtime` bound to the **aggregate contract** (composed app + extension spaces) serving all roots natively — and the substrate real cross-space querying would ride on. Deferred (see [`deferred.md`](deferred.md)).

- **C16. Brownfield `contract infer` in a Supabase environment must omit extension-owned elements.** ✅ Settled 2026-07-02 (scope decision). Adopting an existing Supabase app via `contract infer` must write a meaningful `contract.prisma` containing only the app's *own* schema (`managed`), omitting every element the Supabase extension owns (`auth`/`storage`/… tables, native enum types, roles) — those are supplied by `extensionPacks: [supabasePack]` (`external`). Today `contract infer` is neither control-policy- nor extension-aware: with no contract it introspects the default `public` schema and emits everything `managed`, blind to pack ownership; the pack's `external` policy plays no part in inference. Making it extension-aware — subtracting the stack packs' owned set (defined by the pack's complete contract, extension-supabase [Slice F](../extension-supabase/plan.md)) from the inferred output — is a **launch requirement** of this project ([Slice G](../extension-supabase/plan.md)), not a follow-up. This pulls the previously-deferred "introspection-based emit" and "`adopt --from-database`" items into scope. Ownership resolution (by owned namespace; how the pack expresses its owned set to the CLI; any pack-owned objects in shared schemas like `public`) is shaped at Slice G pickup.

## Architectural offcuts (deserve their own treatment, not RLS-specific)

These surfaced during RLS / Supabase discussions but apply to PSL or the framework as a whole. Each will be drafted as an ADR or architecture doc when the right project pulls them in.
Expand Down
4 changes: 2 additions & 2 deletions projects/supabase-integration/deferred.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Explicit list of things we've decided are **not** part of the Supabase integrati
- **Realtime support.** Supabase Realtime is a separate subsystem (WebSocket-based change feed); not on the v0.1 path. Decision recorded upstream from when this work started.
- **Storage API.** `storage.*` tables are visible via the `external`-control contract, but uploading/managing storage objects is not a database concern. We don't ship file-upload helpers.
- **`@supabase/supabase-js` parity for non-DB features.** Auth flows (sign in, password reset), edge functions, etc. — outside Prisma Next's remit.
- **Introspection-based emit of the Supabase contract.** We hand-author the shipped `contract.json` for v0.1. An emitter that introspects a Supabase Postgres database is plausible follow-up work, not v0.1.
- **`prisma-next adopt --from-database` introspection.** Same family of work as above; needed for users migrating from existing Supabase apps. Likely the next obvious project after this one.
- ~~**Introspection-based emit of the Supabase contract.**~~ **Moved into scope (2026-07-02)** — the extension now ships a complete, introspection-generated contract of everything it owns (extension-supabase Slice F; decision [C16](decisions.md)).
- ~~**`prisma-next adopt --from-database` introspection.**~~ **Partly moved into scope (2026-07-02)** — extension-aware `contract infer` (writing a meaningful app-only `contract.prisma` that omits Supabase-owned elements) is a launch requirement (extension-supabase Slice G; decision [C16](decisions.md)). A general `adopt` UX beyond that remains follow-up.
- **Identity providers other than Supabase.** Auth0, Clerk, custom auth, JWT-from-anywhere. The control-policy/cross-contract-ref/RLS machinery is reusable for these (they'd each become their own extension package), but they're not v0.1 targets.
- **Typed `m.sql\`...\`` template tag for RLS predicates.** Plain strings only for v0.1. The typed template tag is real future polish; it's not on this project's critical path.
- **Visibility / encapsulation between contract spaces.** All extension contract spaces are visible to app contracts. Tooling-level "this extension's internals are private" controls are a future concern; for v0.1 every extension is fully visible.
Expand Down
Loading