UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

270 lines (269 loc) • 17.1 kB
/** * 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 {};