Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b0cb6bb
wip(m1): RawSqlExpr AST node + initial test scaffold
cursoragent May 1, 2026
05e93b9
feat(sql-runtime): handle raw-sql AST kind in decoding + lint switches
cursoragent May 1, 2026
620a473
feat(adapter-postgres): lower RawSqlExpr via interleaved render (AC-L…
cursoragent May 1, 2026
9d2253c
feat(sql-relational-core): add planFromAst helper for raw-SQL plans (…
cursoragent May 1, 2026
d5db73f
test(sql-runtime): async codec encode over RawSqlExpr-backed plan (AC…
cursoragent May 1, 2026
9425690
feat(framework-components): per-execute signal on RuntimeMiddlewareCo…
cursoragent May 1, 2026
3140114
feat(sql-runtime): SqlParamRefMutator seam + abort phases on beforeEx…
wmadden May 1, 2026
b66a414
feat(mongo-runtime): MongoMiddleware accepts MongoParamRefMutator
wmadden May 1, 2026
33a6e5a
test(sql-relational-core): SqlParamRefMutator type-level guards (AC-T…
wmadden May 1, 2026
e2b7de6
docs(project-1/plan): cross-reference TML-2376 Mongo mutator runtime …
wmadden May 1, 2026
2b2efbe
feat(extension-cipherstash): bootstrap package skeleton (M2.a AC-PKG1…
wmadden May 1, 2026
473032f
feat(extension-cipherstash): EncryptedString envelope + CipherstashSd…
wmadden May 1, 2026
fab1593
feat(extension-cipherstash): cipherstash/string codec + parameterized…
wmadden May 1, 2026
2d05b90
feat(extension-cipherstash): databaseDependencies.init EQL stub + con…
wmadden May 1, 2026
6bbbee2
refactor(extension-cipherstash): use ifDefined() helper in EncryptedS…
wmadden May 1, 2026
e03acf4
docs(extension-cipherstash): drop transient projects/ links; add DEVE…
wmadden May 1, 2026
0d558b1
docs(extension-cipherstash): drop transient projects/ links from stub…
wmadden May 1, 2026
584bbcd
feat(framework-components): boolean kind for AuthoringArgumentDescriptor
wmadden May 2, 2026
c38863d
feat(extension-cipherstash): cipherstash.EncryptedString PSL construc…
wmadden May 2, 2026
9cd526f
feat(extension-cipherstash): encryptedString({...}) TS contract factory
wmadden May 2, 2026
8ea4a1b
test(integration-tests): cipherstash-encrypted-string parity fixture …
wmadden May 2, 2026
75c2074
test(integration-tests): cipherstash dbInit DDL snapshot (M2.b AC-LOW…
wmadden May 2, 2026
2f7685d
test(extension-cipherstash): typecheck-clean PSL interpretation test …
wmadden May 2, 2026
c48d4d7
test(integration-tests): typecheck-clean cipherstash dbInit snapshot
wmadden May 2, 2026
cc99d50
docs(project-1/plan): cross-reference codec-SDK binding refactor defe…
wmadden May 4, 2026
eefdb1d
docs(project-1): refresh status + add HANDOVER.md for incoming driver
wmadden May 5, 2026
8ceebb5
docs(project-1): file TML-2388 + resolve M2.c design defaults
wmadden May 6, 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
42 changes: 42 additions & 0 deletions architecture.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,48 @@
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/core/**",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/middleware/**",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/index.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/column-types.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/control.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "migration"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/runtime.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/middleware.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
Comment on lines +460 to +488
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

Add architecture mapping for packages/3-extensions/cipherstash/src/exports/pack.ts.

The cipherstash pack export is part of the package surface but is not registered in this config block. That leaves dependency/plane checks incomplete for that entrypoint.

Proposed diff
     {
       "glob": "packages/3-extensions/cipherstash/src/exports/column-types.ts",
       "domain": "extensions",
       "layer": "adapters",
       "plane": "shared"
     },
+    {
+      "glob": "packages/3-extensions/cipherstash/src/exports/pack.ts",
+      "domain": "extensions",
+      "layer": "adapters",
+      "plane": "shared"
+    },
     {
       "glob": "packages/3-extensions/cipherstash/src/exports/control.ts",
       "domain": "extensions",
       "layer": "adapters",
       "plane": "migration"

As per coding guidelines, "Package domain/layer/plane configuration must include glob pattern, domain name, layer name, and plane designation for each package entry."

📝 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
"glob": "packages/3-extensions/cipherstash/src/exports/index.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/column-types.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/control.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "migration"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/runtime.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/middleware.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
"glob": "packages/3-extensions/cipherstash/src/exports/index.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/column-types.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/pack.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "shared"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/control.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "migration"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/runtime.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
{
"glob": "packages/3-extensions/cipherstash/src/exports/middleware.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@architecture.config.json` around lines 460 - 488, Add a new architecture
mapping object for the missing cipherstash pack export by inserting an entry
with "glob": "packages/3-extensions/cipherstash/src/exports/pack.ts", "domain":
"extensions", "layer": "adapters", and "plane": "runtime" into the same
array/block alongside the other cipherstash export entries so the pack entry is
registered for dependency/plane checks.

{
"glob": "packages/2-mongo-family/1-foundation/**",
"domain": "mongo",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import type { CodecCallContext } from '../shared/codec-types';
import type { RuntimeAbortedPhase } from './runtime-error';
import { runtimeAborted } from './runtime-error';

/**
* Throw a phase-tagged `RUNTIME.ABORTED` envelope if the supplied
* codec-call context is already aborted at the precheck site. Centralises
* the `if (ctx.signal?.aborted) throw runtimeAborted(...)` pattern that
* every codec dispatch site repeats.
* context is already aborted at the precheck site. Centralises the
* `if (ctx.signal?.aborted) throw runtimeAborted(...)` pattern that
* every codec dispatch site (and the `beforeExecute` middleware phase)
* repeats. Accepts both the framework `CodecCallContext` and the
* `RuntimeMiddlewareContext`; both expose `signal?: AbortSignal`.
*/
export function checkAborted(ctx: CodecCallContext, phase: RuntimeAbortedPhase): void {
export function checkAborted(
ctx: { readonly signal?: AbortSignal },
phase: RuntimeAbortedPhase,
): void {
if (ctx.signal?.aborted) {
throw runtimeAborted(phase, ctx.signal.reason);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { AsyncIterableResult } from './async-iterable-result';
import type { ExecutionPlan } from './query-plan';
import type { RuntimeMiddleware, RuntimeMiddlewareContext } from './runtime-middleware';
import { checkAborted, raceAgainstAbort } from './race-against-abort';
import type {
ParamRefMutator,
RuntimeMiddleware,
RuntimeMiddlewareContext,
} from './runtime-middleware';

/**
* Drives a single execution of `runDriver()` through the middleware lifecycle.
*
* Lifecycle, in order:
* 1. For each middleware in registration order: `beforeExecute(exec, ctx)`.
* 1. For each middleware in registration order: `beforeExecute(exec, ctx,
* paramsMutator)`. The mutator is the family-specific
* {@link ParamRefMutator} the caller passes through (`undefined` for
* plans / families with no mutator surface). Cooperative cancellation:
* before each middleware body, an already-aborted `ctx.signal` throws
* `RUNTIME.ABORTED { phase: 'beforeExecute' }`; mid-flight aborts race
* the body via `raceAgainstAbort` so the runtime returns
* `RUNTIME.ABORTED` promptly even when the middleware ignores the
* signal. Non-abort errors thrown by a middleware body pass through
* unchanged.
* 2. For each row yielded by `runDriver()`: for each middleware in registration
* order: `onRow(row, exec, ctx)`; then yield the row to the consumer.
* 3. On successful completion: for each middleware in registration order:
Expand All @@ -20,11 +34,16 @@ import type { RuntimeMiddleware, RuntimeMiddlewareContext } from './runtime-midd
* This helper is the single canonical implementation of the middleware
* orchestration loop; family runtimes should not reimplement it.
*/
export function runWithMiddleware<TExec extends ExecutionPlan, Row>(
export function runWithMiddleware<
TExec extends ExecutionPlan,
Row,
TMutator extends ParamRefMutator = ParamRefMutator,
>(
exec: TExec,
middleware: ReadonlyArray<RuntimeMiddleware<TExec>>,
middleware: ReadonlyArray<RuntimeMiddleware<TExec, TMutator>>,
ctx: RuntimeMiddlewareContext,
runDriver: () => AsyncIterable<Row>,
paramsMutator?: TMutator,
): AsyncIterableResult<Row> {
const iterator = async function* (): AsyncGenerator<Row, void, unknown> {
const startedAt = Date.now();
Expand All @@ -34,7 +53,22 @@ export function runWithMiddleware<TExec extends ExecutionPlan, Row>(
try {
for (const mw of middleware) {
if (mw.beforeExecute) {
await mw.beforeExecute(exec, ctx);
// Already-aborted at entry to this middleware short-circuits with
// a phase-tagged envelope before the body runs (AC-ABT2).
checkAborted(ctx, 'beforeExecute');
// The framework only forwards the mutator the caller supplied; a
// pass-through `undefined` for non-mutating families is safe — the
// base `RuntimeMiddleware` declares the third parameter, and
// existing `(plan, ctx)` bodies that ignore it stay unchanged.
// The cast below is the single point at which the framework's
// generic mutator slot meets the (possibly absent) caller value;
// `runWithMiddleware` cannot synthesize a TMutator instance.
const work = mw.beforeExecute(exec, ctx, paramsMutator as TMutator);
if (work !== undefined) {
// Mid-flight abort surfaces RUNTIME.ABORTED promptly even when
// the middleware body ignores ctx.signal (AC-ABT3).
await raceAgainstAbort(Promise.resolve(work), ctx.signal, 'beforeExecute');
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,19 @@ export abstract class RuntimeCore<

const compiled = await self.runBeforeCompile(plan);
const exec = await self.lower(compiled, codecCtx);
// Merge the per-execute signal onto the persistent middleware ctx
// for the duration of this execute() call. The ctx object itself is
// freshly allocated per-execute so middleware sees the signal that
// belongs to *its* invocation, not a shared one. Identity matches
// codecCtx.signal so middleware authors who compare `ctx.signal`
// across the codec/middleware boundary observe the same reference.
const execMiddlewareCtx = signal === undefined ? self.ctx : { ...self.ctx, signal };
Comment on lines +121 to +127
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

Strip any base ctx.signal before layering the per-execute one.

When options?.signal is absent, this branch reuses self.ctx verbatim. If the long-lived context already carries a signal, later executions inherit that stale signal and can abort against the wrong request. Build the per-execute middleware context from self.ctx with any existing signal removed first.

🛠️ Proposed fix
-      const execMiddlewareCtx = signal === undefined ? self.ctx : { ...self.ctx, signal };
+      const { signal: _baseSignal, ...baseMiddlewareCtx } = self.ctx;
+      const execMiddlewareCtx: RuntimeMiddlewareContext =
+        signal === undefined ? baseMiddlewareCtx : { ...baseMiddlewareCtx, signal };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/1-framework/1-core/framework-components/src/execution/runtime-core.ts`
around lines 121 - 127, The per-execute middleware context currently reuses
self.ctx when options?.signal is undefined, which can preserve a stale signal;
change the construction of execMiddlewareCtx in execute() so it always starts
from a copy of self.ctx with any existing signal property removed, then only add
the per-execute signal when provided (i.e., create a shallow copy of self.ctx,
delete its signal key, and if signal !== undefined assign that signal into the
new object before assigning to execMiddlewareCtx) to ensure no stale signal is
inherited.

// The driver yields raw `Record<string, unknown>`; we cast to `Row` here.
// The Row contract is enforced by the caller via `plan._row`.
yield* runWithMiddleware<TExec, Row>(
exec,
self.middleware,
self.ctx,
execMiddlewareCtx,
() => self.runDriver(exec) as AsyncIterable<Row>,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@ export interface RuntimeErrorEnvelope extends Error {
* - `'decode'` — abort fired during `decodeRow` / `decodeField`.
* - `'stream'` — abort fired between rows or before any codec call
* (already-aborted at entry).
* - `'beforeExecute'` / `'afterExecute'` / `'onRow'` — abort fired
* on entry to or during the corresponding middleware phase
* (cooperative cancellation per the param-transform seam).
*/
export const RUNTIME_ABORTED = 'RUNTIME.ABORTED' as const;

/** Discriminator placed in `details.phase` of a `RUNTIME.ABORTED` envelope. */
export type RuntimeAbortedPhase = 'encode' | 'decode' | 'stream';
export type RuntimeAbortedPhase =
| 'encode'
| 'decode'
| 'stream'
| 'beforeExecute'
| 'afterExecute'
| 'onRow';

/**
* Type guard for the runtime-error envelope produced by `runtimeError`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,31 @@ export interface RuntimeLog {
debug?(event: unknown): void;
}

/**
* Per-execute context threaded through every middleware phase
* (`beforeExecute`, `onRow`, `afterExecute`). Allocated once per
* `runtime.execute()` call and shared by reference across all
* middleware in the chain.
*
* - `signal` carries the per-query `AbortSignal` -- the same
* reference that `runtime.execute(plan, { signal })` was invoked
* with, and the same reference threaded into the per-call
* `CodecCallContext` (ADR 207). Middleware that wraps a
* network-backed SDK forwards `ctx.signal` into that SDK to
* propagate caller cancellation; pure-CPU middleware ignores it.
*
* Symmetric plumbing across all middleware phases (rather than only
* `beforeExecute`) is a deliberate choice: a middleware that wraps a
* downstream observability hook or post-processor in `afterExecute` /
* `onRow` needs the same cancellation reach as its `beforeExecute`
* counterpart.
*/
export interface RuntimeMiddlewareContext {
readonly contract: unknown;
readonly mode: 'strict' | 'permissive';
readonly now: () => number;
readonly log: RuntimeLog;
readonly signal?: AbortSignal;
}

export interface AfterExecuteResult {
Expand All @@ -22,19 +42,45 @@ export interface AfterExecuteResult {
readonly completed: boolean;
}

/**
* Marker interface for family-specific param-ref mutators threaded into
* `beforeExecute` as the third argument. The framework treats the mutator
* opaquely — it allocates and forwards the family's mutator instance so
* `runWithMiddleware` can stay family-agnostic. SQL extends this with
* `SqlParamRefMutator` (over `ParamRef`); Mongo extends with
* `MongoParamRefMutator` (over `MongoParamRef`).
*
* Extension authors target the family-specific mutator type, not this
* marker.
*/
export type ParamRefMutator = {};

/**
* Family-agnostic middleware SPI parameterized over the plan marker.
*
* `TPlan` defaults to the framework `QueryPlan` marker so a generic
* middleware (e.g. cross-family telemetry) can be authored without
* naming a family. Family-specific middleware (`SqlMiddleware`,
* `MongoMiddleware`) narrow `TPlan` to their concrete plan type.
*
* `TMutator` is the family-specific {@link ParamRefMutator} the runtime
* threads into `beforeExecute(plan, ctx, params)` as a third argument.
* Existing `(plan)` / `(plan, ctx)` middleware bodies continue to compile
* — TypeScript permits assigning a function with fewer parameters to a
* function-typed slot that declares more. The third arg is additive.
*/
export interface RuntimeMiddleware<TPlan extends QueryPlan = QueryPlan> {
export interface RuntimeMiddleware<
TPlan extends QueryPlan = QueryPlan,
TMutator extends ParamRefMutator = ParamRefMutator,
> {
readonly name: string;
readonly familyId?: string;
readonly targetId?: string;
beforeExecute?(plan: TPlan, ctx: RuntimeMiddlewareContext): Promise<void>;
beforeExecute?(
plan: TPlan,
ctx: RuntimeMiddlewareContext,
params?: TMutator,
): void | Promise<void>;
onRow?(row: Record<string, unknown>, plan: TPlan, ctx: RuntimeMiddlewareContext): Promise<void>;
afterExecute?(
plan: TPlan,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
} from '../execution/runtime-error';
export type {
AfterExecuteResult,
ParamRefMutator,
RuntimeExecuteOptions,
RuntimeExecutor,
RuntimeLog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface AuthoringArgumentDescriptorCommon {
export type AuthoringArgumentDescriptor = AuthoringArgumentDescriptorCommon &
(
| { readonly kind: 'string' }
| { readonly kind: 'boolean' }
| {
readonly kind: 'number';
readonly integer?: boolean;
Expand Down Expand Up @@ -188,6 +189,13 @@ function validateAuthoringArgument(
return;
}

if (descriptor.kind === 'boolean') {
if (typeof value !== 'boolean') {
throw new Error(`Authoring helper argument at ${path} must be a boolean`);
}
return;
}

if (descriptor.kind === 'stringArray') {
if (!Array.isArray(value)) {
throw new Error(`Authoring helper argument at ${path} must be an array of strings`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,49 @@ describe('authoring template resolution', () => {
properties: {
label: { kind: 'string' },
length: { kind: 'number', integer: true, minimum: 1, maximum: 3 },
enabled: { kind: 'boolean', optional: true },
},
},
{ kind: 'number', optional: true, minimum: 0 },
],
['vector', ['a', 'b'], { label: 'embedding', length: 2 }, 0],
['vector', ['a', 'b'], { label: 'embedding', length: 2, enabled: true }, 0],
),
).not.toThrow();
});

it('accepts boolean leaf arguments at the top level', () => {
expect(() =>
validateAuthoringHelperArguments(
'type.flag',
[{ kind: 'boolean' }, { kind: 'boolean', optional: true }],
[true, false],
),
).not.toThrow();
expect(() =>
validateAuthoringHelperArguments(
'type.flag',
[{ kind: 'boolean' }, { kind: 'boolean', optional: true }],
[false],
),
).not.toThrow();
});

it('rejects non-boolean values where boolean is expected', () => {
expect(() =>
validateAuthoringHelperArguments('type.flag', [{ kind: 'boolean' }], ['yes']),
).toThrow(/must be a boolean/);
expect(() => validateAuthoringHelperArguments('type.flag', [{ kind: 'boolean' }], [1])).toThrow(
/must be a boolean/,
);
expect(() =>
validateAuthoringHelperArguments(
'type.flag',
[{ kind: 'object', properties: { equality: { kind: 'boolean' } } }],
[{ equality: 'yes' }],
),
).toThrow(/type\.flag\[0\]\.equality must be a boolean/);
});

it('allows omitted optional helper arguments', () => {
expect(() =>
validateAuthoringHelperArguments(
Expand Down
Loading
Loading