UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

96 lines (95 loc) • 6.98 kB
/** 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>; };