@sema-agent/core
Version:
Stateless, task-oriented AI agent core
96 lines (95 loc) • 6.98 kB
TypeScript
/** A containment-face state. `"roots"` = the containment judgment runs. `"open"` = the containment
* step is skipped (canonicalization, the deny/sensitive sets, the UNC out-of-set refusal and the
* type gates all still run). One type for both seats: the two faces differ in WHICH band consults
* them, never in what the word means. */
export type ContainmentFace = "open" | "roots";
/** Which band a face governs. Selects the seat spellings and the error codes below — never the
* resolution ORDER, which is one body shared by both. */
export type ContainmentFaceKind = "read" | "write";
/** Which layer a seat belongs to. The SPELLING is derived from (kind, layer), never re-typed at a
* call site: three doors screen the same two seats, and the value gate's message is the only place
* a deployment reads the seat's name back. */
export type ContainmentFaceLayer = "spec" | "deps";
/** The seat spelling for a (kind, layer) pair — single-sourced from {@link FACE_DIALECT}. */
export declare function containmentFaceSeatName(kind: ContainmentFaceKind, layer: ContainmentFaceLayer): string;
/** Loud value gate: a containment-face seat carries exactly "open" | "roots" — anything else (case
* variants, truthy garbage) refuses at wiring time, never folds to a default. Exported
* module-internally (not on the package surface) so prepare's unconditional config-guard leg can
* screen the seats on runs that never mount hands. The resolver itself also runs on every hands-less
* leg; this screen still fires first, so a garbage value refuses before any leg-shape branching. */
export declare function assertContainmentFaceValue(v: unknown, kind: ContainmentFaceKind, layer: ContainmentFaceLayer): ContainmentFace | undefined;
/** Inputs to {@link resolveContainmentFace} — structural/declaration facts; a permission mode enters only as
* the face its table row states ({@link modeFace}), never as a mode word. */
export interface ContainmentFaceInputs {
/** Which face is being resolved. Selects the seat spellings and the mint codes; the ORDER below is
* one body, identical for both. */
kind: ContainmentFaceKind;
/** TaskSpec.readFace / TaskSpec.writeFace (raw — validated here). The TASK layer: may only tighten
* under governance. */
specFace?: unknown;
/** RunnerDeps / HandsToolkitOptions seat of the same face (raw) — the DEPLOYMENT's own declaration. */
depsFace?: unknown;
/** The verifier read-only mount (`handsReadOnly` / toolkit `readOnly`): for the read face its
* containment is a LOAD-BEARING wall (bash_readonly is genuinely confined by it); for the write
* face it is the absence of the band entirely. Neither is openable. */
readOnlyMount: boolean;
/** Org governance declared (`RunnerDeps.permissionRuleStore.partitions.org === true`): default roots; the task layer
* may not open (tighten-only under governance; the deps layer, being the deployment's own
* declaration, still may). */
orgGoverned: boolean;
/** Is the FULL shell (`core.bash@1`) structurally reachable on this mount? Computed BEFORE
* tool-band assembly from spec-time facts (env kind / handsReadOnly / Bash exclusion — every
* exclusion source is spec-time-frozen, so this is always computable). When false, the roots fence
* is the ONLY boundary this face has, so no IMPLICIT default may open it; an EXPLICIT "open" on
* either seat or the mode's face still wins (row 4 of the order is a default, not a clamp). */
fullShellReachable: boolean;
/** The face the leg's permission MODE states (`ModePolicy.writeFace`: `bypassPermissions` ⇒ "open"; absent ⇒
* the mode states nothing). Row 3′: read after the explicit seats and before the defaults — a seat's word (the
* deployment's ceiling, a task's tighten) outranks it, and neither the read-only mount nor org governance
* consults it. */
modeFace?: ContainmentFace;
/** Observation hook for the ONE silent branch of the order: row 1's deployment-seat clamp
* (readOnly mount + deps "open" + NO task seat). Fired exactly when the deps declaration was the
* deciding seat and the mount overrode it — not when a task-level "roots" pinned the same value
* anyway (the mount changed nothing there, and announcing it would claim a false cause), and
* never on the throw rows (those are already loud). The resolver stays the single owner of the
* clamp predicate; stations own delivery/dedup ({@link deploymentFaceClampNotice}). */
onDeploymentClamp?: () => void;
}
/**
* The ONE resolution order, first hit wins — ONE body for BOTH faces. prepare-task AND
* createHandsToolkit both call it, once per face, so a library-direct mount gets identical validation
* and identical defaults on each seat.
*
* 1. read-only (verifier) mount → ROOTS; an explicit "open" on the TASK seat is a genuine
* per-call CONTRADICTION and refuses loudly (never silently pick a side). An "open" on the
* DEPLOYMENT seat alone is a deployment-wide default, not a per-task assertion — it silently
* CLAMPS to roots (stricter-wins), the same seat distinction row 2 draws for org governance.
* 2. org-governed: the task seat's "open" refuses (task layer only tightens); deps "open" wins;
* otherwise ROOTS.
* 3. explicit seat: spec ?? deps.
* 3′. the mode's face ({@link ContainmentFaceInputs.modeFace}).
* 4. bash-less mount: ROOTS (the fence is the only boundary there — "the fence guards nothing bash
* reaches anyway" does not hold, so no implicit default may open it).
* 5. default: ROOTS — the engine never opens implicitly.
*
* @contract fs.containment_face.seat_independence — the two containment seats never read each other:
* each band passes its OWN resolved value into the shared containment step, a seat nobody declared
* resolves to "roots" on its own, and no gate other than that step is touched by either word. A
* deployment that wants both bands open declares both.
*/
export declare function resolveContainmentFace(i: ContainmentFaceInputs): ContainmentFace;
/**
* The ONE text of a face's deployment-clamp announcement (`config.<face>_face_deployment_clamped`).
* Both stations deliver THIS object through their own `deliverEngineNotice` call (prepare-task with
* the `RunnerDeps.onNotice` seat, once per notice sink — the sink, not the deps object, because the
* Runner rebuilds its deps; `createHandsToolkit` with its band-local `HandsToolkitOptions.onNotice`
* seat — toolkit creation is itself the one-per-mount boot moment, and an absent seat prints the
* same line via `console.warn`). Built here, import-free, so the message can never fork between
* the stations OR between the two faces.
*/
export declare function deploymentFaceClampNotice(kind: ContainmentFaceKind): {
code: string;
message: string;
detail: Record<string, unknown>;
};