Skip to content

TML-2962: extension-aware contract infer omits pack-claimed public tables#901

Closed
wmadden-electric wants to merge 1 commit into
mainfrom
tml-2962-extension-aware-contract-infer
Closed

TML-2962: extension-aware contract infer omits pack-claimed public tables#901
wmadden-electric wants to merge 1 commit into
mainfrom
tml-2962-extension-aware-contract-infer

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

contract infer introspects the public schema only, so a table an extension pack owns in public leaked into the app's inferred contract.prisma. This makes infer extension-aware: it omits any introspected table a stack extension pack claims in its public namespace.

Slice G of the extension-supabase project (TML-2962). Independent of native enums, RLS, and the complete-contract work (Slice F).

How

  • inferPslContract (SQL family instance) derives the set of table names each stack pack claims in its public namespace — matched by namespace .id, not record key, because the on-disk hydration path (hydrateSqlNamespaceEntry) does not reconcile the two.
  • sqlSchemaIrToPslAst gains an optional claimedTableNames filter. It drops claimed tables and also strips surviving tables' foreign keys that reference an omitted table, so no dangling relation (a @relation to a non-existent model) is emitted.
  • Namespace-correctness: a pack claiming auth.users / storage.objects (non-public) never suppresses a same-named public app table.
  • Empty/absent claimed set → byte-identical output (the only production caller for pack-free stacks is unaffected; fixtures:check clean).

Tests

  • Unit: claimedTableNames filters the named table; no-options path keeps all tables.
  • Family-instance: a pack claiming a table in its public namespace → omitted; app table kept.
  • Namespace-correctness: same name claimed in a non-public namespace → app table kept.
  • Public namespace matched by .id even when keyed under a different record key.
  • Cross-boundary FK: a surviving table's FK to an omitted table drops the relation (no dangling model reference).

Verification

build · typecheck · family-sql tests (384/384) · fixtures:check (byte-identical) · lint:deps · lint:casts (delta 0) · check:upgrade-coverage — all green locally.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • SQL contract inference now respects claimed table names from public namespaces when generating models.
    • Added support for optional table-claim filtering during schema-to-PSL conversion.
  • Bug Fixes

    • Foreign keys pointing to omitted claimed tables are now removed from the generated output.
    • Public-namespace matching now relies on the namespace ID, improving consistency when names differ.
  • Tests

    • Added coverage for claimed-table filtering, public-namespace behavior, and foreign-key handling.

…bles

contract infer introspects the public schema only, so a table an extension
pack owns in public leaked into the app's inferred contract.prisma. Make
inferPslContract omit any introspected table a stack pack claims in its
public namespace (matched by namespace .id, not record key), and strip
surviving tables' foreign keys that reference an omitted table so no
dangling relation is emitted. Pack claims in non-public namespaces (auth,
storage) never suppress a same-named public app table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric requested a review from a team as a code owner July 2, 2026 07:42
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds table filtering to SQL-to-PSL schema inference. sqlSchemaIrToPslAst now accepts optional claimedTableNames to exclude tables (and dangling foreign keys) claimed by extension packs. control-instance.ts collects claimed table names from extensions' public namespace and passes them through. Tests cover both layers.

Changes

Claimed table filtering

Layer / File(s) Summary
Table/foreign-key filtering
packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts, packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts
Adds SqlSchemaIrToPslAstOptions with claimedTableNames, filters non-claimed tables and foreign keys referencing removed tables, and adds unit tests for filtered and unfiltered results.
Public-namespace claim collection and wiring
packages/2-sql/9-family/src/core/control-instance.ts
Imports ContractSpace, adds collectClaimedPublicTableNames to gather claimed table names from extension packs' public namespace, and updates inferPslContract to pass them to sqlSchemaIrToPslAst.
Extension-aware integration tests
packages/2-sql/9-family/test/psl-contract-infer/infer-psl-contract.extension-aware.test.ts
Adds test helpers for extensions, control stacks, and schema IR tables, plus assertions on omission/retention of tables and foreign keys based on namespace claims.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ControlInstance as control-instance.ts
  participant Collector as collectClaimedPublicTableNames
  participant Converter as sqlSchemaIrToPslAst

  ControlInstance->>Collector: extensions
  Collector-->>ControlInstance: claimedTableNames set
  ControlInstance->>Converter: schemaIR, { claimedTableNames }
  Converter->>Converter: filterClaimedTables(schemaIR)
  Converter-->>ControlInstance: PslDocumentAst (filtered)
Loading

Suggested reviewers: wmadden, jkomyno

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: extension-aware contract inference now omits pack-claimed public tables.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2962-extension-aware-contract-infer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@901

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@901

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@901

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@901

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@901

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@901

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@901

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@901

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@901

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@901

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@901

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@901

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@901

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@901

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@901

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@901

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@901

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@901

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@901

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@901

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@901

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@901

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@901

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@901

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@901

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@901

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@901

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@901

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@901

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@901

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@901

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@901

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@901

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@901

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@901

prisma-next

npm i https://pkg.pr.new/prisma-next@901

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@901

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@901

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@901

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@901

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@901

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@901

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@901

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@901

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@901

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@901

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@901

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@901

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@901

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@901

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@901

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@901

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@901

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@901

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@901

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@901

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@901

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@901

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@901

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@901

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@901

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@901

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@901

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@901

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@901

commit: 153257c

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.34 KB (+0.09% 🔺)
postgres / emit 147.55 KB (+0.12% 🔺)
mongo / no-emit 96.52 KB (0%)
mongo / emit 89.23 KB (0%)
cf-worker / no-emit 188.45 KB (+0.11% 🔺)
cf-worker / emit 173.84 KB (+0.09% 🔺)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts (1)

205-259: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a direct unit test for FK-stripping in filterClaimedTables.

Both new tests here cover table omission only; dangling foreign-key removal is exercised solely at the extension-aware integration layer. A focused unit test in this file (a table with a FK to a claimed table, asserting the FK is stripped while the table survives) would make failures in filterClaimedTables easier to isolate from family-instance wiring.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts`
around lines 205 - 259, Add a focused unit test around `filterClaimedTables` in
this spec file: create a schema where a kept table has a foreign key pointing to
a claimed table, then assert the kept table still appears in the AST but its
foreign key is removed. Use the existing `sqlSchemaIrToPslAst` and
`flatPslModels` helpers to locate the behavior, and keep the test separate from
the extension-aware integration coverage so FK-stripping failures are isolated.
packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts (1)

111-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

.map() return-tuple inference likely widens to any via Object.fromEntries.

The callback in .map(([tableName, table]) => { ... return [tableName, table]; ... return [tableName, {...}]; }) has a block body with bare array-literal returns and no explicit return type, so TypeScript infers the element type as (string | SqlTableIR)[] rather than a [string, SqlTableIR] tuple. Object.fromEntries falls back to its (entries: Iterable<readonly any[]>) => any overload in that case, so tables (and therefore filteredSchemaIR) loses type-checking and becomes effectively any. It still works at runtime, but silently defeats compile-time safety for this construction.

Please verify with tsc whether this is actually inferred as any, and consider annotating the tuple explicitly.

♻️ Proposed fix to preserve tuple typing
   const tables = Object.fromEntries(
     Object.entries(schemaIR.tables)
       .filter(([tableName]) => !claimedTableNames.has(tableName))
-      .map(([tableName, table]) => {
+      .map(([tableName, table]): [string, SqlSchemaIR['tables'][string]] => {
         const survivingForeignKeys = table.foreignKeys.filter(
           (fk) => !claimedTableNames.has(fk.referencedTable),
         );
         if (survivingForeignKeys.length === table.foreignKeys.length) {
           return [tableName, table];
         }
         return [tableName, { ...table, foreignKeys: survivingForeignKeys }];
       }),
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts`
around lines 111 - 134, The `filterClaimedTables` construction is losing tuple
typing in the `Object.fromEntries` pipeline, so `tables` can degrade to `any`
and weaken type safety. Update the `.map()` callback in `filterClaimedTables` to
return an explicitly typed `[tableName, table]`-style tuple (or annotate the
callback return type) so `Object.fromEntries` selects the typed overload and
preserves the `SqlSchemaIR` shape. Verify the inferred type with `tsc` after the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts`:
- Around line 111-134: The `filterClaimedTables` construction is losing tuple
typing in the `Object.fromEntries` pipeline, so `tables` can degrade to `any`
and weaken type safety. Update the `.map()` callback in `filterClaimedTables` to
return an explicitly typed `[tableName, table]`-style tuple (or annotate the
callback return type) so `Object.fromEntries` selects the typed overload and
preserves the `SqlSchemaIR` shape. Verify the inferred type with `tsc` after the
change.

In
`@packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts`:
- Around line 205-259: Add a focused unit test around `filterClaimedTables` in
this spec file: create a schema where a kept table has a foreign key pointing to
a claimed table, then assert the kept table still appears in the AST but its
foreign key is removed. Use the existing `sqlSchemaIrToPslAst` and
`flatPslModels` helpers to locate the behavior, and keep the test separate from
the extension-aware integration coverage so FK-stripping failures are isolated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 7624bf72-a8b4-4992-99c2-236d854eb27a

📥 Commits

Reviewing files that changed from the base of the PR and between 5c898e7 and 153257c.

⛔ Files ignored due to path filters (1)
  • projects/extension-supabase/slices/g-extension-aware-infer/spec.md is excluded by !projects/**
📒 Files selected for processing (4)
  • packages/2-sql/9-family/src/core/control-instance.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts
  • packages/2-sql/9-family/test/psl-contract-infer/infer-psl-contract.extension-aware.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts

@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Closing: built prematurely on the flat SqlSchemaIR.tables substrate that #894 retires in favour of the database → namespace → table → policy node tree. The requirement (TML-2962) stands; G will be rebuilt on top of #894, where omitting pack-claimed elements is a natural namespace-level operation rather than the bare-name workaround here. Branch kept for reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant