TML-2784: M:N slice 0 — contract + resolver foundation#678
TML-2784: M:N slice 0 — contract + resolver foundation#678tensordreams wants to merge 16 commits into
Conversation
|
Warning Review limit reached
More reviews will be available in 16 minutes and 51 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (4)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds ContractRelationThrough for junction-table metadata, emits through in generated types when present, updates validation and JSON schema to accept through for N:M, updates contract builder to populate through.namespaceId/targetColumns, implements resolveThrough with requiredPayloadColumns, and normalizes cardinality literals to 'N:M'. ChangesMany-to-Many Relations with Junction Table Metadata (N:M)
Sequence DiagramsequenceDiagram
participant resolveModelRelations
participant resolveThrough
participant contractStorage
resolveModelRelations->>resolveThrough: pass raw through object
resolveThrough->>contractStorage: lookup junction table by (namespaceId, table)
contractStorage-->>resolveThrough: return junction table columns & metadata
resolveThrough->>resolveThrough: compute FK column set (parentColumns + childColumns)
resolveThrough->>resolveThrough: filter columns → requiredPayloadColumns (non-FK, NOT NULL, no-default)
resolveThrough-->>resolveModelRelations: return ResolvedThrough
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
90cf526 to
669c230
Compare
e44f8aa to
aaefe75
Compare
433e9bb to
c350460
Compare
aaefe75 to
87de795
Compare
87de795 to
4763690
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts (1)
385-395: ⚡ Quick winAdd a regression case for non-
N:Mrelations that still carrythrough.Current coverage only checks omission when
throughis absent. Add a case wherecardinalityisN:1(or1:N) with athroughobject and assertreadonly through:is not emitted.🧪 Suggested test addition
+ it('omits through when relation is not N:M even if through is present', () => { + const result = generateModelRelationsType({ + author: { + to: crossRef('User'), + cardinality: 'N:1', + through: { + table: 'user_author', + parentColumns: ['authorId'], + childColumns: ['userId'], + targetColumns: ['id'], + }, + }, + }); + expect(result).not.toContain('readonly through:'); + });🤖 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/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts` around lines 385 - 395, Add a regression test that verifies generateModelRelationsType does not emit a "readonly through:" property for non-N:M relations even when a through object is present: update the test suite around domain-type-generation.test.ts by adding a case that calls generateModelRelationsType with a relation (e.g., author) whose cardinality is 'N:1' (or '1:N') but includes a through object, then assert the returned string does not contain 'readonly through:' and still contains the expected 'readonly localFields:' token; reference generateModelRelationsType and the existing test pattern to mirror assertions and test structure.
🤖 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.
Inline comments:
In `@packages/3-extensions/sql-orm-client/src/collection-contract.ts`:
- Around line 257-295: The resolveThrough function currently proceeds even when
resolveTableForContract(contract, table) returns undefined, making
misspelled/stale junction tables appear valid; change resolveThrough so that if
resolvedJunction is undefined it returns undefined (i.e., reject the relation)
instead of continuing to build and returning a through object with
requiredPayloadColumns: []; locate resolveThrough and the call to
resolveTableForContract(contract, table) and add an early return undefined when
resolvedJunction is falsy before computing requiredPayloadColumns or returning
the through shape.
---
Nitpick comments:
In `@packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts`:
- Around line 385-395: Add a regression test that verifies
generateModelRelationsType does not emit a "readonly through:" property for
non-N:M relations even when a through object is present: update the test suite
around domain-type-generation.test.ts by adding a case that calls
generateModelRelationsType with a relation (e.g., author) whose cardinality is
'N:1' (or '1:N') but includes a through object, then assert the returned string
does not contain 'readonly through:' and still contains the expected 'readonly
localFields:' token; reference generateModelRelationsType and the existing test
pattern to mirror assertions and test structure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: c505dd74-597c-4364-9cc0-988eeb557c98
⛔ Files ignored due to path filters (8)
projects/sql-orm-many-to-many/learnings.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/00-contract-resolver-foundation/dispatches/01-contract-shape.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/00-contract-resolver-foundation/dispatches/02-cardinality-canonicalise.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/00-contract-resolver-foundation/dispatches/03-resolver-through.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/00-contract-resolver-foundation/dispatches/03-resolver-through.r2.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/00-contract-resolver-foundation/plan.mdis excluded by!projects/**projects/sql-orm-many-to-many/slices/00-contract-resolver-foundation/spec.mdis excluded by!projects/**projects/sql-orm-many-to-many/trace.jsonlis excluded by!projects/**
📒 Files selected for processing (16)
packages/1-framework/0-foundation/contract/src/domain-types.tspackages/1-framework/0-foundation/contract/src/exports/types.tspackages/1-framework/3-tooling/emitter/src/domain-type-generation.tspackages/1-framework/3-tooling/emitter/test/domain-type-generation.test.tspackages/2-mongo-family/5-query-builders/orm/src/collection-state.tspackages/2-sql/1-core/contract/src/validators.tspackages/2-sql/2-authoring/contract-ts/schemas/data-contract-sql-v1.jsonpackages/2-sql/2-authoring/contract-ts/src/build-contract.tspackages/2-sql/2-authoring/contract-ts/test/contract-builder.dsl.test.tspackages/2-sql/2-authoring/contract-ts/test/contract.edge-cases.test.tspackages/3-extensions/sql-orm-client/src/collection-contract.tspackages/3-extensions/sql-orm-client/src/collection-internal-types.tspackages/3-extensions/sql-orm-client/src/mutation-executor.tspackages/3-extensions/sql-orm-client/src/types.tspackages/3-extensions/sql-orm-client/test/collection-contract.test.tspackages/3-extensions/sql-orm-client/test/mutation-executor.test.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/3-extensions/sql-orm-client/src/collection-contract.ts (1)
283-300:⚠️ Potential issue | 🟠 MajorMissing junction table still returns a valid-looking through descriptor.
When
contract.storage.namespaces[namespaceId]?.tables[table]returnsundefined, the function proceeds and returns aResolvedThroughobject withrequiredPayloadColumns: []. This masks invalid junction table names (typos, wrong namespace, or stale metadata) and defers the error to downstream SQL generation rather than failing fast at resolution time.This mirrors the concern raised in the previous review comment on lines 255-300. Consider returning
undefinedwhenjunctionTableis not found so that invalid through metadata is detected early:💡 Suggested early-return guard
const junctionTable = contract.storage.namespaces[namespaceId]?.tables[table]; +if (!junctionTable) { + return undefined; +} + const requiredPayloadColumns: string[] = []; -if (junctionTable) { - for (const [colName, col] of Object.entries(junctionTable.columns)) { - if (!fkColumnSet.has(colName) && !col.nullable && col.default === undefined) { - requiredPayloadColumns.push(colName); - } +for (const [colName, col] of Object.entries(junctionTable.columns)) { + if (!fkColumnSet.has(colName) && !col.nullable && col.default === undefined) { + requiredPayloadColumns.push(colName); } }🤖 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/3-extensions/sql-orm-client/src/collection-contract.ts` around lines 283 - 300, The current resolution builds a ResolvedThrough even when junctionTable is undefined (lookup via contract.storage.namespaces[namespaceId]?.tables[table]), which hides invalid through metadata; change the logic in the function that computes junctionTable so that if contract.storage.namespaces[namespaceId]?.tables[table] is undefined you return undefined immediately (instead of continuing and returning a ResolvedThrough with requiredPayloadColumns: []), ensuring consumers see a missing-through early; update any callers/type annotations if necessary to accept undefined from the ResolvedThrough resolver.
🤖 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.
Duplicate comments:
In `@packages/3-extensions/sql-orm-client/src/collection-contract.ts`:
- Around line 283-300: The current resolution builds a ResolvedThrough even when
junctionTable is undefined (lookup via
contract.storage.namespaces[namespaceId]?.tables[table]), which hides invalid
through metadata; change the logic in the function that computes junctionTable
so that if contract.storage.namespaces[namespaceId]?.tables[table] is undefined
you return undefined immediately (instead of continuing and returning a
ResolvedThrough with requiredPayloadColumns: []), ensuring consumers see a
missing-through early; update any callers/type annotations if necessary to
accept undefined from the ResolvedThrough resolver.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 77e8d72f-6bef-4549-b0de-8f193236eac6
📒 Files selected for processing (8)
packages/1-framework/0-foundation/contract/src/domain-types.tspackages/1-framework/3-tooling/emitter/src/domain-type-generation.tspackages/1-framework/3-tooling/emitter/test/domain-type-generation.test.tspackages/2-sql/1-core/contract/src/validators.tspackages/2-sql/2-authoring/contract-ts/schemas/data-contract-sql-v1.jsonpackages/2-sql/2-authoring/contract-ts/src/build-contract.tspackages/3-extensions/sql-orm-client/src/collection-contract.tspackages/3-extensions/sql-orm-client/test/collection-contract.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts
- packages/3-extensions/sql-orm-client/test/collection-contract.test.ts
- packages/1-framework/0-foundation/contract/src/domain-types.ts
- packages/2-sql/2-authoring/contract-ts/src/build-contract.ts
Contract + resolver foundation: make M:N a validatable contract shape (through + N:M), surface ResolvedRelation.through, canonicalise on N:M. 3 dispatches (contract substrate / N:M rename / resolver judgment). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…tract shape
Extend ContractReferenceRelation to accept cardinality 'N:M' and an
optional through: { table, parentColumns, childColumns, targetColumns }.
Three places updated in lockstep:
- domain-types.ts: add 'N:M' to cardinality union, add ContractRelationThrough
type and optional through field
- validators.ts: add 'N:M' to ContractReferenceRelationSchema enum, add
ContractRelationThroughSchema with its own '+': 'reject' guard
- data-contract-sql-v1.json: add through object to ModelRelation def
- build-contract.ts: remove the 'as ContractRelation['cardinality']' cast
(now unnecessary), rename emitted parentCols/childCols to parentColumns/
childColumns, populate targetColumns from the target model's PK
- mongo-orm collection-state.ts: align local cardinality type from 'M:N' to
'N:M' (trivial consumer fix required by the type widening)
New round-trip test: author User-UserTag-Tag M:N via rel.manyToMany,
emit, validateSqlContractFully passes, and through descriptor is asserted.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…'N:M' The SQL contract already emits 'N:M' (landed in D1). sql-orm-client was the lone holdout still spelling it 'M:N', meaning a real emitted M:N relation parsed to undefined and the mutation guard silently never fired. Flip all four literal sites in src/: - RelationCardinalityTag union (types.ts) - partitionByOwnership guard + error message (mutation-executor.ts) - IsToManyRelation type-level check (collection-internal-types.ts) - parseRelationCardinality acceptance check (collection-contract.ts) Update tests to match: move the rejection test's cardinality literal to 'N:M' (stays a rejection test), update the guard error message regex, and update the isToOneCardinality argument in collection-contract.test.ts (required for typecheck: 'M:N' is no longer assignable to RelationCardinalityTag). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…for M:N relations Add ResolvedThrough interface and populate through on ResolvedRelation when the contract relation carries a through object (cardinality N:M). The resolver reads table, parentColumns, childColumns, and targetColumns directly from the contract, then derives requiredPayloadColumns by inspecting the junction table's storage columns: any column that is NOT NULL, has no default, and is not in parentColumns ∪ childColumns is required payload that the caller must supply when creating a junction row. Four resolver unit test cases: simple single-column FK, composite-key junction, junction with required non-FK payload columns, and junction with only nullable/defaulted extra columns. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The 5 bare `as` casts in `resolveThrough` (parentColumns/childColumns in the Set spread, and all three *Columns in the return object) were declarative widenings from `unknown[]` to `readonly string[]`. Replace all five with `castAs<readonly string[]>(…)` from `@prisma-next/utils/casts` per the no-bare-casts rule. No behaviour change. lint:casts delta: -1. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… (TML-2784) Orchestrator artifacts for the slice-0 build loop (3 dispatches, D3 in 2 rounds). Review log under reviews/ is gitignored by repo convention. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Teach generateModelRelationsType to serialize the junction-table descriptor (through.table / parentColumns / childColumns / targetColumns) as a typed readonly literal when present in the relation object. Non-M:N relations (no through key) are byte-for-byte unchanged. Adds three unit tests: M:N with single-column keys, M:N with a composite parent key, and a regression guard that confirms through is absent on a plain N:1 relation that carries only an on block. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…rebase Main's namespace work (TML-2605) removed the unboundTable helper the resolver relied on and made runtime SQL namespace-qualified. Resolve the junction via resolveTableForContract so requiredPayloadColumns reads the correct table and the resolved namespaceId is carried on the descriptor. Consolidate ResolvedThrough onto ContractRelationThrough (export the latter from @prisma-next/contract) and surface the junction namespaceId, addressing Will's review on #678 (collection-contract.ts:201 'Needs namespace' + 'Duplicate of ContractRelationThrough?'). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…criptor Nothing constrains the source, target, and junction tables of an N:M relation to one namespace, so resolving the junction by bare table name is ambiguous under cross-namespace collisions (TML-2550). Mirror the FK-reference shape: the emitted through block now carries the junction's namespaceId (JSON schema + arktype validator + ContractRelationThrough + build-contract emission + contract.d.ts literal), and the orm-client resolver reads it from the contract — looking the junction up in its declared namespace — instead of re-deriving by name scan. Follows up wmadden's review on #678 (domain-types.ts 'Needs namespace'). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
d15ad0b to
5450121
Compare
…s N:M Split ContractReferenceRelation into a discriminated union: an N:M variant that requires through, and a non-junction variant that forbids it (through?: never). Mirror the invariant in the arktype validator with a discriminated schema and drop the all-optional through cast plus the per-field presence guard in the emitter, which branches on through presence only now that a present through always carries all five fields. Addresses PR #678 review threads A01-A04. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… is missing Replace the silent fallbacks in M:N through lowering: throw when the junction table is not a declared model (instead of defaulting its namespace), and derive junction targetColumns from the target's primary id, else its first unique constraint, else throw (instead of an empty array). Addresses PR #678 review threads A05, A06. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ct shape Accept ContractRelationThrough | undefined (the validateContract-validated shape) instead of an opaque all-unknown bag, dropping the redundant typeof/Array.isArray re-validation and the castAs<readonly string[]> casts it forced. resolveThrough now returns undefined when the junction table is absent from its declared namespace, so a stale/misspelled junction fails fast instead of masquerading as a valid descriptor. Addresses PR #678 review threads A07, A08, A09. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…tract Replace the hand-rolled buildManyToManyContract() blob (returned via 'as unknown as Contract<SqlStorage>') with a fixture patched onto the generated base contract and run through the Postgres serializer's deserializeContract, so the through-descriptor suite exercises a genuinely validated contract. Add a regression test asserting resolveThrough omits through when the junction storage table is absent. Addresses PR #678 review threads A07, A11, A12. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
|
Re: the nitpick to add a regression test asserting |
… cast generateModelRelationsType blind-cast relObj['through'] with the invariant 'through is present iff cardinality is N:M' asserted in a justification string. Gate the through emission on cardinality === 'N:M' and read through off a single castAs<ContractManyToManyRelation> at the JSON union boundary, so the discriminated union proves the shape. Emitter output is byte-identical for valid contracts. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…:N fixture Add genuine M:N surface to the shared sql-orm-client test fixture so the through-descriptor tests run against an emitted, validated contract instead of a hand-patched one: - User.tags -> Tag through UserTag (user_tags), single-column FK pair; user_tags also carries a nullable note and a now()-defaulted created_at to cover the requiredPayloadColumns exclusion path. - User.roles -> Role through UserRole (user_roles) with a NOT NULL no-default level column, covering requiredPayloadColumns = ['level']. - Project.related -> Project through ProjectLink (project_links) over composite (tenant_id,id) keys, covering composite parent/child/target column arrays. Rewrite the through-descriptor tests to read these relations from the emitted fixture via getTestContract(), and delete buildManyToManyContract. The missing-junction case stays as a targeted withPatchedDomainModels mutation (through.table -> bogus name) on the emitted contract. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The integration schema creates tags(id, name); a contract-declared label column the table doesn't have breaks implicit selection in the stacked include tests. No slice-0 test reads it. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Slice 0 of the SQL ORM: Many-to-Many End to End project (Linear project). The foundation that gates the parallel read/filter/write slices.
Overview
Many-to-many relations are authored + lowered correctly, but the runtime stack could not even validate an emitted M:N contract: the relation validator rejected
cardinality: N:M(not in its enum) and thethroughkey (undeclared on a reject-policy object). This slice makes the M:N relation shape first-class through the contract and surfaces a uniformthroughdescriptor on the shared ORM resolver — without yet teaching any consumer to walk it (that is slices 1–3).Changes
f962fd47d):N:Madded to the relation cardinality enum and an optionalthrough { table, parentColumns[], childColumns[], targetColumns[] }across the arktype validator, the JSON schema, and theContractReferenceRelationtype. Deleted theas ContractRelation[cardinality]cast (the "until the contract type is extended for many-to-many" seam), reconciled the emittedparentCols/childCols→parentColumns/childColumnsdrift, and populatedtargetColumns. Round-trip test added (authorsrel.manyToMany→ emits →validateContractpasses — failed before).f255714e7): flipped the fourM:Nsites insql-orm-clienttoN:M(the contract/PSL/lowering already usedN:M; the ORM client was the lone holdout, so a real emitted M:N parsed toundefined). The M:N-rejection unit test moved toN:Mso it exercises the live guard branch (still a rejection — slice 3 flips it positive).through(3a87c7c55,5ff87bc21):ResolvedRelation.throughon the single sharedresolveModelRelationsresolver, includingrequiredPayloadColumns(junction columns that are NOT NULL, no default, and not FK columns — slice 3 uses this to disable nested.createon required-payload junctions). 4-case resolver test (simple / composite-key / required-payload / all-nullable-or-defaulted).Why
The relation-shaped ORM API over M:N is the most-asked-about migration gap from Prisma. This slice is the prerequisite the other three build on: there is exactly one shared resolver (
resolveModelRelations→ResolvedRelation) feeding includes, filters, and mutations, so surfacingthroughonce is the uniform primitive each consumer will branch on. Discovered while shaping: the change is purely additive — existing non-M:N contracts emit byte-identically (zero golden drift), so no consumer breaks and no deprecation window is needed.Scope
Foundation only. This PR does not teach the include / filter / write paths to walk
through— those are slices 1 (TML-2785), 2 (TML-2786), 3 (TML-2787). No user-observable behaviour change yet, so no manual-QA script (the runtime M:N surfaces arrive in the consumer slices).Verification
validateContractround-trips an M:N contract (was failing);ResolvedRelation.throughpopulated incl.requiredPayloadColumns; noM:Nliteral remains insql-orm-client/src.pnpm lint:depsclean;pnpm lint:castsdelta=-1; golden fixtures diff empty (additive).fixtures:checkruns in CI (the local emit step has a pre-existing CLI-on-PATH env limitation in this sandbox; additivity confirmed via direct golden diff).Refs: TML-2784.
Summary by CodeRabbit
New Features
Updates
Tests