Skip to content

contract infer signs the database: full-fidelity indexes, policy blocks, @@rls — scenario A complete#1052

Open
wmadden-electric wants to merge 15 commits into
slice/rls-exact-namesfrom
slice/infer-round-trip
Open

contract infer signs the database: full-fidelity indexes, policy blocks, @@rls — scenario A complete#1052
wmadden-electric wants to merge 15 commits into
slice/rls-exact-namesfrom
slice/infer-round-trip

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

At a glance

Sign a database this toolchain has never seen:

contract infer → emit → db verify: zero issues → db update --dry-run: zero operations

— proven end-to-end on a raw-SQL database carrying an expression index, a partial index, a unique expression index, and PERMISSIVE + RESTRICTIVE RLS policies. And the second step, adopting it into management: flip one index's map: to name: and delete one policy's @@map, and the widening plan is exactly two ALTER … RENAME statements. The emitted contract is the database's signature. This closes scenario A and the functional-indexes project's build phase.

Decision

Slice 4 of the functional-indexes project (spec projects/functional-indexes/specs/infer-round-trip.spec.md on the base branch): contract infer emits everything the identity model can hold.

  • Indexes at full fidelity with managed re-detection. The slice-1 transitional skips (expression- and where-carrying nodes) and the silent drop of unique non-constraint indexes are gone. For each index, infer recomputes the content hash from the introspected node: if the live name is <prefix>_<that hash>, it emits name: "<prefix>" — databases this toolchain created re-infer to managed contracts byte-identically. Otherwise map: "<live name>" with content verbatim. (A simple expression whose reprint equals the authored text re-detects managed too — equally sound, pinned by test; the spec's over-general side-claim was corrected.) A default-method index carrying storage parameters emits type: "btree" beside options: so it round-trips clean — the PSL surface requires the pair, and the constructor normalization from slice 2 makes the comparison exact.
  • Policies and @@rls. Every pg_policies row emits a policy_* block: head identifier derived from the name and sanitized to the PSL grammar (collisions numeric-suffixed deterministically), @@map always — policy re-detection is not attempted by design — with verbatim reprinted bodies. Tables with row security emit @@rls natively; the Supabase harness's bolt-on appender is deleted.
  • permissive became authorable — a falsified spec assumption resolved: D8 commits to capturing permissive from the row, but RESTRICTIVE had no authoring surface. The five policy_* blocks gain an optional permissive property (default true; the hash tuple already carried the slot, so managed hashes are byte-unchanged). En route this exposed the third instance of the false-literal canonicalization class: permissive: false was dropped on emit — fixed target-side (shouldPreserveEmpty override), with a sweep confirming no other target-entity boolean is droppable.
  • Legal twin databases are signable: the duplicate-index guard keys exact-mode entries by name (two content-identical indexes under different names are legal in Postgres); managed entries keep the content key, and same-name collisions were already covered by the named-object pass.
  • Supabase regenerated through its checked-in generator: purely additive — the partial and expression indexes the slice-1 era omitted are adopted, @@rls comes from infer, zero removals or flips. CONTRACT-FIDELITY.md's transitional-omission passage is retired.

The scenario matrix is complete

Every row of the project spec's normative A–J table now has named green tests — A (this PR's sign-the-database journey + slice 1's fields-only round-trip), B/D/E/G/H (slice 2), C/F (slice 3 + this PR's transition), I/J (slice 1, J extended by the twin-guard tests and the live Supabase pair). The full walk is in the review ledger and the slice spec.

Verification

Full standing gate green at every dispatch (build · typecheck · whole Lint surface · fixtures:check · check:upgrade-coverage · all three suites · test:examples). The DoD journeys assert on real --json CLI outputs with byte-asserted plans; upgrade entries extended for both audiences (infer fidelity, permissive, twin legality, the map:name: re-detection consequence). Release notes are deliberately deferred to release-cut per repo convention (draft-release-notes enumerates all merged PRs); the deviation is recorded in plan.md.

Base

Stacked: slice/rls-exact-names (#1050) → #1048#1047#1046. Merge in stack order, retargeting as bases land. After this merges, one final close-out PR promotes the ADR, strips projects/functional-indexes/** references, and deletes the project folder.

Skill update

Both 0.16-to-0.17 skill clusters extended: user entry covers infer's full-fidelity capture and consequences; extension entry contract-infer-emits-full-fidelity covers regen expectations and appender deletion.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The Skill update section is filled in.

🤖 Generated with Claude Code

@wmadden-electric
wmadden-electric requested a review from a team as a code owner July 24, 2026 07:52
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 300c90be-13e3-4036-8e47-790933550857

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slice/infer-round-trip

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 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 72ef213

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 169.13 KB (+0.23% 🔺)
postgres / emit 150.11 KB (+0.21% 🔺)
mongo / no-emit 100.72 KB (0%)
mongo / emit 90.4 KB (0%)
cf-worker / no-emit 194.61 KB (+0.22% 🔺)
cf-worker / emit 173.66 KB (+0.24% 🔺)

…plicate guard

The duplicate-index rejection keyed every entry by content signature,
which rejects a legally-twinned database: two content-identical indexes
under different physical names, which a signed (adopted) contract may
carry. Exact-mode entries (no prefix) now skip the content key — their
identity is the name, and name collisions are already rejected by the
named-object check. Managed entries keep the content key: two managed
twins would share the content hash and collide on the wire name anyway,
and the content key additionally rejects same-content different-prefix
pairs, which could never both converge.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…managed re-detection

The slice-1 skip guard (expression and partial indexes) and the unique
gate (unique non-constraint indexes silently dropped) are deleted —
every non-constraint index emits. A new buildIndexAttribute re-detects
identity: when the live name parses as a wire name AND its hash
recomputes from the introspected content, the index is managed and
emits name: with the prefix; otherwise it adopts exactly with map: and
the content verbatim (expression:, where:, unique: true, type:,
options: as introspected — options only alongside type, the pair the
PSL surface requires). The btree edge is documented: an index authored
type: 'btree' hashed the method into its suffix but introspects
type-normalized, so it re-infers as map: — a clean round trip, just
exact rather than managed. Constraint-backed uniques keep flowing to
@@unique through the untouched id/unique builder. Fidelity journey
seeds gain expression, partial, and unique indexes with one test per
new emission, and the full infer -> emit -> verify round trip stays
zero-issue.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… an explicit btree type

The dispatch-1 narrowing left scenario A holed: a btree index with
reloptions (fillfactor) had no emission, so it signed with perpetual
options drift. The builder now emits an explicit type: 'btree' beside
options: when the introspected node carries options with the type
normalized away — the pair the PSL surface requires — and the expected
node's constructor normalizes btree back to undefined, so verify
compares clean. Round-trip pinned at unit level and in the fidelity
journey (fillfactor index infers, emits, and verifies zero-issue).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The five policy_* block descriptors gain an optional permissive boolean
parameter (pg/bool codec, default true), threaded through
lowerRlsPolicyFromBlock and buildRlsPolicyEntity into the hash-tuple
slot and entity field that always existed — a managed permissive: true
policy's wire name is byte-unchanged (pinned), a RESTRICTIVE twin gets
a different hash, and an @@Map policy carries permissive: false
verbatim. This is D8's minimal enabler: a RESTRICTIVE live policy can
now infer and round-trip. The TS entity-handle path stays defaulted
permissive.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Every introspected policy emits a policy_<operation> block in its
namespace: the head is the parsed wire prefix (else the physical name)
sanitized to the PSL identifier grammar, within-namespace collisions
numeric-suffixed deterministically by sorted physical name; @@Map ALWAYS
carries the physical name (a body reprint never reliably re-hashes, so
every adopted policy is exact); bodies and roles emit verbatim, and a
RESTRICTIVE row emits permissive = false. A policy referencing a role
whose name fails the identifier grammar skips with a comment note on
its target model — the honest leftover a strict verify then names.
@@rls emits from the table node's rlsEnabled, replacing the Supabase
harness's out-of-band appender shape exactly. Policy-bearing output
namespace-wraps like enum-bearing output.

Emitting RESTRICTIVE policies exposed a canonicalization hole: the
default-omission walk dropped permissive: false, so the emitted
contract failed its own validation on the next read.
PostgresContractSerializer now preserves the policy permissive path,
the same class of preservation the SQL family declares for an index's
unique: false. Fidelity and infer-workflow journeys cover permissive +
RESTRICTIVE policies, multi-role, withCheck, @@rls, and re-infer
stability; two pre-existing assertions were namespace-wrap-adjusted
(indentation-tolerant regex, field-precise name match).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…and full index fidelity

applyRlsEnablement is deleted — @@rls now comes from infer natively,
line-for-line identical. The regen (checked-in generator only) ends the
slice-1 transitional omission: the reference's partial unique indexes,
partial non-unique indexes, expression indexes, and unique
non-constraint indexes all adopt as exact-named map: entries. The
movement is purely additive — zero removals, zero map:-to-name: flips
(no real Supabase index name is wire-shaped), and no policy blocks (the
reference fixture seeds none). CONTRACT-FIDELITY's partial-index
omission passage is replaced by the full-fidelity statement, and the
oauth_consents.client_id FK note now reflects its declared partial
backing index.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…es shipped behavior

A byte-identical reprint re-hashes to the authored suffix, so a simple
expression index re-detects managed — equally sound, proven by test.
Policies remain always-exact by design, not by hash impossibility.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…(project DoD-2/3)

A database created by another tool — an expression index, a partial
index, a unique expression index, and PERMISSIVE + RESTRICTIVE policies
on an RLS-enabled table — signs through contract infer -> emit ->
db verify with zero issues, and db update --dry-run plans zero
operations (both asserted on the --json outputs). From that signed
contract, one index and one policy transition from map: to the managed
spelling with bodies verbatim; the widening plan is exactly two ops,
an ALTER INDEX RENAME and an ALTER POLICY RENAME, byte-asserted against
the recomputed wire names; applying it verifies clean.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The contract and quickstart skills' brownfield sections state what infer
now captures (the full index matrix with managed re-detection, @@rls,
policy blocks with @@Map and verbatim bodies, the RESTRICTIVE spelling,
the skip-with-note case). Upgrade entries, both audiences: the user
skill's newly-available note covers infer fidelity, the map:-to-name:
re-detection on wire-shaped names, authorable permissive, and the legal
exact twins; the extension skill gains contract-infer-emits-full-
fidelity for packs whose contract-space generators run infer (additive
regen movement, delete out-of-band @@rls appenders). plan.md marks the
two slice-4 carry-overs landed with their commits and records the
release-notes deviation (authored at release-cut per repo convention).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…t an exact twin

S4-2 review observation: the reference pair differs in unique, so the
exact-twin guard rests on its unit tests, not a live fixture.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…rable, name-identified, adoptable

Adds the functional-indexes entry to requirement 4 as Landed:
expression/partial/unique index authoring in PSL and TypeScript (the
Cipherstash EQL shape), name-identified indexes and RLS policies with
content-hashed wire names, and full-fidelity contract infer so live
databases adopt cleanly and convert to managed naming by renames.
Aggregates updated (35 tasks, 7 done; requirement-4 minibar 2 done) and
the header legend corrected to match the caption.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…behavior

The journey header, describe title, and step comments describe what each
step does — sign a foreign database as-is, then transition one index and
one policy to managed naming via exactly two renames — instead of
pointing at planning checklist coordinates. Behavior and assertions
unchanged.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… is derived

SqlObjectNaming — { kind: "exact", name } | { kind: "managed", prefix,
hash } — is the naming input for all four name-identified classes
(contract Index, SqlIndexIR, PostgresRlsPolicy,
PostgresPolicySchemaNode). The flat fields stay exactly as stored:
`name` is DERIVED (formatWireName(prefix, hash) for managed, verbatim
for exact) and the optional `prefix` property is retained, so canonical
JSON, storage hashes, emitters, validators, and every fixture are
byte-identical. The hash is input-only — never a stored field.

A mismatched name/prefix pair is now unconstructable from the union, so
the constructor parse-back checks delete; flat data still arrives at
load boundaries (contract JSON via indexInputFromSerialized and the
policy entity-kind descriptor, the migration API literal via
rlsPolicyInputFromFlat, entity/node rebuilds via namingFromFlat), and
the pair is validated exactly there. parseWireName returns the managed
arm directly, so introspection reads
`parseWireName(name) ?? { kind: "exact", name }`.

Producers migrate to the union: lowerAuthoredIndex,
buildRlsPolicyEntity, the @@Map exact policy, both introspection sites,
the family and Postgres tree derivations, policyNodeToContractPolicy,
and the test corpus (constructor-invariant tests retarget at the flat
load boundaries, where the checks now live).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
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