Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
89ba2ed
test(postgres): cover target-owned built-in query operations
wmadden-electric Sep 18, 2026
92b1167
feat(postgres): move built-in query operations into the target pack
wmadden-electric Sep 18, 2026
0ef894a
docs(adr): record target-owned built-in query operations as ADR 253
wmadden-electric Sep 18, 2026
559c835
refactor(publish-surface): point query-operation type imports at the …
wmadden-electric Sep 18, 2026
b403454
chore(fixtures): regenerate emitted contract types for the target ope…
wmadden-electric Sep 18, 2026
8887cbd
test(integration): exercise Postgres full-text search through the SQL…
wmadden-electric Sep 18, 2026
1e121a6
test(integration): cover full-text search through the ORM and at the …
wmadden-electric Sep 18, 2026
bbda998
test(postgres): prove the GIN to_tsvector index plans and renders
wmadden-electric Sep 18, 2026
d8e9082
fix(test): narrow the interpreted contract to SQL storage in the inde…
wmadden-electric Sep 18, 2026
1e7fc7c
chore(fixtures): point two hand-maintained contract fixtures at the t…
wmadden-electric Sep 18, 2026
d33d2c4
refactor(postgres): give the text-search language allowlist its own m…
wmadden-electric Sep 18, 2026
6b66e80
docs(postgres): document full-text search and the moved operation types
wmadden-electric Sep 18, 2026
fca80fd
docs: name the builder lane db.sql and the target as an operations co…
wmadden-electric Sep 18, 2026
50aa48f
feat(authoring): a contributed model attribute may lower to an index
wmadden-electric Sep 21, 2026
23b8a51
feat(postgres): @@fullTextIndex renders the index the operations need
wmadden-electric Sep 21, 2026
803b96b
test(postgres): plan and emit @@fullTextIndex end to end
wmadden-electric Sep 21, 2026
4c8f116
docs(postgres): lead with @@fullTextIndex, keep the raw expression as…
wmadden-electric Sep 21, 2026
25f2d55
test(integration): record @@fullTextIndex in the model-attribute surf…
wmadden-electric Sep 21, 2026
6cc9f67
fix(authoring): report a malformed contributed index as a pack contri…
wmadden-electric Sep 21, 2026
780e1b4
feat(contract-ts): an index expression may be rendered at lowering
wmadden-electric Sep 21, 2026
d07dbe1
fix(test): type the facade parity test against the shapes both surfac…
wmadden-electric Sep 21, 2026
23abe73
refactor(postgres): one default language, and assertions that name th…
wmadden-electric Sep 21, 2026
3277607
refactor(postgres): assert the resolved column name instead of defaul…
wmadden-electric Sep 21, 2026
2b9cb0c
test(integration): prove Postgres uses the full-text index for the SQ…
wmadden-electric Sep 21, 2026
a0f4bc6
test(integration): record the cast Postgres stores in the varchar index
wmadden-electric Sep 21, 2026
5d3e474
docs(postgres): state the real index-matching rule, and the silent la…
wmadden-electric Sep 21, 2026
c1f14bb
test(postgres): pin the options surface for the full-text operations
wmadden-electric Sep 21, 2026
5150fad
feat(postgres): the full-text operations take an options object
wmadden-electric Sep 21, 2026
8a86f96
feat(postgres): @@fullTextIndex and the TS helper take a where predicate
wmadden-electric Sep 21, 2026
c74e3a4
test(integration): name the storage column in the partial-index predi…
wmadden-electric Sep 21, 2026
4c0cdcd
docs(postgres): document the operation options and the partial index
wmadden-electric Sep 21, 2026
535dff2
test(integration): build the partial index from the contract, like ev…
wmadden-electric Sep 21, 2026
0ffd733
Merge origin/main into the Postgres full-text search branch
wmadden-electric Sep 21, 2026
1de810f
docs(adr): renumber the target-owned query operations ADR to 255
wmadden-electric Sep 21, 2026
22c70c0
fix(postgres): tighten what the full-text options and @@fullTextIndex…
wmadden-electric Sep 21, 2026
7220bfe
fix(postgres): close the PR review threads on the full-text surface
wmadden-electric Sep 22, 2026
8a78862
docs(adr): name the target as the owner of ilike in ADR 203 and recon…
wmadden-electric Sep 22, 2026
375bbc9
Merge origin/main into the Postgres full-text search branch
wmadden-electric Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/telemetry-backend/src/prisma/contract.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Context

Adapters and extensions register query operations (e.g., `ilike`, `cosineDistance`) that attach to fields of a given shape. Until now an operation declared its `self` argument as a specific `codecId` β€” `pgvector/vector@1` for `cosineDistance`, for example. The type and runtime machinery then surfaced that operation on every field whose codec matched that ID.
Adapters and extensions register query operations (e.g., `ilike`, `cosineDistance`) that attach to fields of a given shape. (Per [ADR 206](ADR%20206%20-%20Operations%20as%20TypeScript%20functions.md)'s amendment, a target registers its built-in operations too β€” `ilike` is contributed by the Postgres target, not its adapter.) Until now an operation declared its `self` argument as a specific `codecId` β€” `pgvector/vector@1` for `cosineDistance`, for example. The type and runtime machinery then surfaced that operation on every field whose codec matched that ID.

Codec-ID targeting works when an operation is tied to one concrete codec. It breaks down when an operation is defined by a *capability* that multiple codecs share:

Expand All @@ -21,10 +21,10 @@ Extend operation argument specs with an optional `traits` field alongside `codec

## Grounding example

Adapter-provided operations declare their arguments as trait-targeted where appropriate. The Postgres `ilike` descriptor names no specific textual codec β€” just the `textual` trait on its self argument:
Operations contributed by a target, adapter or extension declare their arguments as trait-targeted where appropriate. The Postgres target's `ilike` descriptor names no specific textual codec β€” just the `textual` trait on its self argument:

```typescript
// Adapter descriptor (runtime shape)
// Postgres target descriptor (runtime shape)
{
method: 'ilike',
args: [
Expand Down Expand Up @@ -52,15 +52,15 @@ readonly ilike: {
};
```

From this single descriptor, every textual field in the contract gains the operation. A non-textual field does not. An adapter that does not register the operation never exposes it, regardless of what codecs are on the target.
From this single descriptor, every textual field in the contract gains the operation. A non-textual field does not. A target, adapter or extension that does not register the operation never exposes it, regardless of what codecs are on the target.

## Design principles

1. **Codec identity and codec capability are independent axes.** An operation should be free to target either. Forcing capability-based operations into codec-ID enumerations duplicates what traits already encode on codecs and prevents extensibility.
2. **Traits are a closed union at the type level, an open set at runtime.** `CodecTrait` is a union so type matching stays structural and narrowing works; `ParamSpec.traits` is `readonly string[]` at runtime so registries can carry forward trait values they don't statically know about.
3. **Return targeting stays exact.** Predicate detection and result decoding depend on knowing the concrete return codec. `ReturnSpec` enforces this separately, so loosening `ParamSpec.codecId` to optional does not weaken return-type guarantees.
4. **Registration does the expansion work.** Operation lookup is on the hot path β€” it happens for every field access on an ORM model accessor. Trait resolution runs once per operation at registration time so field access remains a single map lookup.
5. **Same descriptor, different reachability per contract.** Contracts see different operation sets because their adapters register different operations β€” not because core branches on target. This preserves the thin-core discipline from [ADR 005](ADR%20005%20-%20Thin%20Core%20Fat%20Targets.md).
5. **Same descriptor, different reachability per contract.** Contracts see different operation sets because their targets, adapters and extensions register different operations β€” not because core branches on target. This preserves the thin-core discipline from [ADR 005](ADR%20005%20-%20Thin%20Core%20Fat%20Targets.md).

## How matching works

Expand Down Expand Up @@ -89,7 +89,7 @@ The ORM client surfaces this distinction by returning an `AnyExpression` (compos
## Interaction with other subsystems

- **Codec registry** ([ADR 030](ADR%20030%20-%20Result%20decoding%20%26%20codecs%20registry.md)). Traits are codec metadata, owned by the codec registry. Operation matching reads traits from there; no new source of truth is introduced.
- **Adapter SPI** ([ADR 016](ADR%20016%20-%20Adapter%20SPI%20for%20Lowering.md)). Adapter runtime descriptors expose `queryOperations()` alongside the existing lowering surface. Contract emission picks up `types.queryOperationTypes` from descriptor meta, and trait-targeted operations flow through the same pipeline as codec-ID operations.
- **Adapter SPI** ([ADR 016](ADR%20016%20-%20Adapter%20SPI%20for%20Lowering.md)). Target, adapter and extension runtime descriptors expose `queryOperations()`; for adapters it sits alongside the existing lowering surface. Contract emission picks up `types.queryOperationTypes` from descriptor meta, and trait-targeted operations flow through the same pipeline as codec-ID operations.
- **Contract extension encoding** ([ADR 105](ADR%20105%20-%20Contract%20extension%20encoding.md)) and [ADR 106](ADR%20106%20-%20Canonicalization%20for%20extensions.md). Trait-targeted argument specs serialize with `traits` as a string at the type level and as a string array at runtime. Canonicalization treats the `traits` field as opaque to the extension owner.
- **Extension compatibility** ([ADR 017](ADR%20017%20-%20Extension%20Compatibility%20Policy.md)). Trait-targeting is additive. Existing codec-ID-targeted operations continue to work without changes.

Expand All @@ -104,8 +104,8 @@ The ORM client surfaces this distinction by returning an `AnyExpression` (compos

### Positive

- Adapter-specific operations that apply to a capability (rather than one codec) can be registered once and automatically attach to every matching codec β€” including codecs added later by downstream extensions.
- Operations are reachable only on contracts whose adapter registered them. Capability-based operators no longer need runtime guards to reject calls on unsupported targets; the operation is absent from the type surface.
- Target-, adapter- or extension-specific operations that apply to a capability (rather than one codec) can be registered once and automatically attach to every matching codec β€” including codecs added later by downstream extensions.
- Operations are reachable only on contracts whose target, adapter or extension registered them. Capability-based operators no longer need runtime guards to reject calls on unsupported targets; the operation is absent from the type surface.
- Boolean-returning extension operations are predicates by construction, composable with `and`/`or`/`not` without special-casing per operation.

### Trade-offs
Expand Down Expand Up @@ -137,7 +137,7 @@ Doubles the surface β€” adapters, runtime registries, and type-level matchers al
## Open questions

- Whether trait-targeted arguments should support OR-composition of required traits. Today `traits` is a single string at the type level and a conjunction at runtime. A more expressive combinator can be added later without breaking the current shape.
- How the emitter pipeline should validate that every adapter-declared `QueryOperationTypes` entry has a corresponding runtime descriptor registered. The two are wired through descriptor meta but not cross-checked at emission time.
- How the emitter pipeline should validate that every contributor-declared `QueryOperationTypes` entry has a corresponding runtime descriptor registered. The two are wired through descriptor meta but not cross-checked at emission time.

## Decision record

Expand Down
Loading
Loading