Skip to content

Commit d601f36

Browse files
wmaddenclaude
andcommitted
Record Supabase walking-skeleton strategy and lane sequencing
Document the walking-skeleton delivery approach for the Supabase integration: a single runnable examples/supabase app, stood up early by extension-supabase M1 and grown one feature at a time, with a "wire your feature into the running example" definition-of-done clause on every constituent. Capture the two-lane test strategy (hermetic PGlite + bootstrapSupabaseShim; real-Supabase acceptance) and the current lane sequencing. Strip the stale May-18 launch dates throughout. - umbrella: add C13/C14 decisions, walking-skeleton + running-order sections, refresh the status table - extension-supabase: M1 stands up the skeleton + authors the shim; M3 reframed to finalize the grown example; example path corrected to examples/supabase; two resolved open questions - cross-contract-refs / postgres-rls / runtime-target-layer / explicit-namespace-dsl: add walking-skeleton DoD hooks Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Will Madden <madden@prisma.io>
1 parent 5b4fc83 commit d601f36

9 files changed

Lines changed: 106 additions & 37 deletions

File tree

projects/cross-contract-refs/plan.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
9797

9898
**Validation:** docs review by the team; AC1–AC10 all green and verified through merged PRs.
9999

100+
## Walking-skeleton integration (cross-cutting DoD)
101+
102+
Per the umbrella's walking-skeleton strategy (decisions [C13/C14](../supabase-integration/decisions.md); [README](../supabase-integration/README.md) §"Walking skeleton"), this project's definition of done includes wiring its feature into the running `examples/supabase` app:
103+
104+
- [ ] Add the `Profile.userId → auth.User.id` cross-contract FK (with `onDelete: 'cascade'`) to the `examples/supabase` app contract; confirm the planner emits qualified `REFERENCES "auth"."users"("id")`.
105+
- [ ] Cover it in the example's hermetic test lane (PGlite + `bootstrapSupabaseShim`): migration creates the FK; a cascade delete from `auth.users` removes the dependent `public.profile` row.
106+
100107
## Risks and mitigations
101108

102109
- **Risk:** the colon-prefix tokenizer change in PSL is a backwards-incompatible lexer change. Any existing code that used `:` inside a type position (unlikely, but possible in malformed contracts) breaks.

projects/explicit-namespace-dsl/plan.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Single PR; dispatches are logical execution order for one implementer (or one re
3939
- **Outcome:** Supabase-shaped fixture: `namespace public { model Profile … }` plus extension `auth` `users`; emit contract; integration test queries `db.sql.auth.users` and `db.sql.public.profile` (and ORM equivalents) against PGlite; AC1–AC4 satisfied.
4040
- **Builds on:** D2 runtime resolution.
4141
- **Hands to:** D4 close-out.
42-
- **Focus:** Authoring + emit + query in one test; keep example minimal if extension-supabase will own the polished demo.
42+
- **Focus:** Authoring + emit + query in one test. **Wire this into the `examples/supabase` walking skeleton** (decisions [C13/C14](../supabase-integration/decisions.md)) rather than a throwaway fixture — add the `auth.users`-alongside-`public.users` explicit-accessor query to the running example, tested via PGlite + `bootstrapSupabaseShim`. `extension-supabase` finalizes the polished demo on top.
4343

4444
### D4 — Close-out (ADR, upgrade instructions, umbrella tracker)
4545

@@ -54,6 +54,7 @@ Single PR; dispatches are logical execution order for one implementer (or one re
5454
- [ ] Collision-behaviour decision recorded (ADR or spec amendment).
5555
- [ ] `pnpm test:packages` + relevant integration tests green; `pnpm lint:deps` passes.
5656
- [ ] No regressions on default-namespace demo queries (AC3).
57+
- [ ] The `examples/supabase` walking skeleton exercises the explicit `auth.users` / `public.users` accessor (cross-cutting walking-skeleton DoD; [README](../supabase-integration/README.md) §"Walking skeleton").
5758
- [ ] PR linked from [TML-2550](https://linear.app/prisma-company/issue/TML-2550); operator notified that [TML-2503](https://linear.app/prisma-company/issue/TML-2503) explicit-accessor prerequisite is cleared.
5859

5960
## Risks and mitigations

projects/extension-supabase/plan.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Summary
44

5-
The project ships in four PRs sequenced contract → runtime → example → polish. M1 scaffolds the `@prisma-next/extension-supabase` package: subpath exports, hand-authored `contract.json`, `contract.d.ts`, branded `/contract` handles, the `/pack` descriptor. M2 builds the runtime facade: `SupabaseRuntime extends PostgresRuntime`, JWT validation (sync via `jwtSecret`, async warmup via `jwksUrl`), `SupabaseDb` with `asUser` / `asAnon` / `asServiceRole`, `RoleBoundDb` extending `Db` with `transaction(...)`, the `SET LOCAL` + implicit-transaction plumbing wired through `withRawConnection`. M3 ships the canonical example app at `packages/3-extensions/supabase/examples/basic/` plus its integration tests against PGlite-with-Supabase-schema. M4 closes out documentation, the launch-blocking real-Supabase-project acceptance test, and the umbrella decisions log.
5+
The project ships in four PRs sequenced contract → runtime → example → polish, and is the home of the **walking skeleton** — the runnable `examples/supabase` app that every other constituent wires into as it lands (strategy + growth table in the [umbrella README](../supabase-integration/README.md) §"Walking skeleton"; decisions [C13/C14](../supabase-integration/decisions.md)). M1 scaffolds the `@prisma-next/extension-supabase` package (subpath exports, hand-authored `contract.json`, `contract.d.ts`, branded `/contract` handles, the `/pack` descriptor) **and stands up the skeleton** running on the stock `@prisma-next/postgres/runtime`; the Supabase `/runtime` subpath is deferred to M2, so M1 is unblocked by the foundation + control-policy alone. M2 builds the runtime facade: `SupabaseRuntime extends PostgresRuntime`, JWT validation (sync via `jwtSecret`, async warmup via `jwksUrl`), `SupabaseDb` with `asUser` / `asAnon` / `asServiceRole`, `RoleBoundDb` extending `Db` with `transaction(...)`, the `SET LOCAL` + implicit-transaction plumbing wired through `withRawConnection`. M3 finalizes the (by-then incrementally-grown) `examples/supabase` app and adds the live-query RLS-enforcement e2e that depends on M2's role binding, running on the hermetic PGlite + Supabase-shim lane. M4 closes out documentation, the real-Supabase acceptance lane, and the umbrella decisions log.
66

77
**Spec:** [`projects/extension-supabase/spec.md`](spec.md)
88
**Linear:** _(to be created — see project tracker in umbrella `projects/supabase-integration/README.md`)_
@@ -17,21 +17,21 @@ This project is the integration layer; it consumes **all four** sibling projects
1717
- **[postgres-rls](../postgres-rls/spec.md)**`.rls(...)` authoring + `PostgresRole` IR + verifier algorithm.
1818
- **[runtime-target-layer](../runtime-target-layer/spec.md)**`PostgresRuntime` base class + `withRawConnection` accessor.
1919

20-
Resulting global sequence (within the Supabase umbrella): **TML-2459 + control-policy****cross-contract-refs ∥ postgres-rls ∥ runtime-target-layer****this project** (which is the May 18th launch milestone).
20+
Resulting global sequence (within the Supabase umbrella): **TML-2459 + control-policy****cross-contract-refs ∥ postgres-rls ∥ runtime-target-layer****this project** (the integration / launch milestone).
2121

22-
A slip in any upstream project cascades into a launch-date risk. The implementer should watch upstream PR status weekly and surface blockers early.
22+
A slip in any upstream project cascades into this project. The implementer should watch upstream PR status and surface blockers early.
2323

2424
## Milestones
2525

2626
The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each milestone is one PR.
2727

28-
### M1 — Package scaffolding + contract + typed handles
28+
### M1 — Package scaffolding + contract + typed handles + walking skeleton
2929

30-
**Goal:** the package exists with the subpath exports, the shipped contract is in place, and an app contract can import `AuthUser` and `roles` and reference them. No runtime yet.
30+
**Goal:** the package exists with the subpath exports, the shipped contract is in place, an app contract can import `AuthUser` and `roles` and reference them, and the `examples/supabase` walking skeleton runs end-to-end against `public.*` on the stock Postgres runtime. No Supabase runtime yet (`/runtime` is M2).
3131

3232
**Tasks:**
3333

34-
- [ ] Scaffold the package at `packages/3-extensions/supabase/`. `package.json` with `exports` field declaring `/pack`, `/contract`, `/runtime` subpaths. `tsconfig.json` matching the existing extension packages (`cipherstash`, `pgvector`).
34+
- [ ] Scaffold the package at `packages/3-extensions/supabase/`. `package.json` with `exports` field declaring `/pack`, `/contract`, `/runtime` subpaths. `tsconfig.json` matching the existing extension packages (`cipherstash`, `pgvector`). The `/runtime` subpath may be a stub in M1 (filled in by M2); `/pack` + `/contract` are complete.
3535
- [ ] Hand-author `src/contract/contract.json`. Models: `AuthUser`, `AuthIdentity`, `StorageBucket`, `StorageObject` (the minimum set the example app needs). Roles: `anon`, `authenticated`, `service_role`. `defaultControl: 'external'`. Namespaces: `auth`, `storage`, `realtime`, `extensions`.
3636
- [ ] Generate `src/contract/contract.d.ts` via the standard emitter pipeline. The contract type is consumed by the runtime facade's generics in M2.
3737
- [ ] Hand-author `src/contract/index.ts` exporting branded `ModelHandle<'supabase', …>` instances for each model and `roles: { anon, authenticated, serviceRole }` as `RoleRef<'supabase'>` values. Each handle exposes `.refs.<column>` accessors typed from the `contract.d.ts` model shape.
@@ -43,8 +43,10 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
4343
- The contract lowers correctly when the app's `defineContract` declares `extensionPacks: [supabasePack]`.
4444
- [ ] `pnpm lint:deps` passes. `/pack` does not transitively import runtime; `/contract` does not transitively import SDK/runtime.
4545
- [ ] Bundle-size measurement: `/pack` under 5 KB gzip; `/contract` under 10 KB gzip.
46+
- [ ] **Stand up the walking skeleton** at `examples/supabase` (top-level, alongside the other example apps). Minimal runnable app: `prisma-next.config.ts` wiring `extensionPacks: [supabasePack]`; an app contract with a `Profile` model in `public`; a `db.ts` that builds a db via the **stock `@prisma-next/postgres/runtime`** factory (not the Supabase `/runtime` — that's M2); one handler that runs a basic `public.*` query. Gate it out of the default test matrix until green (or mark its integration test `.skip` / `todo`) so an intentionally-WIP example never reds CI.
47+
- [ ] **Author `bootstrapSupabaseShim(client)`** (mirroring [`test/integration/test/postgres-bootstrap.ts`](../../test/integration/test/postgres-bootstrap.ts)) for the hermetic PGlite lane: seeds the `anon`/`authenticated`/`service_role`/`authenticator` roles, the `auth` schema + `auth.users`, and `auth.uid()`/`auth.jwt()`/`auth.role()` SQL functions reading the session GUCs. This is the shared fixture every later constituent's `examples/supabase` test uses (decision [C14](../supabase-integration/decisions.md)).
4648

47-
**Validation:** AC1, AC2, AC3 verified through end-to-end authoring smoke tests. No runtime yet; AC4 onwards land in M2.
49+
**Validation:** AC1, AC2, AC3 verified through end-to-end authoring smoke tests. The skeleton boots and runs a `public.*` query on the stock Postgres runtime against the shim-bootstrapped PGlite database. No Supabase runtime yet; AC4 onwards land in M2.
4850

4951
### M2 — Runtime facade (`SupabaseRuntime`, role binding, SET LOCAL)
5052

@@ -67,21 +69,21 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
6769
- `db.asUser(badJwt)` throws synchronously; no connection acquired.
6870
- `db.asUser(goodJwt)` returns a `RoleBoundDb`.
6971
- `SupabaseDb` doesn't expose `.sql.from(...)` at the top level (type-level assertion via a `// @ts-expect-error` test).
70-
- [ ] Integration tests against PGlite-with-Supabase-schema (a hand-crafted minimal schema mirroring `auth.users` shape):
72+
- [ ] Integration tests against PGlite seeded with `bootstrapSupabaseShim(client)` (from M1):
7173
- Statement sequence on `asUser(jwt).sql.from(...).execute()` is `BEGIN; SET LOCAL role; SET LOCAL request.jwt.claims; <query>; COMMIT;`. Verified by hooking the connection and recording statements.
7274
- User middleware sees only the logical query (the `BEGIN` / `SET LOCAL` / `COMMIT` statements are invisible). Verified by a logging middleware in the test fixture.
7375
- `asUser(jwt).transaction(async (tx) => { tx.sql..., tx.sql... })` issues one BEGIN + one SET LOCAL + two queries + one COMMIT.
7476

7577
**Validation:** AC4, AC5, AC6, AC7 verified.
7678

77-
### M3 — Example app + RLS-enforcement integration tests
79+
### M3 — Finalize the example app + live-query RLS-enforcement e2e
7880

79-
**Goal:** the canonical demo runs end-to-end in CI. Every framework primitive is exercised against a real database; RLS enforcement is observable.
81+
**Goal:** the `examples/supabase` walking skeleton — grown incrementally across the constituent lands — is finalized as the canonical demo, ungated in CI, and the RLS-enforcement e2e that depends on M2's automatic role binding is added. By this point the example already exercises the cross-contract FK, RLS policies, and namespace queries (each wired in by its own constituent's DoD); M3 assembles the full handler flow and proves enforcement through the runtime rather than by hand.
8082

8183
**Tasks:**
8284

83-
- [ ] Scaffold the example app at `packages/3-extensions/supabase/examples/basic/`. App contract (with `Profile` model and cross-contract FK to `AuthUser`, RLS policies), `prisma-next.config.ts`, `db.ts` factory, handler module exposing `listProfiles` / `createProfile` / `updateProfile` / `adminListProfiles`.
84-
- [ ] Integration tests against PGlite-with-Supabase-schema:
85+
- [ ] Finalize the example app at `examples/supabase` (stood up in M1, grown by the constituents). Complete the app contract (`Profile` with cross-contract FK to `AuthUser` + RLS policies), `db.ts` switched to the Supabase `/runtime` factory (`supabase({...})`), and the handler module exposing `listProfiles` / `createProfile` / `updateProfile` / `adminListProfiles`. Ungate it from the default test matrix.
86+
- [ ] Live-query RLS-enforcement integration tests against PGlite seeded with `bootstrapSupabaseShim(client)` — these are the tests that need M2's automatic role binding (policy correctness via manual `SET ROLE` was already proven in the `postgres-rls` project):
8587
- Migration: `prisma-next push` against the test database creates `public.profile` with FK to `auth.users.id`; creates the RLS policies; enables RLS on the table.
8688
- RLS enforcement: a query through `asAnon()` returns rows whose policy permits anon read; returns zero rows where it doesn't.
8789
- RLS enforcement: a query through `asUser(jwtForUserA)` can update User A's row but not User B's row (zero rows updated).
@@ -120,8 +122,8 @@ The four PRs below correspond to the four milestones (M1, M2, M3, M4). Each mile
120122

121123
## Risks and mitigations
122124

123-
- **Risk:** the May 18th launch date hinges on five upstream projects all landing. Any slip cascades.
124-
- **Mitigation:** the four sibling projects + control-policy are independent. The umbrella tracker (in `projects/supabase-integration/README.md`) lists each constituent's status. The implementer of this project watches upstream progress weekly. If a slip is foreseeable >1 week out, scope-cut decisions surface to the team: either delay the launch, or carve a smaller v0.1 (e.g. drop the example app from launch; ship the package without it; backfill the example post-launch).
125+
- **Risk:** launch hinges on five upstream projects all landing. Any slip cascades.
126+
- **Mitigation:** the four sibling projects + control-policy are independent. The umbrella tracker (in `projects/supabase-integration/README.md`) lists each constituent's status. The implementer of this project watches upstream progress. If a slip is foreseeable, scope-cut decisions surface to the team: either delay the launch, or carve a smaller v0.1 (e.g. drop the example app from launch; ship the package without it; backfill the example post-launch).
125127
- **Risk:** JWT validation has subtle bugs (audience checking, clock-skew tolerance, algorithm confusion). A bad JWT validator is a security hole.
126128
- **Mitigation:** use `jose` (or another mature library) rather than hand-rolling validation. Set `algorithms` strictly to `['HS256', 'RS256']` (or whatever Supabase actually uses) to prevent algorithm confusion. Test the validation path explicitly against every documented failure mode.
127129
- **Risk:** PGlite-with-Supabase-schema diverges from real Supabase behaviour in ways that hide bugs.

0 commit comments

Comments
 (0)