@sema-agent/core
Version:
Stateless, task-oriented AI agent core
270 lines (269 loc) • 17.1 kB
TypeScript
/**
* The TIGHTEN-ONLY SEATS of a TaskSpec — the ONE table that says which seats only tighten, which value of each is the
* restricting one, how two declarations of a seat compose, and which carrier hands it to an engine-built child. Every
* reader reads the table: the public merge ({@link import("./tighten-task-spec.js").tightenTaskSpec}), the child-spec
* carrier ({@link childClampSeats}), the run's trusted ctx seats and the workflow mount (one {@link ChildClampSource}
* minted per prepare), and a revival's fold of the waker's restrictions ({@link clampValuesOf}). A row added here
* reaches all of them; a reader that spells a seat by hand is named by `scripts/verify-child-clamp-seats.mjs`.
*
* Two mechanical halves keep it whole:
* · the COMPILER — {@link ChildClampSource} requires every clamp-carrier row as a PRESENT key, so a site that builds a
* source by hand cannot drop a row;
* · the SCAN — the gate reads this table literally, requires each registered constructor to go through the carrier,
* requires the published carriers (`ToolExecuteContext`, `ParentSpecSeat`) to declare exactly the rows they carry,
* and names any hand-written restricting projection outside the carriers.
*
* "Tighten-only" here: the value that travels is the RESTRICTING one and only that one, so a parent that clamped nothing
* changes nothing about its children, and nothing a child can author lifts a clamp its parent ran under.
*/
import { readDenyEntryProblem, type ReadDenyEntry } from "../tools/fs/read-deny.js";
import { complianceCapabilityProblem, type ComplianceCapability } from "./compliance.js";
import type { TaskSpec } from "./types.js";
import { writeProtectedEntryIdentity, writeProtectedEntryProblem } from "./write-protect.js";
/** Which carrier hands a row to an engine-built child: the child-spec carrier ({@link childClampSeats}, ctx seats of the
* same names) or the parent spec seat (`ParentSpecSeat`, applied by `applyParentSpecSeat`). */
export type ClampCarrier = "clamp" | "specSeat";
/** The identity an add-only list folds by — case-insensitive unless the entry says otherwise (the deny matcher's reading). */
declare function readDenyIdentity(entry: ReadDenyEntry): string;
/** A capability is its own identity (a closed word). */
declare function complianceIdentity(cap: ComplianceCapability): string;
/**
* How a governed workflow SCRIPT's untrusted list for an add-only row is read before the row's `entry` law judges it —
* the untrusted layer is bounded where the deployment and task layers are not. `atMost` is refused before any element is
* read (`why` says what each entry costs); `entry` reads ONE element, each of its properties exactly once, rebuilds it
* from the keys it recognizes (an element's extra own keys never ride along) and throws, naming `at`, on a shape it
* refuses. `shape` names the list's element grammar for the not-a-list refusal.
*/
export interface ScriptListLaw<E> {
readonly atMost: number;
readonly shape: string;
readonly why: string;
readonly entry: (el: unknown, at: string) => E;
}
/**
* THE CLAMP TABLE. Two laws:
* · `restricting` — the seat has ONE restricting `value`. Either side carrying it wins; a side that carries a different
* value cannot replace it (the public merge refuses that as a loosening); a seat with nothing restricting keeps what was
* authored.
* · `addOnly` — the seat is a list that only grows: every two of its lists meet in {@link unionAddOnly} (in order, folded
* by `identity`; `[]` erases nothing), and the run's effective list is that union taken with the deployment's own list
* (`deploymentLayer`, the `RunnerDeps` member it arrives through) — so the task layer can add entries and never remove
* one. `entry` is the seat's own entry law (its compiler's, never a copy); the task layer's lists meet it at the one
* door ({@link assertAddOnlySeats}), and an entry it refuses never folds into another. `script` bounds a governed
* workflow script's untrusted list for the seat before `entry` judges it.
* `script` also says whether a governed workflow SCRIPT may declare the seat for its child, under the row's law: a
* restricting row with `script: true` only as its restricting value (`false` = inherited only); an add-only row through its
* `script` list law, then `entry`. The script whitelist reads its tighten-only keys off this table ({@link SCRIPT_SEATS}),
* never off a list of its own.
* Rows are literals (the gate reads them as written).
*/
export declare const CLAMP_SET: Readonly<{
handsReadOnly: Readonly<{
readonly law: "restricting";
readonly value: true;
readonly script: true;
readonly carrier: "clamp";
}>;
interactiveTools: Readonly<{
readonly law: "restricting";
readonly value: false;
readonly script: true;
readonly carrier: "clamp";
}>;
approvalReach: Readonly<{
readonly law: "restricting";
readonly value: "none";
readonly script: true;
readonly carrier: "clamp";
}>;
readFace: Readonly<{
readonly law: "restricting";
readonly value: "roots";
readonly script: true;
readonly carrier: "clamp";
}>;
writeFace: Readonly<{
readonly law: "restricting";
readonly value: "roots";
readonly script: true;
readonly carrier: "clamp";
}>;
readDenyPatterns: Readonly<{
readonly law: "addOnly";
readonly identity: typeof readDenyIdentity;
readonly entry: typeof readDenyEntryProblem;
readonly script: ScriptListLaw<ReadDenyEntry>;
readonly deploymentLayer: "readDenyPatterns";
readonly carrier: "clamp";
}>;
writeProtectedAdditions: Readonly<{
readonly law: "addOnly";
readonly identity: typeof writeProtectedEntryIdentity;
readonly entry: typeof writeProtectedEntryProblem;
readonly script: ScriptListLaw<import("./write-protect.js").WriteProtectedEntry>;
readonly deploymentLayer: "writeProtectedPaths";
readonly carrier: "clamp";
}>;
complianceAdditionalDenies: Readonly<{
readonly law: "addOnly";
readonly identity: typeof complianceIdentity;
readonly entry: typeof complianceCapabilityProblem;
readonly script: ScriptListLaw<ComplianceCapability>;
readonly deploymentLayer: "compliancePostureResolver";
readonly carrier: "clamp";
}>;
oneShot: Readonly<{
readonly law: "restricting";
readonly value: true;
readonly script: true;
readonly carrier: "specSeat";
}>;
memoryPersistenceCapable: Readonly<{
readonly law: "restricting";
readonly value: false;
readonly script: true;
readonly carrier: "specSeat";
}>;
fileHistory: Readonly<{
readonly law: "restricting";
readonly value: "disabled";
readonly script: false;
readonly carrier: "specSeat";
}>;
promptForm: Readonly<{
readonly law: "restricting";
readonly value: "minimal";
readonly script: false;
readonly carrier: "specSeat";
}>;
}>;
/** One row's seat name. */
export type ClampSeat = keyof typeof CLAMP_SET;
/** The add-only rows' seats. */
export type AddOnlySeat = {
[K in ClampSeat]: (typeof CLAMP_SET)[K]["law"] extends "addOnly" ? K : never;
}[ClampSeat];
/** One entry of an add-only seat's list. */
export type AddOnlyEntry<S extends AddOnlySeat> = NonNullable<TaskSpec[S]> extends ReadonlyArray<infer E> ? E : never;
/** The seats the child-spec carrier hands on. */
export type ChildClampSeat = {
[K in ClampSeat]: (typeof CLAMP_SET)[K]["carrier"] extends "clamp" ? K : never;
}[ClampSeat];
/** Every row's value as some record carries it — present keys, `undefined` where the record has none. */
export type ClampSource = {
[K in ClampSeat]: TaskSpec[K] | undefined;
};
/** The clamp source a construction site hands the carrier: EVERY clamp-carrier row as a PRESENT key. */
export type ChildClampSource = Pick<ClampSource, ChildClampSeat>;
/** Only the restricting values (add-only rows: a non-empty list) — the shape a waker's clamp seat and a projection take. */
export type ClampValues = {
readonly [K in ClampSeat]?: (typeof CLAMP_SET)[K] extends {
readonly value: infer V;
} ? V : NonNullable<TaskSpec[K]>;
};
/** Every row's seat name, in table order. */
export declare const CLAMP_SEATS: readonly ClampSeat[];
/** The rows a governed workflow script may declare for its child (every row whose `script` is not `false`). */
export type ScriptSeat = {
[K in ClampSeat]: (typeof CLAMP_SET)[K]["script"] extends false ? never : K;
}[ClampSeat];
/** {@link ScriptSeat}, in table order — the script whitelist's tighten-only half. */
export declare const SCRIPT_SEATS: readonly ScriptSeat[];
/** Two declarations of one seat under its row's law (`b` is the later one): the restricting value if either carries it,
* else what was authored (`b` over `a`); an add-only list is the union of both, in order, by the row's identity. */
export declare function composeClampSeat(seat: ClampSeat, a: unknown, b: unknown): unknown;
/**
* THE union of an add-only seat's lists — the ONE merge every add-only row reads, wherever two of its lists meet: two
* declarations of the seat (the public merge, a fold of records) and the run's EFFECTIVE list (the deployment's list
* first, then every task-layer list). In order; a lawful entry folds into an earlier one with the same `identity`; `[]`
* and absent add nothing, and nothing here removes an entry — so a later layer can only add. An entry the row's entry
* law refuses is kept as it is, never folded into a lawful one: it reaches the door or the compiler that refuses it
* instead of vanishing behind a namesake. A list that is not a list refuses here (it would be read letter by letter).
*/
export declare function unionAddOnly<S extends AddOnlySeat>(seat: S, ...lists: ReadonlyArray<ReadonlyArray<AddOnlyEntry<S>> | undefined>): AddOnlyEntry<S>[];
/**
* THE task layer's door for the add-only rows: every add-only seat a spec carries is a list of entries its row's entry
* law accepts, or the prepare refuses — one code (`config.invalid_argument`, the table's own refusal), one sentence for a
* value that is not a list, and the seat's own entry law (labelled `TaskSpec.<seat>`) for a bad entry. Runs before any
* list is folded or compiled, so a task-layer value is judged as the caller wrote it. Absent ⇒ nothing to judge.
*/
export declare function assertAddOnlySeats(spec: object): void;
/** Would `override` LOOSEN `base` on this seat? Only a restricting row can be loosened: a base carrying the restricting
* value meeting an override that states a different one. */
export declare function loosensClampSeat(seat: ClampSeat, base: unknown, override: unknown): boolean;
/** Read every row (`carrier`: that carrier's rows only) off a record that carries the seats under their own names (a spec, a ctx, a seat). */
export declare function clampSourceOf(record: object | undefined): ClampSource;
export declare function clampSourceOf(record: object | undefined, carrier: "clamp"): ChildClampSource;
/** Fold several records' declarations of every row, in order, under each row's law — "either side restricting wins",
* add-only lists unioned. The fold of a spawn snapshot and its waker, of a workflow child's own spec and its host's
* clamps, of a ctx and its spec seat. */
export declare function foldClampSources(...records: ReadonlyArray<object | undefined>): ClampSource;
/** The restricting values of every row (`carrier` narrows it to one carrier's rows): a row with nothing restricting adds
* NO KEY, so the result spreads onto a spec without ever writing a loosening value. */
export declare function clampSeatsOf(source: Partial<Record<ClampSeat, unknown>>, carrier?: ClampCarrier): ClampValues;
/** The coded refusal of a clamp record a caller handed in (`config.invalid_argument`). */
export declare function clampRecordError(message: string): Error & {
code: "config.invalid_argument";
};
/**
* Refuse a caller-supplied clamp record that names a seat the table does not have (or, with `carrier`, a row another
* carrier hands on): a name nobody reads would sit on the record as if wired — the silent drop the retired-key door
* exists to prevent. `extra` names the record's own members that are not rows. Absent ⇒ nothing to judge; a member
* whose value is `undefined` carries nothing and is read as absent (the retired-key door's presence rule).
*/
export declare function assertClampRecord(record: unknown, owner: string, carrier?: ClampCarrier, extra?: readonly string[]): void;
/** The restricting values of every row that ANY of `records` carries — the clamp seat a waker hands a revival. */
export declare function clampValuesOf(...records: ReadonlyArray<object | undefined>): ClampValues;
/**
* Project a parent's clamps onto a child spec — the clamp-carrier rows of the table, the ONE place a source becomes the
* seats a child spec carries. Spread the result onto the child spec; a seat with nothing to clamp contributes NO KEY, so
* a constructor may state its own stricter value above the spread and keep it (a `false` from the parent can never lift it).
*
* @contract child_spec.clamps_travel_whole — every child spec the ENGINE builds carries the whole of its parent's
* tighten-only clamp seats (the `CLAMP_SET` rows whose carrier is `"clamp"`) through this one carrier: only the
* restricting value of each seat travels, arrays are copied at spawn, and a seat the parent did not clamp adds no key —
* so a clamp can never end one level deep, and a new seat cannot reach some constructors and not others.
*/
export declare function childClampSeats(source: ChildClampSource): Pick<ClampValues, ChildClampSeat>;
/**
* The seats `tightenTaskSpec` governs OUTSIDE the table, each with the reason it is not a row — the "forgot" / "decided"
* distinction, kept where a new reader will trip over it. The scan requires every seat `tightenTaskSpec` names to be a
* row or listed here.
*/
export declare const CHILD_CLAMP_SEATS_NOT_CARRIED: Readonly<Record<string, string>>;
/**
* The engine-built child-spec constructors, as `<repo-relative file>#<name>` — the closed registry the scan enumerates.
* A constructor that builds a spec for a child RUN belongs here; the scan requires each row to resolve to exactly one
* declaration IN ITS OWN FILE that calls {@link childClampSeats}.
*
* Why the file is half the key, and why a shape the scan cannot read is a RED rather than an absence: a registry keyed
* on a bare NAME asks a syntactic scan to enumerate every shape that binds a callable, and that list has no end — every
* shape it misses leaves the real constructor invisible while a namesake answers for it.
*/
export declare const ENGINE_CHILD_SPEC_CONSTRUCTORS: readonly ["src/agents/subagent.ts#buildChildSpec", "src/agents/subagent.ts#observerSpec", "src/agents/subagent.ts#resumeSpec", "src/agents/verify.ts#verifierSpec", "src/agents/verify.ts#fixLegSpec", "src/orchestration/workflow-primitives.ts#workflowChildClamps"];
/**
* The interaction posture a leg keeps under the clamps it inherited: `"interactive"` promises a person on the leg, and a
* leg that inherited the hard-headless clamp (`interactiveTools: false`) or the declaration that nobody can be asked
* (`approvalReach: "none"`) cannot keep that promise — the clamp is the tightening value, so the posture becomes
* `"headless"` (stated, so the leg's own resolution does not fall through to a deployment default of `"interactive"`);
* every other posture is returned as it is. Read where an inherited posture meets inherited clamps: the workflow lane
* (a baseline's posture beside the host's clamps) and the revival fold (the spawn's posture beside the waker's clamps).
*/
export declare function postureUnderClamps(posture: "interactive" | "headless" | undefined, clamps: {
readonly interactiveTools: boolean | undefined;
readonly approvalReach: TaskSpec["approvalReach"] | undefined;
}): "interactive" | "headless" | undefined;
/**
* The OWN seats of an engine-built leg that never asks the person (the observer sidecar, the verifier) — ONE declaration
* the leg's own resolutions read: no human-facing tool is mounted, the leg is headless, and nobody on it can be asked to
* approve (the approval seat resolves without the deployment's live approver: refused, or parked where the deployment's
* park facility is armed). The spec's posture outranks the one a leg inherits, and the prepare door refuses an
* "interactive" posture beside either clamp, so the three travel together. Spread ABOVE {@link childClampSeats}: a caller
* clamp can only tighten it.
*/
export declare function engineHeadlessLegSeats(): {
interactiveTools: false;
approvalReach: "none";
interactionPosture: "headless";
};
export {};