cocottetech/@platform/codebase/@packages/surface-adapter-contracts/src/action.ts

71 lines
2.9 KiB
TypeScript

/**
* SurfaceAdapterAction — the canonical contract every adapter action implements
* (Layer 6 of `_engineering-surface-adapter-container.md`).
*
* Deltas from the Layer-6 doc sketch, made deliberately and authoritative here:
* - `schema` is the INPUT schema only (a `z.ZodType<I>`), not `{input, output}`.
* Output validation is the action's own concern; the dispatcher validates
* input at the boundary.
* - `rollback?(input, ctx)` takes the original INPUT (not the output) so a
* rollback can be reconstructed from the request alone.
* - `surface` is NOT on the action — within one specialist the surface is fixed.
* It lives on {@link ActionDescriptor} instead.
*/
import type { z } from 'zod';
import type { ActionVerb } from './action-verb.js';
import type { AdapterContext } from './context.js';
/** One declined deterministic gate (brief K). `gate` is the rule label, e.g. `'K1'`, `'K3c-1'`. */
export interface GateRejection {
/** Stable rule label from brief K (e.g. `'K1'`, `'K3c-1'`, `'K3f-2'`, `'K3f-3'`). */
gate: string;
/** Human-readable, user-surfaceable reason the gate fired. */
reason: string;
}
/**
* Result of running an action's deterministic eligibility gates.
* `ok === true` iff `rejections` is empty. When not ok, the dispatcher declines
* the action WITHOUT calling `execute` (no container spin-up) and surfaces the
* rejections to chat per brief K3k.
*/
export interface PrecheckResult {
ok: boolean;
rejections: GateRejection[];
}
/**
* A self-contained adapter action over a single surface.
*
* @typeParam I - validated input shape (parsed from {@link SurfaceAdapterAction.schema}).
* @typeParam O - execution result shape.
*/
export interface SurfaceAdapterAction<I, O> {
/** Which verb this action implements. The registry indexes by this. */
readonly action: ActionVerb;
/** Zod schema validating the input `I` at the dispatch boundary. */
readonly schema: z.ZodType<I>;
/**
* Deterministic eligibility gates (blocklist, identity, location, jurisdiction
* per brief K). MUST be pure w.r.t. side effects beyond reads via `ctx`. If the
* result is not `ok`, `execute` is never called.
*/
precheck(input: I, ctx: AdapterContext): Promise<PrecheckResult>;
/**
* Performs the action against the live surface session, then writes an
* `agent_actions` audit row via `ctx.agentActions`. Only invoked when
* `precheck` returned `ok`.
*/
execute(input: I, ctx: AdapterContext): Promise<O>;
/**
* Optionally undoes a previously-executed action (e.g. delete a post, remove a
* bump) reconstructed from the original input. Absent for irreversible actions.
*/
rollback?(input: I, ctx: AdapterContext): Promise<void>;
}
/** Convenience helper to build an ok / not-ok {@link PrecheckResult}. */
export function precheckResult(rejections: GateRejection[]): PrecheckResult {
return { ok: rejections.length === 0, rejections };
}