Skip to content

TML-2330: add per-query codec call context + AbortSignal#400

Merged
wmadden merged 51 commits intomainfrom
tml-2330-codec-dispatch-concurrency-control-rate-limit-abortsignal
May 1, 2026
Merged

TML-2330: add per-query codec call context + AbortSignal#400
wmadden merged 51 commits intomainfrom
tml-2330-codec-dispatch-concurrency-control-rate-limit-abortsignal

Conversation

@wmadden
Copy link
Copy Markdown
Contributor

@wmadden wmadden commented Apr 30, 2026

closes TML-2330

m1 of a multi-milestone project. SQL runtime threading lands in m2, Mongo in m3, ADR + framework-gaps cross-references in m4. This PR establishes the public surface only — codec author signatures, factory acceptance, runtime entry-point shape, and the RUNTIME.ABORTED envelope helper. No family runtime is signal-aware yet.

Intent

Introduce CodecCallContext — a per-query, per-call context object the runtime threads to every codec.encode / codec.decode call — and admit an optional { signal } option on the framework RuntimeCore.execute entry point. This is the foundation for KMS-backed codecs (e.g. CipherStash) to cancel in-flight network calls when the caller aborts; m2/m3 will thread the signal through the SQL and Mongo dispatch sites end-to-end.

The shape is named (not inlined) so future fields land additively without breaking codec author signatures.

Change map

  • Framework CodecCallContext in packages/1-framework/1-core/framework-components/src/codec-types.ts{ readonly signal?: AbortSignal }, family-agnostic. The framework Codec.encode / Codec.decode admit it as an optional second argument.
  • SQL SqlCodecCallContext extends CodecCallContext in packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts — adds readonly column?: SqlColumnRef ({ table, name }). The SQL Codec interface narrows encode/decode to use it.
  • Mongo — uses the framework CodecCallContext directly; no Mongo extension introduced (the Mongo read path doesn't go through codec.decode per ADR 204, and the encode site has no per-call concept that needs to extend the framework type today).
  • codec() and mongoCodec() factories — accept author functions of either arity ((value) or (value, ctx)).
  • RUNTIME.ABORTED envelope helper in packages/1-framework/1-core/framework-components/src/runtime-error.tsRUNTIME_ABORTED constant, RuntimeAbortedPhase = 'encode' | 'decode' | 'stream', and runtimeAborted(phase, cause?) envelope builder. Falls back to a synthesized DOMException('AbortError') when no reason is supplied.
  • RuntimeCore.execute(plan, options?) in packages/1-framework/1-core/framework-components/src/runtime-core.ts — accepts { signal? }, builds a single per-execute CodecCallContext, forwards it to the abstract lower(plan, ctx?), and pre-checks signal.aborted at first next() to throw RUNTIME.ABORTED { phase: 'stream' } for already-cancelled callers.

The story

The natural shape is a single CodecCallContext object the runtime constructs once per execute() and threads through every codec dispatch site. Two motivations converge on this seam: per-query cancellation (so codecs that perform network I/O can forward AbortSignal to their underlying SDK), and decode-side column identity (so codecs can construct return values that carry (table, name) for downstream bulk operations like envelope-pattern decryption).

The framework-vs-family layering ruled where each piece lives. signal is per-query cancellation, genuinely framework-shaped — every family wants it. column is rooted in SQL's (table, column) addressing model and would not generalise to Mongo (paths/keys), document stores, or graph stores; it lives on a SQL-family extension. TypeScript's bivariant handling of method-syntax declarations admits the SQL parameter-narrowing under strict mode without any cast — pinned by type tests so a future change to arrow-property syntax fails loudly.

The factory work is a separate concern: a naive single-arg-to-two-arg widening at the user-facing config type widens TInput for codecs whose author signatures are non-trivial (e.g. sqlTimestampCodec's (value: string | Date): string), which then breaks the conditional JsonRoundTripConfig. The fix is a union of two arities at the user surface, widened to a uniform internal shape so the lift forwards ctx through either author shape unchanged.

The RUNTIME.ABORTED helper lands in m1 (rather than m2 as originally planned) because RuntimeCore.execute's entry pre-check needs a construction site for the throw, making the helper a m1 prerequisite rather than a m2 deliverable.

Behavior changes & evidence

A — Codec.encode / Codec.decode admit a second optional CodecCallContext argument

B — codec() and mongoCodec() factories accept either author arity and lift uniformly

C — RUNTIME.ABORTED envelope helper

  • After: RUNTIME_ABORTED = 'RUNTIME.ABORTED' as const, RuntimeAbortedPhase = 'encode' | 'decode' | 'stream', runtimeAborted(phase, cause?) builds an envelope with details: { phase } and a cause that falls back to new DOMException('The operation was aborted.', 'AbortError') when no reason is supplied.
  • Implementation: framework-components/src/runtime-error.ts (L8–L83).
  • Tests: framework-components/test/runtime-aborted.test.ts (code constant, envelope shape, phase recording, supplied cause, DOMException fallback, string-reason pass-through).

D — RuntimeExecutor<TPlan> + RuntimeCore.execute accept { signal } and pre-check abort

Compatibility / migration / risk

  • Strictly additive at every author surface. Existing in-tree codecs (sqlChar, sqlVarchar, sqlInt, sqlFloat, sqlText, sqlTimestamp, etc.) compile unchanged. Existing runtime.execute(plan) call sites compile unchanged.
  • No walk-back regressions. Codec retains exactly four type parameters; no TRuntime generic, no kind / runtime discriminator, no conditional return types, no exported sync-vs-async predicates, no Codec.context alias. All pinned by negative-form type tests per ADR 204's walk-back framing.
  • Method-syntax narrowing is the load-bearing soundness assumption for SQL ctx narrowing. The framework and SQL Codec interfaces declare encode/decode with method syntax, so TypeScript's bivariant method handling admits the SQL parameter-narrowing under strict mode. Pinned by type-tests; converting the declarations to arrow-property syntax in a future round would unwind the layering correction.
  • Layering invariant restored. pnpm lint:deps reports zero violations across 661 modules; framework-components/src/codec-types.ts imports only @prisma-next/contract/types; no SQL-package imports anywhere in framework-components/.

Follow-ups

  • m2 — SQL runtime threads SqlCodecCallContext through encodeParams / decodeRow / executeAgainstQueryable; populates ctx.column = { table, name } on decode; races Promise.all against the signal; throws RUNTIME.ABORTED { phase: 'encode' | 'decode' | 'stream' } at the right boundaries. Integration test against an aborting fetch.
  • m3 — Mongo runtime threads CodecCallContext through resolveValue recursively. (Mongo decode is out of scope per ADR 204; encode-only signal coverage.)
  • m4 — ADR (e.g. ADR 0NN — Codec call context: per-query AbortSignal + column metadata), ADR 204 §"Risks & mitigations" cross-reference update, framework-gaps G10 → resolved + G1 → partially resolved.

Non-goals (intentionally out of scope)

  • Concurrency cap on Promise.all codec dispatch (framework-gaps G4-option-2). Today's Promise.all shape is preserved unchanged; deferred to a separate ticket.
  • Bulk-codec interface (bulkEncode / bulkDecode). Out of scope. Bulk-encrypt at write time is solved at the middleware layer (separate ticket); bulk-decrypt at read time is solved by extension-owned envelope objects + helpers operating on ctx.column-bearing return values.
  • Driver-level cancellation (forwarding signal to pg / MongoDB driver so an in-flight query is killed at the connection level). Worth doing later; the codec story is independently useful.
  • Transaction-scoped signal composition. Per-execute signal covers the headline use case; AbortSignal.any(...) composition with transaction-scoped signals is a future enhancement.

Summary by CodeRabbit

  • New Features

    • Per-execution cooperative cancellation: pass an AbortSignal to runtime.execute(...) to abort streaming, encoding, or decoding with phase-specific RUNTIME.ABORTED errors.
    • Codec call context: runtimes thread a per-execute context (signal; SQL decode-only column metadata) into codec/adapter calls so codecs can observe cancellation.
  • Documentation

    • New reference and ADRs plus README updates describing context, abort semantics, and examples.
  • Tests

    • Broad unit/integration suites validating propagation and abort behavior across SQL and Mongo paths.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a per-execute CodecCallContext (with optional AbortSignal), threads it through codec encode/decode and lowering paths, extends runtime.execute to accept RuntimeExecuteOptions (signal), implements abort/error envelopes (RUNTIME.ABORTED) and raceAgainstAbort, and adds tests, ADRs, and docs.

Changes

Cohort / File(s) Summary
Reference docs
docs/reference/framework-gaps.md
New reference catalog enumerating framework gaps (G1–G16), workarounds, desired API changes, ownership, and prioritized next-phase plan.
Framework: codec surface
packages/1-framework/1-core/framework-components/src/codec-types.ts, packages/1-framework/1-core/framework-components/src/exports/codec.ts, packages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.ts, packages/1-framework/1-core/framework-components/README.md
Adds CodecCallContext { signal?: AbortSignal }; updates Codec.encode/decode signatures to accept optional ctx; re-exports type; adds type tests and README docs.
Framework: runtime surface & errors
packages/1-framework/1-core/framework-components/src/runtime-middleware.ts, .../runtime-core.ts, .../runtime-error.ts, .../exports/runtime.ts, .../test/runtime-core-options*.test*, .../test/runtime-aborted.test.ts
Adds RuntimeExecuteOptions { signal?: AbortSignal }, updates RuntimeExecutor.execute(..., options?), forwards ctx into lower, introduces RUNTIME.ABORTED, RuntimeAbortedPhase, runtimeAborted() and tests.
Framework: abort helper
packages/1-framework/1-core/framework-components/src/race-against-abort.ts, .../test/race-against-abort.test.ts
New raceAgainstAbort(work, signal, phase) utility that races work vs AbortSignal and maps aborts to runtimeAborted(phase, reason); unit tests added.
Mongo family: codecs & lowering
packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts, packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.*, packages/2-mongo-family/6-transport/mongo-lowering/package.json, packages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.ts
Allows author encode/decode to accept optional ctx; adds framework-components dependency in mongo-lowering; extends MongoAdapter.lower(plan, ctx?) typing and docs.
Mongo runtime & adapter threading
packages/2-mongo-family/7-runtime/src/mongo-runtime.ts, packages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.ts, packages/3-mongo-target/2-mongo-adapter/src/{mongo-adapter.ts,lowering.ts,resolve-value.ts}, packages/3-mongo-target/2-mongo-adapter/test/*-ctx.test.ts
Threads CodecCallContext from Mongo runtime into adapter lowering and resolveValue; forwards ctx into codec.encode; adds pre-abort checks and mid-flight abort racing; tests confirm propagation and abort semantics.
SQL family: codec types & runtime
packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts, packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.*, packages/2-sql/5-runtime/src/codecs/{encoding,decoding}.ts, packages/2-sql/5-runtime/src/sql-runtime.ts, packages/2-sql/5-runtime/test/*-ctx.test.ts, packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts
Adds SqlCodecCallContext (extends base with optional column), updates codec factory/author arities, threads ctx into encodeParams/decodeRow and runtime execute(options?), adds pre-checks, raceAgainstAbort use, and tests verifying column population and abort behavior.
Adapter target: resolve/value lowering changes
packages/3-mongo-target/2-mongo-adapter/src/*
Integrates ctx into pipeline/lowering/resolution helpers, forwards ctx into codec encodes, and adds abort handling when ctx.signal present.
Integration tests
test/integration/test/sql-builder/execution-abort.test.ts, test/integration/test/mongo/execution-abort.test.ts
End-to-end tests asserting pre-abort and mid-stream abort behavior, regression for omission of options, and preservation of abort cause.
ADR & docs
docs/architecture/.../ADR 204 - Single-Path Async Codec Runtime.md, docs/architecture/.../ADR 207 - Codec call context per-query AbortSignal and column metadata.md, various README updates
Adds ADR 207, updates ADR 204 mitigations, documents ctx, abort phases (encode,decode,stream), RUNTIME.ABORTED envelope, race strategy, and developer-facing README examples.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Runtime
    participant Codec
    participant Driver

    Client->>Runtime: execute(plan, { signal })
    alt signal.aborted at entry
        Runtime->>Client: Reject RUNTIME.ABORTED (phase: "stream")
    else
        Runtime->>Runtime: create CodecCallContext(signal)
        Runtime->>Codec: encode(param, ctx)
        Codec-->>Runtime: encoded param
        Runtime->>Driver: execute(query)
        Driver-->>Runtime: yields rows
        loop per row
            Runtime->>Runtime: check signal
            Runtime->>Codec: decode(wireRow, ctx)
            alt signal fires during decode
                Runtime->>Client: Reject RUNTIME.ABORTED (phase: "decode")
            else
                Runtime->>Client: yield row
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I stitched a signal to encode and decode,
Hopped through runtimes where the cursors flow,
When pings arrive I race and shout,
“Abort!” — the stream will stop and go,
Carrots clutched, I trace each ctx with glee.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding per-query codec call context and AbortSignal support. It directly reflects the primary objective of enabling cooperative cancellation at the codec layer.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2330-codec-dispatch-concurrency-control-rate-limit-abortsignal

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 30, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/middleware-telemetry

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/middleware-telemetry@400

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 5dde224

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
docs/reference/framework-gaps.md (1)

445-446: ⚡ Quick win

Avoid volatile “latest stable X.Y.Z” wording in durable docs

The “latest stable 0.4.2” claim will stale quickly. Prefer either a dated snapshot (“as of 2026-04-29”) or a link to npm dist-tags/version history.

As per coding guidelines: “Keep docs current (READMEs, rules, links) as defined in .cursor/rules/doc-maintenance.mdc.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/framework-gaps.md` around lines 445 - 446, Replace the
volatile phrase "latest stable `0.4.2`" in the paragraph (the quoted text
containing “latest stable `0.4.2`”) with a durable alternative: either annotate
it as a dated snapshot (e.g., "version 0.4.2 (as of 2026-04-29)") or replace it
with a link to the npm dist-tags/version history for the `@prisma-next` packages;
also add a short adherence note referencing the doc-maintenance guideline
".cursor/rules/doc-maintenance.mdc" to signal how to keep this claim updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/reference/framework-gaps.md`:
- Around line 417-434: Update the G10 gap text to reflect that cancellation
tokens are now present but not fully propagated: replace the sentence claiming
the codec interface lacks a cancellation token with wording that references
CodecCallContext.signal and the execute(..., { signal }) API and states the
remaining limitation is that the signal is not yet fully threaded through all
runtime dispatch paths (e.g., some codec runtime call-sites or dispatch layers
still ignore or fail to forward the signal). Keep the rest of the rationale and
recommended solution (Expose AbortSignal on EncodeContext/DecodeContext and
forward to SDK calls) but emphasize “not yet fully threaded through all runtime
dispatch paths” as the current gap.
- Around line 613-621: The fenced code block containing the package tree that
starts with "@prisma-next/extension-template" is missing a language tag and
triggers MD040; update that block by adding a language hint (e.g., add "text"
after the opening triple backticks) so the tree layout is treated as plain text,
leaving the contents (the src/ tree, tsup.config.ts and package.json lines)
unchanged.
- Around line 3-7: The two companion links at the top
(`[./system-design-review.md](./system-design-review.md)`,
`[./code-review.md](./code-review.md)` and
`[./walkthrough.md](./walkthrough.md)`) are currently wrapped in backticks and
render as code; remove the surrounding backticks so they become normal Markdown
links (e.g., replace the inline code tokens around those link tokens in
docs/reference/framework-gaps.md) to make them clickable and follow the
doc-maintenance guideline.

In `@packages/1-framework/1-core/framework-components/src/runtime-error.ts`:
- Around line 79-82: The current runtimeAborted function overwrites explicit
falsy causes (null, false, 0, '') by using the nullish coalescing expression
cause ?? new DOMException(...); change the resolution logic so only an undefined
cause is replaced with the synthetic AbortError (e.g., test cause === undefined)
and preserve any other explicit falsy values; update the resolvedCause
assignment in runtimeAborted (which constructs the envelope via runtimeError and
RUNTIME_ABORTED) to use this undefined-only check so real cancellation details
are not lost.

In
`@packages/1-framework/1-core/framework-components/test/runtime-core-options.test.ts`:
- Around line 96-111: The test currently only asserts the thrown value has a
matching cause; update it to also assert the error is the runtime abort error by
checking isRuntimeError(observed) and that observed.code === RUNTIME.ABORTED (in
the same branch where you check observed.cause). Ensure you reference the same
RUNTIME constant used elsewhere in the tests and keep the existing
observed.cause assertion so the test both pins the error class via
isRuntimeError/observed.code and validates the cause.

In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts`:
- Around line 69-74: Update the author-facing function type signatures so the
second `ctx` parameter is optional: change the two-argument overloads for
`encode` and `decode` in `encode?` / `decode` definitions to accept `ctx?:
CodecCallContext` instead of `ctx: CodecCallContext`, and do the same for the
other occurrences referenced around lines 86-113 (the other encode/decode
overload blocks and any related callback type aliases) so callers and
implementations can safely be invoked with `ctx === undefined`.

In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts`:
- Around line 287-296: The current identity fallback for config.encode (assigned
to userEncode) can lie about TWire; change the fallback so identity is only used
when TWire and TInput are actually interchangeable and otherwise require/throw:
update the userEncode definition (and related CtxEncode/CtxDecode typing) to use
a conditional type or overload that yields the identity function only when TWire
extends TInput, and for the non-interchangeable case set the fallback to a
runtime-throwing stub (e.g., a function that throws "encode not implemented for
differing TWire") so callers cannot accidentally get the wrong wire shape; keep
references to CtxEncode, CtxDecode, userEncode, config.encode and ensure
userDecode remains typed as before.

---

Nitpick comments:
In `@docs/reference/framework-gaps.md`:
- Around line 445-446: Replace the volatile phrase "latest stable `0.4.2`" in
the paragraph (the quoted text containing “latest stable `0.4.2`”) with a
durable alternative: either annotate it as a dated snapshot (e.g., "version
0.4.2 (as of 2026-04-29)") or replace it with a link to the npm
dist-tags/version history for the `@prisma-next` packages; also add a short
adherence note referencing the doc-maintenance guideline
".cursor/rules/doc-maintenance.mdc" to signal how to keep this claim updated.
🪄 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: 35e26dff-b324-40ec-b427-6bca07c64bf0

📥 Commits

Reviewing files that changed from the base of the PR and between 975b674 and 1247511.

⛔ Files ignored due to path filters (2)
  • projects/codec-call-context/plan.md is excluded by !projects/**
  • projects/codec-call-context/spec.md is excluded by !projects/**
📒 Files selected for processing (17)
  • docs/reference/framework-gaps.md
  • packages/1-framework/1-core/framework-components/src/codec-types.ts
  • packages/1-framework/1-core/framework-components/src/exports/codec.ts
  • packages/1-framework/1-core/framework-components/src/exports/runtime.ts
  • packages/1-framework/1-core/framework-components/src/runtime-core.ts
  • packages/1-framework/1-core/framework-components/src/runtime-error.ts
  • packages/1-framework/1-core/framework-components/src/runtime-middleware.ts
  • packages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.ts
  • packages/1-framework/1-core/framework-components/test/runtime-aborted.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core-options.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core-options.types.test-d.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.types.test-d.ts

Comment thread docs/reference/framework-gaps.md Outdated
Comment thread docs/reference/framework-gaps.md
Comment thread docs/reference/framework-gaps.md Outdated
Comment thread packages/1-framework/1-core/framework-components/src/runtime-error.ts Outdated
Comment thread packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts Outdated
Comment thread packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts (1)

123-151: ⚡ Quick win

Add manifest to the stub control-plane descriptors.

targetDescriptor and adapterDescriptor are both descriptor fixtures, but neither includes manifest. That makes the test shape drift from the repo’s required control-plane descriptor contract.

As per coding guidelines, "All control plane descriptors must include manifest property of type ExtensionPackManifest".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts` around lines 123 -
151, The test descriptors targetDescriptor and adapterDescriptor are missing the
required manifest property; update both objects to include a manifest:
ExtensionPackManifest (matching the control-plane contract) — e.g., add a
manifest field to targetDescriptor and adapterDescriptor with a minimal valid
ExtensionPackManifest object (name/version and any required fields) so the
fixtures conform to the expected descriptor shape used by the control plane.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/2-sql/5-runtime/src/codecs/race-against-abort.ts`:
- Around line 36-42: The abort listener registration in the abortPromise may
miss already-aborted signals; before calling signal.addEventListener in the
abortPromise factory (and before creating onAbort), check signal.aborted and if
true immediately set sentinel.reason and reject with sentinel, otherwise proceed
to create onAbort and register it; ensure you still use { once: true } and keep
the onAbort/sentinel logic unchanged so AbortSignal.aborted short-circuits
synchronous aborts for the abortPromise used in raceAgainstAbort.

In `@packages/2-sql/5-runtime/src/sql-runtime.ts`:
- Around line 309-315: The current for-await loop lets the runtime call
stream.next() before the between-row abort check, so change the loop to manually
drive the async iterator from stream (use const iterator =
stream[Symbol.asyncIterator]() and an explicit while loop) and check
signal?.aborted and throw runtimeAborted('stream', signal.reason) before each
await iterator.next(); after awaiting, handle done/value as before and process
rawRow. This ensures the abort is observed before requesting the next row while
preserving existing handling of rawRow and errors.

---

Nitpick comments:
In `@packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts`:
- Around line 123-151: The test descriptors targetDescriptor and
adapterDescriptor are missing the required manifest property; update both
objects to include a manifest: ExtensionPackManifest (matching the control-plane
contract) — e.g., add a manifest field to targetDescriptor and adapterDescriptor
with a minimal valid ExtensionPackManifest object (name/version and any required
fields) so the fixtures conform to the expected descriptor shape used by the
control plane.
🪄 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: f3d18d9e-30a1-4680-9dbc-3483328f0542

📥 Commits

Reviewing files that changed from the base of the PR and between 1247511 and e2d745a.

📒 Files selected for processing (8)
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/2-sql/5-runtime/src/codecs/race-against-abort.ts
  • packages/2-sql/5-runtime/src/sql-runtime.ts
  • packages/2-sql/5-runtime/test/codec-decode-ctx.test.ts
  • packages/2-sql/5-runtime/test/codec-encode-ctx.test.ts
  • packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts
  • test/integration/test/sql-builder/execution-abort.test.ts

Comment thread packages/2-sql/5-runtime/src/sql-runtime.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/1-framework/1-core/framework-components/src/race-against-abort.ts (1)

41-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle already-aborted signals before installing the listener.

If signal was aborted while work was being created, this listener will never fire retroactively, so Promise.race() falls through to work instead of surfacing RUNTIME.ABORTED. That path is reachable from callers like resolveValue(), which build Promise.all(...) before invoking this helper.

Suggested fix
   const abortPromise = new Promise<never>((_, reject) => {
+    if (signal.aborted) {
+      sentinel.reason = signal.reason;
+      reject(sentinel);
+      return;
+    }
+
     onAbort = () => {
       sentinel.reason = signal.reason;
       reject(sentinel);
     };
     signal.addEventListener('abort', onAbort, { once: true });
   });
If an AbortSignal is already aborted, does adding an 'abort' event listener later invoke it immediately, or must code check signal.aborted first?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts`
around lines 41 - 47, The abort listener must handle the case where signal is
already aborted before the listener is attached: inside the Promise constructor
that creates abortPromise (the block that declares onAbort, sentinel, and calls
signal.addEventListener), first check if signal.aborted and if so set
sentinel.reason = signal.reason and immediately reject(sentinel); otherwise
register the onAbort listener as before (signal.addEventListener('abort',
onAbort, { once: true })). This ensures abortPromise rejects immediately when
signal.aborted is true instead of relying on a retroactive event.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts`:
- Around line 83-89: The test's use of countAbortListeners() is ineffective
because it only increments on actual abort; replace that check by spying on the
signal's event listener registration/removal: wrap or spy on
controller.signal.addEventListener and controller.signal.removeEventListener (or
capture the callback passed to addEventListener) when calling
raceAgainstAbort(Promise.resolve(1), controller.signal, 'encode') and assert
that addEventListener was called once and removeEventListener was called with
the same callback (or that the captured callback is no longer present), and
apply the same change to the other related test block referenced (lines 110-126)
to verify listeners are actually removed.

In `@packages/2-sql/5-runtime/src/codecs/decoding.ts`:
- Around line 206-210: The current construction of cellCtx can propagate a stale
column when ref is undefined because it returns rowCtx unchanged; update the
logic that builds cellCtx (the SqlCodecCallContext creation using rowCtx and
ref) so that when ref is falsy you return a copy/clone of rowCtx with the column
property explicitly set to undefined (or omitted) instead of returning rowCtx
as-is; modify the branch that currently does `: rowCtx` to produce `{ ...rowCtx,
column: undefined }` (or equivalent) so codecs never see an inherited column for
unresolved aliases.

In `@packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts`:
- Around line 53-59: The codec.encode call in the MongoParamRef branch is
awaited directly and thus ignores mid-flight aborts; update the branch handling
MongoParamRef (where it checks value.codecId, codecs, and calls codec.encode) to
make the encode call abort-aware by racing the codec.encode promise against the
ctx.signal abort (or using a small helper that listens to ctx.signal) and, if
the signal is aborted, immediately reject/throw the RUNTIME.ABORTED error so the
abort surfaces to callers (this change should be applied in the same branch used
by MongoAdapterImpl.#resolveDocument that handles bare MongoParamRef values).

---

Duplicate comments:
In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts`:
- Around line 41-47: The abort listener must handle the case where signal is
already aborted before the listener is attached: inside the Promise constructor
that creates abortPromise (the block that declares onAbort, sentinel, and calls
signal.addEventListener), first check if signal.aborted and if so set
sentinel.reason = signal.reason and immediately reject(sentinel); otherwise
register the onAbort listener as before (signal.addEventListener('abort',
onAbort, { once: true })). This ensures abortPromise rejects immediately when
signal.aborted is true instead of relying on a retroactive event.
🪄 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: b14efbbf-4757-44cd-a83a-e20ded649935

📥 Commits

Reviewing files that changed from the base of the PR and between e2d745a and 5f9ad5b.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/codec-call-context/plan.md is excluded by !projects/**
📒 Files selected for processing (15)
  • packages/1-framework/1-core/framework-components/src/exports/runtime.ts
  • packages/1-framework/1-core/framework-components/src/race-against-abort.ts
  • packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts
  • packages/2-mongo-family/6-transport/mongo-lowering/package.json
  • packages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.ts
  • packages/2-mongo-family/7-runtime/src/mongo-runtime.ts
  • packages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.ts
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/3-mongo-target/2-mongo-adapter/src/lowering.ts
  • packages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.ts
  • packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts
  • test/integration/test/mongo/execution-abort.test.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/2-mongo-family/6-transport/mongo-lowering/package.json
  • packages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.ts

Comment thread packages/2-sql/5-runtime/src/codecs/decoding.ts Outdated
Comment on lines 53 to 59
if (value instanceof MongoParamRef) {
if (value.codecId && codecs) {
const codec = codecs.get(value.codecId);
if (codec?.encode) {
try {
return await codec.encode(value.value);
return await codec.encode(value.value, ctx);
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Top-level scalar encodes still ignore mid-flight aborts.

This branch awaits codec.encode() directly. When the lowered value is a bare MongoParamRef—for example a simple field filter or any leaf reached from MongoAdapterImpl.#resolveDocument()—there is no surrounding raced Promise.all, so an abort during codec.encode() waits for the codec to finish instead of surfacing RUNTIME.ABORTED.

Suggested fix
       if (codec?.encode) {
         try {
-          return await codec.encode(value.value, ctx);
+          const encoded = codec.encode(value.value, ctx);
+          return signal ? await raceAgainstAbort(encoded, signal, 'encode') : await encoded;
         } catch (error) {
           wrapEncodeFailure(error, value, codec.id);
         }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts` around lines 53
- 59, The codec.encode call in the MongoParamRef branch is awaited directly and
thus ignores mid-flight aborts; update the branch handling MongoParamRef (where
it checks value.codecId, codecs, and calls codec.encode) to make the encode call
abort-aware by racing the codec.encode promise against the ctx.signal abort (or
using a small helper that listens to ctx.signal) and, if the signal is aborted,
immediately reject/throw the RUNTIME.ABORTED error so the abort surfaces to
callers (this change should be applied in the same branch used by
MongoAdapterImpl.#resolveDocument that handles bare MongoParamRef values).

@wmadden wmadden force-pushed the tml-2330-codec-dispatch-concurrency-control-rate-limit-abortsignal branch from 4568fe7 to 89c30d6 Compare April 30, 2026 14:25
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (10)
packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts (2)

69-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the author-facing ctx parameter optional.

mongoCodec() can call these two-arg callbacks with ctx === undefined when no signal is supplied, so the signatures need ctx?: CodecCallContext.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts` around lines
69 - 74, The encode/decode function types in codecs.ts currently require a
second parameter ctx: CodecCallContext but mongoCodec can invoke them with ctx
=== undefined, so update both union variants for encode and decode to accept an
optional second parameter (ctx?: CodecCallContext) instead of a required one;
modify the signatures that reference encode and decode in the codec type (the
two-arg overloads) to use ctx? so callers that omit a signal are correctly
typed.

88-90: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let the identity fallback lie about TWire.

Omitting encode still returns the input value verbatim, so a specialized TWire can compile while emitting the wrong wire shape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts` around lines
88 - 90, The current identity fallback for userEncode (using (config.encode as
CtxEncode | undefined) ?? ((value: TInput) => value as unknown as TWire))
falsely promises it produces TWire; remove that identity fallback and treat
encode as optional so callers must handle absence or provide a real encoder.
Change userEncode to obtain only (config.encode as CtxEncode | undefined) (no
default function) and update any code that calls userEncode to check for
undefined or to use a safe conversion path; likewise ensure userDecode comes
from (config.decode as CtxDecode | undefined) if symmetrical handling is needed.
This prevents the compile-time lie about TWire and ensures correct runtime wire
shape handling.
packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts (2)

271-276: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the SQL author-facing ctx parameter optional.

The two-arg codec() overloads can be called without a signal, so these author signatures need ctx?: SqlCodecCallContext.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` around lines
271 - 276, The encode/decode two-argument function signatures should make the
ctx parameter optional so author-facing overloads accept calls without a
context; update the types in codec-related definitions (the encode?: and decode:
union branches) to use (value: TInput, ctx?: SqlCodecCallContext) => ... and
(wire: TWire, ctx?: SqlCodecCallContext) => ... respectively, ensuring all
codec() overloads that accept a ctx match this optional signature.

294-296: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let the SQL identity fallback lie about TWire.

The default encode still returns value unchanged, so a codec with a specialized TWire can compile while producing the wrong runtime shape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` around lines
294 - 296, The current fallback makes userEncode default to an identity function
((value: TInput) => value as unknown as TWire) which misrepresents TWire when it
differs; change the fallback so that userEncode is not silently the identity for
mismatched TWire—either require config.encode be provided for codecs where TWire
!== TInput (i.e., remove the unconditional identity fallback) or replace the
fallback with a runtime guard that throws a clear error when encode is missing
and TWire differs; update the code around userEncode, CtxEncode, config.encode
(and leave userDecode/config.decode handling intact) so codecs cannot compile
with a misleading identity encode for a specialized TWire.
packages/1-framework/1-core/framework-components/src/race-against-abort.ts (1)

41-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard already-aborted signals inside the helper.

AbortSignal does not replay abort events to late listeners, so this race can hang if the signal is already aborted when the helper is called. Check signal.aborted before registering the listener and reject immediately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts`
around lines 41 - 47, The helper currently always registers an abort listener
which can hang if the AbortSignal is already aborted; update the abortPromise
creation (and the onAbort/sentinel logic) to first check signal.aborted and, if
true, set sentinel.reason = signal.reason and immediately reject(sentinel),
otherwise create onAbort and call signal.addEventListener('abort', onAbort, {
once: true }); ensure the code path that rejects uses the same sentinel object
so downstream comparisons still work.
docs/reference/framework-gaps.md (2)

619-627: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a fence language.

The package-tree block still trips MD040; text is enough here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/framework-gaps.md` around lines 619 - 627, The fenced
package-tree block starting with "@prisma-next/extension-template" is missing a
fence language which triggers MD040; update that block to use a language tag
(e.g., add "text" after the opening triple backticks) so the code fence is
explicitly annotated and the linter stops flagging it.

3-7: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unwrap the Markdown links.

The backticks make the companion links render as code instead of clickable links.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/reference/framework-gaps.md` around lines 3 - 7, The Markdown links in
the opening paragraph are wrapped in backticks so they render as inline code
rather than clickable links; update the text by removing the backticks around
`[./system-design-review.md](./system-design-review.md)`,
`[./code-review.md](./code-review.md)`, and
`[./walkthrough.md](./walkthrough.md)` (and any other backticked link tokens in
that paragraph) so they are plain Markdown links, verify they render as
clickable links in the resulting HTML, and keep the surrounding punctuation and
capitalization intact.
packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts (1)

83-89: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert listener removal directly.

countAbortListeners() always returns 0, so this can pass even if the abort listener leaks. Spy on addEventListener / removeEventListener or capture the callback and verify it is detached.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts`
around lines 83 - 89, The test currently relies on countAbortListeners() which
always returns 0 and can hide listener leaks; update the test for
raceAgainstAbort to spy on the signal's addEventListener/removeEventListener (or
stub capture the actual callback passed) and assert that removeEventListener is
called with the same callback when the raced promise resolves; specifically,
wrap the AbortController.signal, spy on its addEventListener/removeEventListener
methods or replace them with instrumented functions in the test, call
raceAgainstAbort(Promise.resolve(1), controller.signal, 'encode'), and verify
removeEventListener was invoked (with the previously captured callback) to
ensure the listener is detached.
packages/1-framework/1-core/framework-components/src/runtime-error.ts (1)

79-82: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve explicit falsy abort reasons.

cause ?? ... replaces null, false, 0, and '' with a synthetic AbortError, so explicit cancellation detail gets lost.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/1-framework/1-core/framework-components/src/runtime-error.ts` around
lines 79 - 82, The runtimeAborted function currently uses the nullish coalescing
operator to set resolvedCause which incorrectly replaces explicit falsy values
(null, false, 0, ''), so change the fallback logic to only substitute when cause
is strictly undefined (e.g., if cause === undefined then use new
DOMException(...), otherwise keep cause) in runtimeAborted; update the
resolvedCause assignment in that function (and ensure the returned envelope
still attaches { cause: resolvedCause } as before), referencing runtimeAborted,
resolvedCause, runtimeError, and RUNTIME_ABORTED to locate the change.
packages/2-sql/5-runtime/src/codecs/decoding.ts (1)

200-204: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear column when ref is missing.

Returning rowCtx unchanged still leaks any inherited column into aggregate/computed cells, so the new “column undefined” contract is violated on reused contexts.

Suggested fix
   const cellCtx: SqlCodecCallContext | undefined = rowCtx
     ? ref
       ? { ...rowCtx, column: { table: ref.table, name: ref.column } }
-      : rowCtx
+      : { ...rowCtx, column: undefined }
     : undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/5-runtime/src/codecs/decoding.ts` around lines 200 - 204, The
current creation of cellCtx can leak an existing rowCtx.column when ref is
falsy; change the logic so that when rowCtx exists but ref is missing you
explicitly clear column (e.g., produce { ...rowCtx, column: undefined }) instead
of returning rowCtx unchanged; update the expression that builds cellCtx
(referencing cellCtx, SqlCodecCallContext, rowCtx, ref, column) so both branches
explicitly set column (either the {table,name} object when ref is present or
undefined when not).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/2-sql/5-runtime/src/codecs/encoding.ts`:
- Around line 122-129: The zero-parameter fast-return in encodeParams skips
cancellation checks; move the abort check (inspect ctx?.signal and throw
runtimeAborted('encode', signal.reason) if signal.aborted) above the early
return for plan.params.length === 0 so an already-aborted context yields
RUNTIME.ABORTED; update the code paths in encodeParams to reference the same
signal logic (ctx?.signal and runtimeAborted) before returning plan.params.

In `@packages/2-sql/5-runtime/test/codec-decode-ctx.test.ts`:
- Around line 142-196: The tests currently call decodeRow with undefined for the
reusable context, so they don't catch a regression where a previously-populated
SqlCodecCallContext.column leaks into unresolved projections; create a reusable
SqlCodecCallContext object (e.g., assign a dummy column value) and pass it as
the fourth argument to decodeRow in both tests (the calls around decodeRow(...,
undefined, { signal })). This seeds the context with a stale column value so the
assertions that ctx.column is cleared (observed?.column toBeUndefined) will fail
if the runtime doesn't reset column for AggregateExpr and non-column-ref
ProjectionItem cases; update both test cases to use the seeded context.

In `@packages/2-sql/5-runtime/test/codec-encode-ctx.test.ts`:
- Around line 141-171: Add a regression test that calls encodeParams with an
empty plan to ensure an already-aborted signal short-circuits even when there
are zero parameters; specifically, create an AbortController, call
controller.abort(reason), then call encodeParams([], registry, { signal:
controller.signal }) (using the same createCodecRegistry()/registry setup used
elsewhere) and assert it rejects with code 'RUNTIME.ABORTED' and details.phase
=== 'encode' and that no codec encode functions (e.g., those registered via
codec(...) and registry.register(...)) are invoked—this mirrors the existing
"already-aborted" test but uses buildPlan([]) / an empty params array to cover
the zero-parameter early-return path.

In `@packages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts`:
- Around line 37-39: The test currently asserts deep equality with
expect(observed).toEqual([ctx]) which allows a cloned context to pass; update
the assertion to verify identity preservation by asserting the observed entry is
the exact same CodecCallContext instance produced earlier (e.g., replace the
deep-equality check with an assertion that observed[0] is the same object as ctx
using toBe, optionally also asserting observed.length === 1). This change should
be applied in the resolveValue test where MongoParamRef('hello', { codecId:
'test/observe@1' }), registry, ctx and the observed array are used.

---

Duplicate comments:
In `@docs/reference/framework-gaps.md`:
- Around line 619-627: The fenced package-tree block starting with
"@prisma-next/extension-template" is missing a fence language which triggers
MD040; update that block to use a language tag (e.g., add "text" after the
opening triple backticks) so the code fence is explicitly annotated and the
linter stops flagging it.
- Around line 3-7: The Markdown links in the opening paragraph are wrapped in
backticks so they render as inline code rather than clickable links; update the
text by removing the backticks around
`[./system-design-review.md](./system-design-review.md)`,
`[./code-review.md](./code-review.md)`, and
`[./walkthrough.md](./walkthrough.md)` (and any other backticked link tokens in
that paragraph) so they are plain Markdown links, verify they render as
clickable links in the resulting HTML, and keep the surrounding punctuation and
capitalization intact.

In `@packages/1-framework/1-core/framework-components/src/race-against-abort.ts`:
- Around line 41-47: The helper currently always registers an abort listener
which can hang if the AbortSignal is already aborted; update the abortPromise
creation (and the onAbort/sentinel logic) to first check signal.aborted and, if
true, set sentinel.reason = signal.reason and immediately reject(sentinel),
otherwise create onAbort and call signal.addEventListener('abort', onAbort, {
once: true }); ensure the code path that rejects uses the same sentinel object
so downstream comparisons still work.

In `@packages/1-framework/1-core/framework-components/src/runtime-error.ts`:
- Around line 79-82: The runtimeAborted function currently uses the nullish
coalescing operator to set resolvedCause which incorrectly replaces explicit
falsy values (null, false, 0, ''), so change the fallback logic to only
substitute when cause is strictly undefined (e.g., if cause === undefined then
use new DOMException(...), otherwise keep cause) in runtimeAborted; update the
resolvedCause assignment in that function (and ensure the returned envelope
still attaches { cause: resolvedCause } as before), referencing runtimeAborted,
resolvedCause, runtimeError, and RUNTIME_ABORTED to locate the change.

In
`@packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts`:
- Around line 83-89: The test currently relies on countAbortListeners() which
always returns 0 and can hide listener leaks; update the test for
raceAgainstAbort to spy on the signal's addEventListener/removeEventListener (or
stub capture the actual callback passed) and assert that removeEventListener is
called with the same callback when the raced promise resolves; specifically,
wrap the AbortController.signal, spy on its addEventListener/removeEventListener
methods or replace them with instrumented functions in the test, call
raceAgainstAbort(Promise.resolve(1), controller.signal, 'encode'), and verify
removeEventListener was invoked (with the previously captured callback) to
ensure the listener is detached.

In `@packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts`:
- Around line 69-74: The encode/decode function types in codecs.ts currently
require a second parameter ctx: CodecCallContext but mongoCodec can invoke them
with ctx === undefined, so update both union variants for encode and decode to
accept an optional second parameter (ctx?: CodecCallContext) instead of a
required one; modify the signatures that reference encode and decode in the
codec type (the two-arg overloads) to use ctx? so callers that omit a signal are
correctly typed.
- Around line 88-90: The current identity fallback for userEncode (using
(config.encode as CtxEncode | undefined) ?? ((value: TInput) => value as unknown
as TWire)) falsely promises it produces TWire; remove that identity fallback and
treat encode as optional so callers must handle absence or provide a real
encoder. Change userEncode to obtain only (config.encode as CtxEncode |
undefined) (no default function) and update any code that calls userEncode to
check for undefined or to use a safe conversion path; likewise ensure userDecode
comes from (config.decode as CtxDecode | undefined) if symmetrical handling is
needed. This prevents the compile-time lie about TWire and ensures correct
runtime wire shape handling.

In `@packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts`:
- Around line 271-276: The encode/decode two-argument function signatures should
make the ctx parameter optional so author-facing overloads accept calls without
a context; update the types in codec-related definitions (the encode?: and
decode: union branches) to use (value: TInput, ctx?: SqlCodecCallContext) => ...
and (wire: TWire, ctx?: SqlCodecCallContext) => ... respectively, ensuring all
codec() overloads that accept a ctx match this optional signature.
- Around line 294-296: The current fallback makes userEncode default to an
identity function ((value: TInput) => value as unknown as TWire) which
misrepresents TWire when it differs; change the fallback so that userEncode is
not silently the identity for mismatched TWire—either require config.encode be
provided for codecs where TWire !== TInput (i.e., remove the unconditional
identity fallback) or replace the fallback with a runtime guard that throws a
clear error when encode is missing and TWire differs; update the code around
userEncode, CtxEncode, config.encode (and leave userDecode/config.decode
handling intact) so codecs cannot compile with a misleading identity encode for
a specialized TWire.

In `@packages/2-sql/5-runtime/src/codecs/decoding.ts`:
- Around line 200-204: The current creation of cellCtx can leak an existing
rowCtx.column when ref is falsy; change the logic so that when rowCtx exists but
ref is missing you explicitly clear column (e.g., produce { ...rowCtx, column:
undefined }) instead of returning rowCtx unchanged; update the expression that
builds cellCtx (referencing cellCtx, SqlCodecCallContext, rowCtx, ref, column)
so both branches explicitly set column (either the {table,name} object when ref
is present or undefined when not).
🪄 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: 291f58ff-809b-4073-becc-3129ad430078

📥 Commits

Reviewing files that changed from the base of the PR and between 4568fe7 and 89c30d6.

📒 Files selected for processing (39)
  • docs/architecture docs/adrs/ADR 204 - Single-Path Async Codec Runtime.md
  • docs/architecture docs/adrs/ADR 207 - Codec call context per-query AbortSignal and column metadata.md
  • docs/reference/framework-gaps.md
  • packages/1-framework/1-core/framework-components/README.md
  • packages/1-framework/1-core/framework-components/src/codec-types.ts
  • packages/1-framework/1-core/framework-components/src/exports/codec.ts
  • packages/1-framework/1-core/framework-components/src/exports/runtime.ts
  • packages/1-framework/1-core/framework-components/src/race-against-abort.ts
  • packages/1-framework/1-core/framework-components/src/runtime-core.ts
  • packages/1-framework/1-core/framework-components/src/runtime-error.ts
  • packages/1-framework/1-core/framework-components/src/runtime-middleware.ts
  • packages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.ts
  • packages/1-framework/1-core/framework-components/test/race-against-abort.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-aborted.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core-options.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core-options.types.test-d.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/README.md
  • packages/2-mongo-family/1-foundation/mongo-codec/src/codecs.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.ts
  • packages/2-mongo-family/6-transport/mongo-lowering/package.json
  • packages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.ts
  • packages/2-mongo-family/7-runtime/src/mongo-runtime.ts
  • packages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.ts
  • packages/2-sql/4-lanes/relational-core/README.md
  • packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.types.test-d.ts
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/2-sql/5-runtime/src/sql-runtime.ts
  • packages/2-sql/5-runtime/test/codec-decode-ctx.test.ts
  • packages/2-sql/5-runtime/test/codec-encode-ctx.test.ts
  • packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts
  • packages/3-mongo-target/2-mongo-adapter/src/lowering.ts
  • packages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.ts
  • packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts
✅ Files skipped from review due to trivial changes (7)
  • packages/2-mongo-family/6-transport/mongo-lowering/package.json
  • packages/1-framework/1-core/framework-components/src/exports/codec.ts
  • packages/1-framework/1-core/framework-components/test/runtime-aborted.test.ts
  • packages/1-framework/1-core/framework-components/test/codec-call-context.types.test-d.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core-options.types.test-d.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.types.test-d.ts
  • packages/1-framework/1-core/framework-components/README.md
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/README.md
  • packages/1-framework/1-core/framework-components/test/runtime-core-options.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter-ctx.test.ts
  • packages/1-framework/1-core/framework-components/src/runtime-middleware.ts
  • packages/1-framework/1-core/framework-components/src/exports/runtime.ts
  • packages/2-mongo-family/1-foundation/mongo-codec/test/codecs-ctx.test-d.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/codec-factory-ctx.test.ts
  • packages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.ts
  • docs/architecture docs/adrs/ADR 207 - Codec call context per-query AbortSignal and column metadata.md
  • packages/1-framework/1-core/framework-components/src/codec-types.ts
  • packages/2-mongo-family/7-runtime/src/mongo-runtime.ts
  • packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts
  • packages/2-sql/5-runtime/src/sql-runtime.ts

Comment thread packages/2-sql/5-runtime/src/codecs/encoding.ts Outdated
Comment thread packages/2-sql/5-runtime/test/codec-decode-ctx.test.ts
Comment thread packages/2-sql/5-runtime/test/codec-encode-ctx.test.ts
Comment on lines +37 to +39
const ctx: CodecCallContext = { signal: new AbortController().signal };
await resolveValue(new MongoParamRef('hello', { codecId: 'test/observe@1' }), registry, ctx);
expect(observed).toEqual([ctx]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert the same context object, not just deep equality.

toEqual([ctx]) can still pass if resolveValue() clones the context object and reuses the same signal. Use toBe on the observed value so this test actually proves identity preservation.

♻️ Proposed fix
-    expect(observed).toEqual([ctx]);
+    expect(observed).toHaveLength(1);
+    expect(observed[0]).toBe(ctx);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ctx: CodecCallContext = { signal: new AbortController().signal };
await resolveValue(new MongoParamRef('hello', { codecId: 'test/observe@1' }), registry, ctx);
expect(observed).toEqual([ctx]);
const ctx: CodecCallContext = { signal: new AbortController().signal };
await resolveValue(new MongoParamRef('hello', { codecId: 'test/observe@1' }), registry, ctx);
expect(observed).toHaveLength(1);
expect(observed[0]).toBe(ctx);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-mongo-target/2-mongo-adapter/test/resolve-value-ctx.test.ts`
around lines 37 - 39, The test currently asserts deep equality with
expect(observed).toEqual([ctx]) which allows a cloned context to pass; update
the assertion to verify identity preservation by asserting the observed entry is
the exact same CodecCallContext instance produced earlier (e.g., replace the
deep-equality check with an assertion that observed[0] is the same object as ctx
using toBe, optionally also asserting observed.length === 1). This change should
be applied in the resolveValue test where MongoParamRef('hello', { codecId:
'test/observe@1' }), registry, ctx and the observed array are used.

cursor Bot pushed a commit that referenced this pull request Apr 30, 2026
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)

The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.

The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.

Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.

Phase E TE.1-TE.5 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request Apr 30, 2026
cfe1b6d

ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5.

Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`.

Phase E TE.6-TE.7 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request Apr 30, 2026
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)

The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.

The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.

Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.

Phase E TE.1-TE.5 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request Apr 30, 2026
cfe1b6d

ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5.

Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`.

Phase E TE.6-TE.7 (codec-registry-unification project).
@wmadden wmadden changed the title feat(codec): add per-query call context and signal option TML-2330: add per-query codec call context + AbortSignal May 1, 2026
Copy link
Copy Markdown
Contributor Author

@wmadden wmadden left a comment

Choose a reason for hiding this comment

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

Many locations now encode ctx as optional, which is wrong. We control it. While the options the user provides to execute() are optional, every call site flowing from that should take a non-optional ctx.

Similarly, adapter methods take an optional codecs option. That's stupid. It's not optional. You'll need to address this since the following option is ctx, which should not be optional.

*/
export function runtimeAborted(phase: RuntimeAbortedPhase, cause?: unknown): RuntimeErrorEnvelope {
const resolvedCause =
cause === undefined ? new DOMException('The operation was aborted.', 'AbortError') : cause;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why is this a DOMException? We don't necessarily run in the browser

Comment on lines +69 to +74
encode?:
| ((value: TInput) => TWire | Promise<TWire>)
| ((value: TInput, ctx: CodecCallContext) => TWire | Promise<TWire>);
decode:
| ((wire: TWire) => TInput | Promise<TInput>)
| ((wire: TWire, ctx: CodecCallContext) => TInput | Promise<TInput>);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This union is unnecessary and will complicate any type inference based on the encode/decode functions. Simply use an optional final argument

See [ADR 204 — Single-Path Async Codec Runtime](../../../../docs/architecture%20docs/adrs/ADR%20204%20-%20Single-Path%20Async%20Codec%20Runtime.md) for the codec runtime's async boundary contract.
### Codec call context (`ctx`)

Codec authors may optionally take a second `ctx?: CodecCallContext` argument on `encode`. The Mongo runtime threads one context per `mongoRuntime.execute(plan, { signal })` call. Mongo uses the framework `CodecCallContext` directly (signal-only); column metadata is SQL-family-specific and isn't part of Mongo's per-call shape today.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're overcomplicating this. "Codecs receive a second options argument..."


> **Note.** Mongo's read path doesn't go through `codec.decode` (per ADR 204 cross-family scope notes), so the `decode` signature above accepts `ctx` for parity with the codec interface but the runtime doesn't currently invoke `decode` on the Mongo read side. Encode-side `ctx.signal` is observed at every recursion level of `resolveValue` so a mid-encode abort surfaces as `RUNTIME.ABORTED { phase: 'encode' }`.

Existing single-arg authors (e.g. `(v: string) => v`) continue to compile and run unchanged. Aborts surface to the caller as `RUNTIME.ABORTED`; codec bodies that ignore the signal complete in the background (cooperative cancellation).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Again, overcomplicated. All codecs receive the arg, you may ignore it.

This is unnecessary because TS permits a function which accepts only one arg to be assigned to a function signature which accepts two (but not the other way around)

paramRef: { readonly codecId?: string; readonly name?: string },
paramIndex: number,
registry: CodecRegistry,
ctx?: SqlCodecCallContext,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is wrong. The context arg is non-optional, what's optional is recipients doing anything with it.

Comment on lines +149 to +151
const settled = signal
? await raceAgainstAbort(Promise.all(tasks), signal, 'encode')
: await Promise.all(tasks);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This would be simpler if raceAgainstAbort() treated the abort signal as optional. Then callers could collapse all these branches

Comment on lines +268 to +272
// Pre-check at entry: an already-aborted caller observes
// RUNTIME.ABORTED on the first `next()` without any work being done.
if (signal?.aborted) {
throw runtimeAborted('stream', signal.reason);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This precheck is repeated often, too. It's a good candidate for a utility function alongside raceAgainstAbort()

export async function lowerFilter(
filter: MongoFilterExpr,
codecs?: MongoCodecRegistry,
ctx?: CodecCallContext,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Non-optional - both this and codecs

export async function lowerStage(
stage: MongoPipelineStage,
codecs?: MongoCodecRegistry,
ctx?: CodecCallContext,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All these ctx usages should be non-optional. We control the calling context. codecs will NEVER be undefined, and neither will ctx.

}

async #resolveDocument(expr: MongoExpr): Promise<Document> {
async #resolveDocument(expr: MongoExpr, ctx?: CodecCallContext): Promise<Document> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same for this and all references to ctx on this branch. Non-optional.

cursor Bot pushed a commit that referenced this pull request May 1, 2026
…lback)

The SQL `codec()` and Mongo `mongoCodec()` factories declared `encode?:`
optional and installed an identity fallback `(value) => value as unknown
as TWire` when omitted. The cast is unsound — an author who declares
`TInput=number, TWire=string` and forgets `encode` silently produces a
runtime that returns numbers where the wire shape promised strings.

Audit confirms zero in-tree consumers of the identity fallback: every
existing codec definition (SQL core, postgres adapter, sqlite adapter,
mongo adapter, pgvector extension) declares an explicit `encode`.
Making `encode` required is therefore non-disruptive and closes the
type hole. `decode` was already required for the same reason.

The standalone `encode` identity fallback is gone; the
`encodeJson`/`decodeJson` identity defaults (gated on
`TInput extends JsonValue`) remain — those defaults are sound because
the conditional input constraint guarantees the contract artifact stays
JSON-safe.

Resolves CodeRabbit C7 on PR #400.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
The author-facing two-arg shape on the SQL `codec()` and Mongo
`mongoCodec()` factories declared `ctx: SqlCodecCallContext` /
`ctx: CodecCallContext` (non-optional), but the runtime constructs
`codecCtx: SqlCodecCallContext | undefined = signal ? { signal } : undefined`
and forwards `undefined` when no signal was supplied. So an author
who writes `encode: (value, ctx: CodecCallContext) => …` was lying
about the type — they would receive `undefined` despite the
signature claiming non-optional.

Switch the two-arg arms of `encode` and `decode` to `ctx?:`, matching
the runtime contract end to end. The internal `CtxEncode` / `CtxDecode`
lift types already used `ctx?:` so the lift body is unchanged.

`mongoVectorCodec` (`packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts`)
and `pgvectorCodec` (`packages/3-extensions/pgvector/src/core/codecs.ts`)
are real-world parameterised-codec consumers that ride the two-arg
author surface, so the type/runtime mismatch is in tree today.

Resolves CodeRabbit C6 on PR #400.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…site

The user-facing boundary `runtime.execute(plan, options?)` keeps `options.signal?` optional, but every internal `ctx` parameter from there on is now non-optional. The runtime allocates one `CodecCallContext` per `execute()` and threads the same reference everywhere; the `signal` field inside the ctx may be `undefined` (when no signal was supplied) but the ctx object itself is always present.

Same treatment for the Mongo adapter `MongoAdapter.lower(plan, ctx)` and the lowering helpers `lowerFilter` / `lowerStage` / `lowerPipeline` / `resolveValue` — `codecs` and `ctx` are both non-optional internally; `MongoAdapter` constructs a default registry once at `createMongoAdapter()` and never threads `codecs?: undefined` through the lowering tree again.

Adds a `checkAborted(ctx, phase)` helper alongside `raceAgainstAbort` and replaces every `if (ctx.signal?.aborted) throw runtimeAborted(...)` precheck site with it (sql encoding/decoding entries, sql-runtime entry + between-rows, mongo resolve-value entry).

Resolves the user's review summary on PR #400.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…ng signature

Completes the deferred Change 1 from the PR #400 review. The previous attempt regressed `TInput` inference for `string | Date`-style codec authors; canonicalizing those authors on `Date` (previous commit) removed the only in-tree consumer that depended on the union-arity author shape, so the simpler single-signature form is now safe to land.

`codec()` and `mongoCodec()` config types now declare `encode: (value, ctx?) => …` and `decode: (wire, ctx?) => …` directly. Authors that ignore the second argument continue to compile via TypeScript bivariance for trailing optional parameters; the `as CtxEncode` widening cast in the factory body is no longer needed. Drops the corresponding "single ctx-bearing config signature" alternative from ADR 207 (the argument it made -- preserves TInput inference for `string | Date`-style authors -- is no longer in force). README copy in framework-components, sql-relational-core, and mongo-codec is reframed: the optional second `ctx` arg is just part of the codec contract; the union-of-arities mechanism is gone.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…lback)

The SQL `codec()` and Mongo `mongoCodec()` factories declared `encode?:`
optional and installed an identity fallback `(value) => value as unknown
as TWire` when omitted. The cast is unsound — an author who declares
`TInput=number, TWire=string` and forgets `encode` silently produces a
runtime that returns numbers where the wire shape promised strings.

Audit confirms zero in-tree consumers of the identity fallback: every
existing codec definition (SQL core, postgres adapter, sqlite adapter,
mongo adapter, pgvector extension) declares an explicit `encode`.
Making `encode` required is therefore non-disruptive and closes the
type hole. `decode` was already required for the same reason.

The standalone `encode` identity fallback is gone; the
`encodeJson`/`decodeJson` identity defaults (gated on
`TInput extends JsonValue`) remain — those defaults are sound because
the conditional input constraint guarantees the contract artifact stays
JSON-safe.

Resolves CodeRabbit C7 on PR #400.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
The author-facing two-arg shape on the SQL `codec()` and Mongo
`mongoCodec()` factories declared `ctx: SqlCodecCallContext` /
`ctx: CodecCallContext` (non-optional), but the runtime constructs
`codecCtx: SqlCodecCallContext | undefined = signal ? { signal } : undefined`
and forwards `undefined` when no signal was supplied. So an author
who writes `encode: (value, ctx: CodecCallContext) => …` was lying
about the type — they would receive `undefined` despite the
signature claiming non-optional.

Switch the two-arg arms of `encode` and `decode` to `ctx?:`, matching
the runtime contract end to end. The internal `CtxEncode` / `CtxDecode`
lift types already used `ctx?:` so the lift body is unchanged.

`mongoVectorCodec` (`packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts`)
and `pgvectorCodec` (`packages/3-extensions/pgvector/src/core/codecs.ts`)
are real-world parameterised-codec consumers that ride the two-arg
author surface, so the type/runtime mismatch is in tree today.

Resolves CodeRabbit C6 on PR #400.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…site

The user-facing boundary `runtime.execute(plan, options?)` keeps `options.signal?` optional, but every internal `ctx` parameter from there on is now non-optional. The runtime allocates one `CodecCallContext` per `execute()` and threads the same reference everywhere; the `signal` field inside the ctx may be `undefined` (when no signal was supplied) but the ctx object itself is always present.

Same treatment for the Mongo adapter `MongoAdapter.lower(plan, ctx)` and the lowering helpers `lowerFilter` / `lowerStage` / `lowerPipeline` / `resolveValue` — `codecs` and `ctx` are both non-optional internally; `MongoAdapter` constructs a default registry once at `createMongoAdapter()` and never threads `codecs?: undefined` through the lowering tree again.

Adds a `checkAborted(ctx, phase)` helper alongside `raceAgainstAbort` and replaces every `if (ctx.signal?.aborted) throw runtimeAborted(...)` precheck site with it (sql encoding/decoding entries, sql-runtime entry + between-rows, mongo resolve-value entry).

Resolves the user's review summary on PR #400.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…ng signature

Completes the deferred Change 1 from the PR #400 review. The previous attempt regressed `TInput` inference for `string | Date`-style codec authors; canonicalizing those authors on `Date` (previous commit) removed the only in-tree consumer that depended on the union-arity author shape, so the simpler single-signature form is now safe to land.

`codec()` and `mongoCodec()` config types now declare `encode: (value, ctx?) => …` and `decode: (wire, ctx?) => …` directly. Authors that ignore the second argument continue to compile via TypeScript bivariance for trailing optional parameters; the `as CtxEncode` widening cast in the factory body is no longer needed. Drops the corresponding "single ctx-bearing config signature" alternative from ADR 207 (the argument it made -- preserves TInput inference for `string | Date`-style authors -- is no longer in force). README copy in framework-components, sql-relational-core, and mongo-codec is reframed: the optional second `ctx` arg is just part of the codec contract; the union-of-arities mechanism is gone.
@cursor cursor Bot force-pushed the tml-2330-codec-dispatch-concurrency-control-rate-limit-abortsignal branch from 80bd348 to e7507f1 Compare May 1, 2026 09:23
wmadden added 7 commits May 1, 2026 10:04
…ference

Captures the shaping output for TML-2330: introduce CodecCallContext
({ signal?, column? }) threaded through codec dispatch sites, scope
narrowed from the original ticket sketch (no concurrency cap, no bulk
codec interface). Adds the framework-gaps reference documenting the
CipherStash integration findings that motivate this work and the
sibling tickets (TML-2359 middleware param transformation, TML-2360
envelope-codec extension + pattern doc).
Introduce `CodecCallContext` as the named, future-extensible per-call
context the runtime threads to every codec.encode / codec.decode
invocation. Two fields land in this commit:

- `signal?: AbortSignal` for per-query cancellation.
- `column?: { table, name }` for decode-side column identity (encode
  call sites pass undefined; encode-time column context is the
  middleware's domain).

`Codec.encode` / `Codec.decode` accept the context as an optional
second argument. The arg is strictly additive: existing single-arg
call sites continue to compile and behave identically. ADR 204's
walk-back constraints are preserved (no TRuntime generic, no
runtime/kind discriminator, no conditional return types).

Project: codec-call-context.
Phase m1 (T1.1, T1.3, T1.4).
The SQL `codec()` factory now accepts author functions of either
arity for `encode` and `decode`:

- `(value) => …` — single-arg authors lift unchanged.
- `(value, ctx) => …` — ctx-bearing authors receive a CodecCallContext
  instance forwarded through the lift; AbortSignal identity is
  preserved across calls.

The encode/decode config types are expressed as a union of the two
arities so TypeScript continues to infer TInput from the user's
function signature without widening (e.g. when the author writes
`(value: string | Date): string` for normalization). The factory
widens to the two-arg shape internally so the lift can forward
`ctx` to either author shape.

Project: codec-call-context.
Phase m1 (T1.2 SQL, T1.5).
The Mongo codec factory now mirrors `codec()` in sql-relational-core:
author functions for `encode` and `decode` may take either `(value)`
or `(value, ctx: CodecCallContext)`. The factory lifts both shapes
uniformly and forwards ctx (with AbortSignal identity preserved)
through the lifted methods.

Project: codec-call-context.
Phase m1 (T1.2 Mongo, T1.6).
Add `RUNTIME_ABORTED` (the canonical code) and `runtimeAborted(phase,
cause?)`, a tiny helper colocated with `runtimeError`. The helper
produces a runtime-error envelope with `details.phase` set to one of
`'encode' | 'decode' | 'stream'` and `cause` set to the supplied abort
reason (falling back to an `AbortError` DOMException so the envelope
always carries a cause).

Pre-existing in-repo discrepancy: runtime error codes are not
enumerated in a central registry; they are string literals at call
sites. T1.7 asks to "add `RUNTIME.ABORTED` to the runtime error code
list ... wherever envelope codes are enumerated." The closest match is
exporting a typed constant + a typed helper from `runtime-error.ts`,
which is what lands here. The helper is the construction site that
m2/m3 will throw from.

Project: codec-call-context.
Phase m1 (T1.7).
…tions

Extend the family-agnostic runtime entry point to accept an optional
`{ signal?: AbortSignal }` second argument:

- `RuntimeExecutor<TPlan>.execute(plan, options?)` — interface gains
  the options arg via the new `RuntimeExecuteOptions` type.
- `RuntimeCore.execute(plan, options?)` — pre-checks `signal.aborted`
  at entry and throws `RUNTIME.ABORTED { phase: 'stream' }` (with
  `signal.reason` as cause) before any work begins. When a signal is
  present, constructs a `CodecCallContext = { signal }` and forwards
  it to the abstract `lower(plan, ctx?)` so concrete subclasses
  (SQL/Mongo) can thread the signal to their codec dispatch sites in
  m2/m3.

Existing `runtime.execute(plan)` call sites continue to compile —
options is optional and the default no-signal path is structurally
identical to today. Concrete `lower(plan)` overrides remain
compatible (the new `ctx?` parameter is optional).

Project: codec-call-context.
Phase m1 (T1.8, T1.9).
The framework Codec interface is target-agnostic and cannot encode
SQL-specific (table, column) addressing. Move the column field off
the framework CodecCallContext and into a SqlCodecCallContext defined
in the SQL layer, with the SQL Codec interface narrowing encode/decode
to use it. Mongo continues to use the framework CodecCallContext
directly (no extension introduced). Spec FR2/FR4/FR9 + AC and plan
M1 T1.4/T1.5 + M2/M3 dispatch tasks updated accordingly.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)

The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.

The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.

Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.

Phase E TE.1-TE.5 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request May 1, 2026
cfe1b6d

ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5.

Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`.

Phase E TE.6-TE.7 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)

The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.

The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.

Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.

Phase E TE.1-TE.5 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request May 1, 2026
cfe1b6d

ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5.

Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`.

Phase E TE.6-TE.7 (codec-registry-unification project).
wmadden pushed a commit that referenced this pull request May 1, 2026
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)

The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.

The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.

Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.

Phase E TE.1-TE.5 (codec-registry-unification project).
wmadden pushed a commit that referenced this pull request May 1, 2026
cfe1b6d

ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5.

Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`.

Phase E TE.6-TE.7 (codec-registry-unification project).
cursor Bot pushed a commit that referenced this pull request May 1, 2026
…ion (TML-2324)

Two other ADRs already claim 207 on main: "Codec call context: per-query AbortSignal and column metadata" (cfe1b6d, PR #400 / TML-2330) and "Family-instance capability views for the framework CLI" (36ea069). ADR 208 ("Higher-order codecs for parameterized types") is also taken. 209 is the next free slot.

- git mv the ADR file 207 -> 209
- update the heading inside the ADR
- update two source-code references (mongo-runtime.ts JSDoc, decoding.test.ts comment)

Other "ADR 207" matches in the repo (ADR 204 cross-family notes, sql-relational-core / mongo-codec / framework-components READMEs, framework-gaps doc) refer to the codec-call-context ADR 207, not ours; left alone.
wmadden pushed a commit that referenced this pull request May 1, 2026
…ion (TML-2324)

Two other ADRs already claim 207 on main: "Codec call context: per-query AbortSignal and column metadata" (cfe1b6d, PR #400 / TML-2330) and "Family-instance capability views for the framework CLI" (36ea069). ADR 208 ("Higher-order codecs for parameterized types") is also taken. 209 is the next free slot.

- git mv the ADR file 207 -> 209
- update the heading inside the ADR
- update two source-code references (mongo-runtime.ts JSDoc, decoding.test.ts comment)

Other "ADR 207" matches in the repo (ADR 204 cross-family notes, sql-relational-core / mongo-codec / framework-components READMEs, framework-gaps doc) refer to the codec-call-context ADR 207, not ours; left alone.
wmadden added a commit that referenced this pull request May 1, 2026
…h middleware-param-transform cross-refs

Rewrites envelope-codec-extension.spec.md against the post-#402 codec API
and the locked Project 1 scope:

- Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema
  for { equality, freeTextSearch }, registered separately from the codec body
  itself (mirrors pgvector's plumbing for length).
- Constrains the column-type surface to EncryptedString only (3 argument
  shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber,
  and other types from scope — those land in Project 2 alongside their codec
  round-trip / search-operator / migration tests.
- Adds the EQL bundle install via databaseDependencies.init, sourced from
  the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for
  CREATE EXTENSION vector.
- Adds operator lowering for eq and ilike, deferring the canonical SQL
  function names to the first-attempt's operation-templates.ts.
- Native type is eql_v2_encrypted (not text), reflecting EQL's actual
  storage type.
- Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests
  for round-trip, bulk amortization, nullable handling, and cancellation.

Refreshes middleware-param-transform.spec.md cross-references:

- Path depth bumped (../../ → ../../../) for the new specs/ subdirectory
  location.
- Adds back-reference to the umbrella spec.
- Updates the cipherstash-extension cross-reference to point at the new
  envelope-codec-extension.spec.md path.
- Annotates ADR 207 reference as forthcoming with PR #400.
wmadden added a commit that referenced this pull request May 1, 2026
… with high-level plan

Restructure the cipherstash-integration project from a single-spec layout
into a three-component umbrella:
- project-1/: searchable-encryption MVP (the previous umbrella spec
  rescoped as Project 1, plus its 5 task specs moved under specs/)
- project-2/: planner-driven DDL + expanded surface (new stub)
- sql-raw-factory/: public raw\`...\` template-literal factory
  (moved from projects/sql-raw-factory/)

New artifacts at the umbrella level:
- spec.md: scope of the umbrella, why three components, cross-component
  design decisions (RawSqlExpr ownership, DataTransformOperation choice,
  end-to-end-tested-only scope), in-flight framework dependency status
  (PRs #400/#402 merged 2026-05-01; #404/#409 still open).
- plan.md: component-level sequencing. Phase A is Project 1 (critical
  path, gated externally on #404 + #409); phase B is sql-raw-factory and
  Project 2 in parallel afterwards, each with its own gating story
  (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing;
  Project 2 blocks on Project 1 + TML-2338 + TML-2339).

Project 1's spec is reframed: drop "this is the umbrella" language, add
a header pointing to the new umbrella, repath all relative references
for the new directory depth (3-up from project-1/, 4-up from
project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously-
open question about Project 2's on-disk slug.

No content changes to the per-task specs beyond reference repaths.
wmadden pushed a commit that referenced this pull request May 1, 2026
…nsion

Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:

- Write side: users construct envelopes from plaintext
  (EncryptedString.from('me@example.com')); the extension's
  middleware walks ParamRefs in beforeExecute, batches all envelope
  plaintexts into one bulkEncrypt({ signal }) call, replaces values
  with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
  ciphertext + the column handle (table, column, KMS metadata)
  supplied by SqlCodecCallContext.column. Users call
  await row.email.decrypt() per cell, or use bulk-decrypt
  utilities (decryptAll, post-buffering) to amortize across many
  envelopes.

Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).

Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.

Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
wmadden added a commit that referenced this pull request May 1, 2026
…h middleware-param-transform cross-refs

Rewrites envelope-codec-extension.spec.md against the post-#402 codec API
and the locked Project 1 scope:

- Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema
  for { equality, freeTextSearch }, registered separately from the codec body
  itself (mirrors pgvector's plumbing for length).
- Constrains the column-type surface to EncryptedString only (3 argument
  shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber,
  and other types from scope — those land in Project 2 alongside their codec
  round-trip / search-operator / migration tests.
- Adds the EQL bundle install via databaseDependencies.init, sourced from
  the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for
  CREATE EXTENSION vector.
- Adds operator lowering for eq and ilike, deferring the canonical SQL
  function names to the first-attempt's operation-templates.ts.
- Native type is eql_v2_encrypted (not text), reflecting EQL's actual
  storage type.
- Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests
  for round-trip, bulk amortization, nullable handling, and cancellation.

Refreshes middleware-param-transform.spec.md cross-references:

- Path depth bumped (../../ → ../../../) for the new specs/ subdirectory
  location.
- Adds back-reference to the umbrella spec.
- Updates the cipherstash-extension cross-reference to point at the new
  envelope-codec-extension.spec.md path.
- Annotates ADR 207 reference as forthcoming with PR #400.
wmadden added a commit that referenced this pull request May 1, 2026
… with high-level plan

Restructure the cipherstash-integration project from a single-spec layout
into a three-component umbrella:
- project-1/: searchable-encryption MVP (the previous umbrella spec
  rescoped as Project 1, plus its 5 task specs moved under specs/)
- project-2/: planner-driven DDL + expanded surface (new stub)
- sql-raw-factory/: public raw\`...\` template-literal factory
  (moved from projects/sql-raw-factory/)

New artifacts at the umbrella level:
- spec.md: scope of the umbrella, why three components, cross-component
  design decisions (RawSqlExpr ownership, DataTransformOperation choice,
  end-to-end-tested-only scope), in-flight framework dependency status
  (PRs #400/#402 merged 2026-05-01; #404/#409 still open).
- plan.md: component-level sequencing. Phase A is Project 1 (critical
  path, gated externally on #404 + #409); phase B is sql-raw-factory and
  Project 2 in parallel afterwards, each with its own gating story
  (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing;
  Project 2 blocks on Project 1 + TML-2338 + TML-2339).

Project 1's spec is reframed: drop "this is the umbrella" language, add
a header pointing to the new umbrella, repath all relative references
for the new directory depth (3-up from project-1/, 4-up from
project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously-
open question about Project 2's on-disk slug.

No content changes to the per-task specs beyond reference repaths.
wmadden pushed a commit that referenced this pull request May 1, 2026
…nsion

Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:

- Write side: users construct envelopes from plaintext
  (EncryptedString.from('me@example.com')); the extension's
  middleware walks ParamRefs in beforeExecute, batches all envelope
  plaintexts into one bulkEncrypt({ signal }) call, replaces values
  with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
  ciphertext + the column handle (table, column, KMS metadata)
  supplied by SqlCodecCallContext.column. Users call
  await row.email.decrypt() per cell, or use bulk-decrypt
  utilities (decryptAll, post-buffering) to amortize across many
  envelopes.

Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).

Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.

Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
wmadden added a commit that referenced this pull request May 1, 2026
…h middleware-param-transform cross-refs

Rewrites envelope-codec-extension.spec.md against the post-#402 codec API
and the locked Project 1 scope:

- Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema
  for { equality, freeTextSearch }, registered separately from the codec body
  itself (mirrors pgvector's plumbing for length).
- Constrains the column-type surface to EncryptedString only (3 argument
  shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber,
  and other types from scope — those land in Project 2 alongside their codec
  round-trip / search-operator / migration tests.
- Adds the EQL bundle install via databaseDependencies.init, sourced from
  the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for
  CREATE EXTENSION vector.
- Adds operator lowering for eq and ilike, deferring the canonical SQL
  function names to the first-attempt's operation-templates.ts.
- Native type is eql_v2_encrypted (not text), reflecting EQL's actual
  storage type.
- Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests
  for round-trip, bulk amortization, nullable handling, and cancellation.

Refreshes middleware-param-transform.spec.md cross-references:

- Path depth bumped (../../ → ../../../) for the new specs/ subdirectory
  location.
- Adds back-reference to the umbrella spec.
- Updates the cipherstash-extension cross-reference to point at the new
  envelope-codec-extension.spec.md path.
- Annotates ADR 207 reference as forthcoming with PR #400.
wmadden added a commit that referenced this pull request May 1, 2026
… with high-level plan

Restructure the cipherstash-integration project from a single-spec layout
into a three-component umbrella:
- project-1/: searchable-encryption MVP (the previous umbrella spec
  rescoped as Project 1, plus its 5 task specs moved under specs/)
- project-2/: planner-driven DDL + expanded surface (new stub)
- sql-raw-factory/: public raw\`...\` template-literal factory
  (moved from projects/sql-raw-factory/)

New artifacts at the umbrella level:
- spec.md: scope of the umbrella, why three components, cross-component
  design decisions (RawSqlExpr ownership, DataTransformOperation choice,
  end-to-end-tested-only scope), in-flight framework dependency status
  (PRs #400/#402 merged 2026-05-01; #404/#409 still open).
- plan.md: component-level sequencing. Phase A is Project 1 (critical
  path, gated externally on #404 + #409); phase B is sql-raw-factory and
  Project 2 in parallel afterwards, each with its own gating story
  (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing;
  Project 2 blocks on Project 1 + TML-2338 + TML-2339).

Project 1's spec is reframed: drop "this is the umbrella" language, add
a header pointing to the new umbrella, repath all relative references
for the new directory depth (3-up from project-1/, 4-up from
project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously-
open question about Project 2's on-disk slug.

No content changes to the per-task specs beyond reference repaths.
wmadden pushed a commit that referenced this pull request May 1, 2026
…nsion

Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:

- Write side: users construct envelopes from plaintext
  (EncryptedString.from('me@example.com')); the extension's
  middleware walks ParamRefs in beforeExecute, batches all envelope
  plaintexts into one bulkEncrypt({ signal }) call, replaces values
  with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
  ciphertext + the column handle (table, column, KMS metadata)
  supplied by SqlCodecCallContext.column. Users call
  await row.email.decrypt() per cell, or use bulk-decrypt
  utilities (decryptAll, post-buffering) to amortize across many
  envelopes.

Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).

Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.

Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
wmadden added a commit that referenced this pull request May 1, 2026
…h middleware-param-transform cross-refs

Rewrites envelope-codec-extension.spec.md against the post-#402 codec API
and the locked Project 1 scope:

- Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema
  for { equality, freeTextSearch }, registered separately from the codec body
  itself (mirrors pgvector's plumbing for length).
- Constrains the column-type surface to EncryptedString only (3 argument
  shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber,
  and other types from scope — those land in Project 2 alongside their codec
  round-trip / search-operator / migration tests.
- Adds the EQL bundle install via databaseDependencies.init, sourced from
  the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for
  CREATE EXTENSION vector.
- Adds operator lowering for eq and ilike, deferring the canonical SQL
  function names to the first-attempt's operation-templates.ts.
- Native type is eql_v2_encrypted (not text), reflecting EQL's actual
  storage type.
- Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests
  for round-trip, bulk amortization, nullable handling, and cancellation.

Refreshes middleware-param-transform.spec.md cross-references:

- Path depth bumped (../../ → ../../../) for the new specs/ subdirectory
  location.
- Adds back-reference to the umbrella spec.
- Updates the cipherstash-extension cross-reference to point at the new
  envelope-codec-extension.spec.md path.
- Annotates ADR 207 reference as forthcoming with PR #400.
wmadden added a commit that referenced this pull request May 1, 2026
… with high-level plan

Restructure the cipherstash-integration project from a single-spec layout
into a three-component umbrella:
- project-1/: searchable-encryption MVP (the previous umbrella spec
  rescoped as Project 1, plus its 5 task specs moved under specs/)
- project-2/: planner-driven DDL + expanded surface (new stub)
- sql-raw-factory/: public raw\`...\` template-literal factory
  (moved from projects/sql-raw-factory/)

New artifacts at the umbrella level:
- spec.md: scope of the umbrella, why three components, cross-component
  design decisions (RawSqlExpr ownership, DataTransformOperation choice,
  end-to-end-tested-only scope), in-flight framework dependency status
  (PRs #400/#402 merged 2026-05-01; #404/#409 still open).
- plan.md: component-level sequencing. Phase A is Project 1 (critical
  path, gated externally on #404 + #409); phase B is sql-raw-factory and
  Project 2 in parallel afterwards, each with its own gating story
  (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing;
  Project 2 blocks on Project 1 + TML-2338 + TML-2339).

Project 1's spec is reframed: drop "this is the umbrella" language, add
a header pointing to the new umbrella, repath all relative references
for the new directory depth (3-up from project-1/, 4-up from
project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously-
open question about Project 2's on-disk slug.

No content changes to the per-task specs beyond reference repaths.
wmadden pushed a commit that referenced this pull request May 4, 2026
…lback)

The SQL `codec()` and Mongo `mongoCodec()` factories declared `encode?:`
optional and installed an identity fallback `(value) => value as unknown
as TWire` when omitted. The cast is unsound — an author who declares
`TInput=number, TWire=string` and forgets `encode` silently produces a
runtime that returns numbers where the wire shape promised strings.

Audit confirms zero in-tree consumers of the identity fallback: every
existing codec definition (SQL core, postgres adapter, sqlite adapter,
mongo adapter, pgvector extension) declares an explicit `encode`.
Making `encode` required is therefore non-disruptive and closes the
type hole. `decode` was already required for the same reason.

The standalone `encode` identity fallback is gone; the
`encodeJson`/`decodeJson` identity defaults (gated on
`TInput extends JsonValue`) remain — those defaults are sound because
the conditional input constraint guarantees the contract artifact stays
JSON-safe.

Resolves CodeRabbit C7 on PR #400.
wmadden pushed a commit that referenced this pull request May 4, 2026
The author-facing two-arg shape on the SQL `codec()` and Mongo
`mongoCodec()` factories declared `ctx: SqlCodecCallContext` /
`ctx: CodecCallContext` (non-optional), but the runtime constructs
`codecCtx: SqlCodecCallContext | undefined = signal ? { signal } : undefined`
and forwards `undefined` when no signal was supplied. So an author
who writes `encode: (value, ctx: CodecCallContext) => …` was lying
about the type — they would receive `undefined` despite the
signature claiming non-optional.

Switch the two-arg arms of `encode` and `decode` to `ctx?:`, matching
the runtime contract end to end. The internal `CtxEncode` / `CtxDecode`
lift types already used `ctx?:` so the lift body is unchanged.

`mongoVectorCodec` (`packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts`)
and `pgvectorCodec` (`packages/3-extensions/pgvector/src/core/codecs.ts`)
are real-world parameterised-codec consumers that ride the two-arg
author surface, so the type/runtime mismatch is in tree today.

Resolves CodeRabbit C6 on PR #400.
wmadden pushed a commit that referenced this pull request May 4, 2026
…site

The user-facing boundary `runtime.execute(plan, options?)` keeps `options.signal?` optional, but every internal `ctx` parameter from there on is now non-optional. The runtime allocates one `CodecCallContext` per `execute()` and threads the same reference everywhere; the `signal` field inside the ctx may be `undefined` (when no signal was supplied) but the ctx object itself is always present.

Same treatment for the Mongo adapter `MongoAdapter.lower(plan, ctx)` and the lowering helpers `lowerFilter` / `lowerStage` / `lowerPipeline` / `resolveValue` — `codecs` and `ctx` are both non-optional internally; `MongoAdapter` constructs a default registry once at `createMongoAdapter()` and never threads `codecs?: undefined` through the lowering tree again.

Adds a `checkAborted(ctx, phase)` helper alongside `raceAgainstAbort` and replaces every `if (ctx.signal?.aborted) throw runtimeAborted(...)` precheck site with it (sql encoding/decoding entries, sql-runtime entry + between-rows, mongo resolve-value entry).

Resolves the user's review summary on PR #400.
wmadden pushed a commit that referenced this pull request May 4, 2026
…ng signature

Completes the deferred Change 1 from the PR #400 review. The previous attempt regressed `TInput` inference for `string | Date`-style codec authors; canonicalizing those authors on `Date` (previous commit) removed the only in-tree consumer that depended on the union-arity author shape, so the simpler single-signature form is now safe to land.

`codec()` and `mongoCodec()` config types now declare `encode: (value, ctx?) => …` and `decode: (wire, ctx?) => …` directly. Authors that ignore the second argument continue to compile via TypeScript bivariance for trailing optional parameters; the `as CtxEncode` widening cast in the factory body is no longer needed. Drops the corresponding "single ctx-bearing config signature" alternative from ADR 207 (the argument it made -- preserves TInput inference for `string | Date`-style authors -- is no longer in force). README copy in framework-components, sql-relational-core, and mongo-codec is reframed: the optional second `ctx` arg is just part of the codec contract; the union-of-arities mechanism is gone.
wmadden pushed a commit that referenced this pull request May 4, 2026
…-agnostic base / SqlCodecInstanceContext (Phase E TE.1-TE.5)

The framework `Ctx` interface in `packages/1-framework/1-core/framework-components/src/codec-types.ts` carried a SQL-shaped `usedAt: ReadonlyArray<{ table; column }>` field — a layering violation: framework-components cannot honestly reference SQL concepts (table/column). Rename to `CodecInstanceContext` (family-agnostic, `{ name }` only) and introduce `SqlCodecInstanceContext extends CodecInstanceContext` in `packages/2-sql/4-lanes/relational-core/src/ast/codec-types.ts` carrying `usedAt`. Mirrors PR #400 (TML-2330)'s already-merged `CodecCallContext` / `SqlCodecCallContext` family-extension precedent.

The descriptor `factory` slot stays on the family-agnostic base; the SQL runtime narrows to `SqlCodecInstanceContext` at the call site. Function-argument contravariance handles the narrow safely (a callee accepting the base accepts the SQL extension). The pgvector and arktype-json factories stay on `CodecInstanceContext` because they don't read `usedAt`.

Test fixtures match the type to what each test consumes — `SqlCodecInstanceContext` where `usedAt` is observed, `CodecInstanceContext` otherwise.

Phase E TE.1-TE.5 (codec-registry-unification project).
wmadden pushed a commit that referenced this pull request May 4, 2026
cfe1b6d

ADR 207 was claimed by `cfe1b6dee` ("Codec call context: per-query AbortSignal and column metadata", PR #400 / TML-2330) on a parallel branch since this project's ADR 207 landed in Phase D. Rename this project's ADR file to 208, update body self-references, and add a Context section citing PR #400's `CodecCallContext` / `SqlCodecCallContext` family-extension as the structural precedent for the `CodecInstanceContext` / `SqlCodecInstanceContext` split shipped in Phase E TE.1-TE.5.

Cross-references updated: subsystem docs (`Contract Emitter & Types`, `No-Emit Workflow`); package READMEs (`framework-components`, `pgvector`, `arktype-json`, `adapter-postgres`). Prose mentions of `Ctx` in those docs become `CodecInstanceContext`.

Phase E TE.6-TE.7 (codec-registry-unification project).
wmadden pushed a commit that referenced this pull request May 4, 2026
…ion (TML-2324)

Two other ADRs already claim 207 on main: "Codec call context: per-query AbortSignal and column metadata" (cfe1b6d, PR #400 / TML-2330) and "Family-instance capability views for the framework CLI" (36ea069). ADR 208 ("Higher-order codecs for parameterized types") is also taken. 209 is the next free slot.

- git mv the ADR file 207 -> 209
- update the heading inside the ADR
- update two source-code references (mongo-runtime.ts JSDoc, decoding.test.ts comment)

Other "ADR 207" matches in the repo (ADR 204 cross-family notes, sql-relational-core / mongo-codec / framework-components READMEs, framework-gaps doc) refer to the codec-call-context ADR 207, not ours; left alone.
wmadden pushed a commit that referenced this pull request May 4, 2026
…nsion

Records the project shape for @prisma-next/extension-cipherstash, the
first real consumer of TML-2330 (codec call context) + TML-2359
(middleware param transform). Uses the envelope-codec pattern:

- Write side: users construct envelopes from plaintext
  (EncryptedString.from('me@example.com')); the extension's
  middleware walks ParamRefs in beforeExecute, batches all envelope
  plaintexts into one bulkEncrypt({ signal }) call, replaces values
  with ciphertexts. Codec encode is identity by the time it runs.
- Read side: codec.decode constructs an envelope wrapping the
  ciphertext + the column handle (table, column, KMS metadata)
  supplied by SqlCodecCallContext.column. Users call
  await row.email.decrypt() per cell, or use bulk-decrypt
  utilities (decryptAll, post-buffering) to amortize across many
  envelopes.

Handle is internal to the extension package — no public TypeScript
surface. Stored on the envelope via private field / closure
(implementation detail).

Spec covers package layout, envelope classes, codec, bulk-encrypt
middleware, bulk-decrypt utility, end-to-end ACs, and documentation
expectations. Project's close-out includes migrating the
envelope-codec pattern doc into a durable docs/ location so future
network-backed bulk-amortizable extensions (Vault, AWS KMS, etc.)
have a canonical reference.

Direct dependencies: TML-2330 / PR #400 (must merge first), TML-2359
/ projects/middleware-param-transform/spec.md (must merge before
this project's middleware lands). The codec / envelope / bulk-decrypt
parts can land independently of TML-2359.
wmadden added a commit that referenced this pull request May 4, 2026
…h middleware-param-transform cross-refs

Rewrites envelope-codec-extension.spec.md against the post-#402 codec API
and the locked Project 1 scope:

- Codec uses RuntimeParameterizedCodecDescriptor<P> with arktype paramsSchema
  for { equality, freeTextSearch }, registered separately from the codec body
  itself (mirrors pgvector's plumbing for length).
- Constrains the column-type surface to EncryptedString only (3 argument
  shapes, nullable + non-nullable). Removes EncryptedJson, EncryptedNumber,
  and other types from scope — those land in Project 2 alongside their codec
  round-trip / search-operator / migration tests.
- Adds the EQL bundle install via databaseDependencies.init, sourced from
  the first-attempt repo's eql-bundle.ts. Same shape pgvector uses for
  CREATE EXTENSION vector.
- Adds operator lowering for eq and ilike, deferring the canonical SQL
  function names to the first-attempt's operation-templates.ts.
- Native type is eql_v2_encrypted (not text), reflecting EQL's actual
  storage type.
- Acceptance criteria expanded to cover end-to-end live-Postgres+EQL tests
  for round-trip, bulk amortization, nullable handling, and cancellation.

Refreshes middleware-param-transform.spec.md cross-references:

- Path depth bumped (../../ → ../../../) for the new specs/ subdirectory
  location.
- Adds back-reference to the umbrella spec.
- Updates the cipherstash-extension cross-reference to point at the new
  envelope-codec-extension.spec.md path.
- Annotates ADR 207 reference as forthcoming with PR #400.
wmadden added a commit that referenced this pull request May 4, 2026
… with high-level plan

Restructure the cipherstash-integration project from a single-spec layout
into a three-component umbrella:
- project-1/: searchable-encryption MVP (the previous umbrella spec
  rescoped as Project 1, plus its 5 task specs moved under specs/)
- project-2/: planner-driven DDL + expanded surface (new stub)
- sql-raw-factory/: public raw\`...\` template-literal factory
  (moved from projects/sql-raw-factory/)

New artifacts at the umbrella level:
- spec.md: scope of the umbrella, why three components, cross-component
  design decisions (RawSqlExpr ownership, DataTransformOperation choice,
  end-to-end-tested-only scope), in-flight framework dependency status
  (PRs #400/#402 merged 2026-05-01; #404/#409 still open).
- plan.md: component-level sequencing. Phase A is Project 1 (critical
  path, gated externally on #404 + #409); phase B is sql-raw-factory and
  Project 2 in parallel afterwards, each with its own gating story
  (sql-raw-factory blocks on Project 1's RawSqlExpr AST node landing;
  Project 2 blocks on Project 1 + TML-2338 + TML-2339).

Project 1's spec is reframed: drop "this is the umbrella" language, add
a header pointing to the new umbrella, repath all relative references
for the new directory depth (3-up from project-1/, 4-up from
project-1/specs/, 3-up from sql-raw-factory/). Resolve the previously-
open question about Project 2's on-disk slug.

No content changes to the per-task specs beyond reference repaths.
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.

2 participants