@sema-agent/core
Version:
Stateless, task-oriented AI agent core
248 lines (247 loc) • 16.8 kB
TypeScript
/**
* The person's persisted rules as ONE run reads them.
*
* ## One read per adjudicated call
*
* A call is judged against ONE read of the store, pinned to its `toolCallId` ({@link PersonPathRows.pin}): whichever of the
* call's consumers asks first — the read station's judgment, the org layer, the personal rule lane — takes the read, the
* others are handed the same answer, and the tables the call's tool judges with when it executes are compiled from it
* ({@link PathTablesFacade.forCall}). A call is an attempt the leg's occurrence table holds; the pin is released when the
* table no longer holds that attempt. A judgment that is no call of the leg (a visibility probe's fixed id, a gate with no
* leg) keeps nothing: its rule lanes take a read of their own, its read station the run's latest tables.
*
* ## The run's unit, one entry per partition
*
* Every read's outcome lands in the run's unit, per personal partition (the durable one — its user and project rows — and
* the session one), and only where it is newer, by the order the reads STARTED: a read that answers late never replaces
* a later one. The prepare read is the unit's first write. A reader that is not a call (the compaction summary's working-file
* reader, the user-attachment seat's reader, the Artifact tool's local read, a read root's grant eligibility, the classifier's table, a language server's session read)
* reads the unit's newest tables: while the rows the unit last took are compiling it waits for them (within
* {@link PATH_TABLES_READY_BOUND_MS}), and a read of tables still compiling, or that could not be compiled, refuses —
* never the tables of rows the unit has since replaced.
*
* ## One failure law, every consumer
*
* A partition a read could not read (it failed, or the whole read failed or did not answer in time) contributes the deny
* and ask rows of the run's last successful read of that partition, and none of its allow rows (a `Read(//dir/**)` read
* directory among them): what could not be read widens nothing, and what was read before still protects. A partition the
* read did read contributes its rows as read, both directions. The cost: a deny row the person just removed keeps
* refusing while its partition cannot be read — the run's one notice says so.
*
* ## One table facade
*
* The read deny table, the write deny table and the read directories are compiled from the rows a read stands on, once
* per distinct content (row text × scope, which fixes the anchors; the directories), and held behind one facade whose
* every reader takes the call's tables or the unit's latest.
*/
import type { EffectivePermissionRules, PermissionRuleStoreProvider, PersonRowsRead } from "../permission-rule-provider.js";
import { type PathRuleBases } from "../permission-rule-model.js";
import { type BoundPathRule, type HeadLeads } from "../path-rule-binding.js";
import type { RunnerDeps } from "../runner-deps.js";
import { type PathRuleDenyRow, type ReadDenyBuiltinConfig, type ReadDenyEntry, type ReadDenySeat, type WriteDenyMatcher } from "../../tools/fs/read-deny.js";
import { type HomeViewSource, type PlacedReadDenyMatcher } from "../../tools/fs/read-deny-home.js";
import { type ShellReadCheck } from "../../tools/fs/shell-read-check.js";
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
/**
* How long a read waits for the store before its call is judged by the failure law: one second inside the rule lanes' own
* wait (`ORG_ADJUDICATION_TIMEOUT_MS`, the gate's bound on each lane), so the law's answer — the deny and ask rows last
* read — reaches a lane before the lane's wait ends and reads its absence as no rule at all.
*/
export declare const PERSON_READ_BOUND_MS: number;
/** The one bound on path tables becoming ready: a compile settles or fails within it, and a reader waits for its tables at
* most this long (a call's own, or the unit's newest) before it reads them — and is refused if they are not ready. */
export declare const PATH_TABLES_READY_BOUND_MS = 30000;
/** The person's rows a run's path tables take: their face-keyed path deny rows, bound at their anchors, and the
* directories of their `Read(//dir/**)` allow rows (lexical, absolute). */
export interface PersonTableRows {
readonly denyRows: readonly BoundPathRule[];
readonly readDirectories: readonly string[];
}
/** A run's path tables, compiled over one set of the person's rows beside the leg's own. */
export interface PathTables {
/** The read deny table, placed at its homes. */
readonly readDeny: PlacedReadDenyMatcher;
/** The write deny table, with the read table's home views. */
readonly writeDeny: WriteDenyMatcher & {
readonly homeViews: HomeViewSource;
};
/** The person's read directories, canonical. */
readonly readDirectories: readonly string[];
/** The rule rows both tables were compiled over (the leg's own and the person's, as bound — before their twins where
* their heads lead): what the tables say they guard. */
readonly ruleRows: readonly PathRuleDenyRow[];
}
/** A read-root set read per judgment, and the one a call judges with (`forCall`: its pinned read's, or the latest); `ready`
* settles once the set a reader that is no call takes is compiled ({@link PersonPathRows.tablesReady}). */
export type ReadRootsSeat = (() => readonly string[]) & {
forCall(toolCallId: string | undefined): () => readonly string[];
ready(signal?: AbortSignal): Promise<void>;
};
/** The read-root seat of a leg with no read face: no roots, ready. */
export declare const EMPTY_READ_ROOTS: ReadRootsSeat;
/** The tables, a call's or the unit's newest: `forCall(undefined)` and a call holding no pinned read take the newest — a
* read of them while they compile, or after they could not be compiled, throws (the check refuses). */
export interface PathTablesFacade {
/** The tables ONE call judges with: its pinned read's, or the unit's newest (an unknown or released call). */
forCall(toolCallId: string | undefined): PathTables;
/** The read deny table: each judgment reads the unit's latest; `forCall` hands a call its own. */
readonly readDeny: PlacedReadDenyMatcher & {
forCall(toolCallId: string | undefined): PlacedReadDenyMatcher;
};
/** The write deny table, the same way. */
readonly writeDeny: WriteDenyMatcher & {
readonly homeViews: HomeViewSource;
forCall(toolCallId: string | undefined): WriteDenyMatcher & {
readonly homeViews: HomeViewSource;
};
};
/** The person's read directories (canonical), a call's or the unit's latest. */
readDirectories(toolCallId?: string): readonly string[];
/** The rule rows of the unit's latest tables ({@link PathTables.ruleRows}). */
ruleRows(): readonly PathRuleDenyRow[];
}
/** The one reader of a run's person's store. */
export interface PersonPathRows {
/** Whether the run has a person whose rows a read could carry (a durable partition, and an owner). */
readonly holdsPersonRows: boolean;
/** The prepare read — the unit's first write. `undefined`, synchronously, when there is no person's rows to read. */
prime(): Promise<void> | undefined;
/** The rows of a call's one read — a call the leg's occurrence table holds: taken by its first consumer, handed to every
* other until the table no longer holds that attempt; any other judgment (no leg, or an id the table does not hold — a
* visibility probe) takes a read of its own, kept by nobody. Bounded ({@link PERSON_READ_BOUND_MS}); never rejects. */
pin(toolCallId: string): Promise<PersonRowsRead>;
/** Settles once the tables a reader takes are compiled: a pinned call's own; any other reader's (no id, or a call holding
* no pin), the unit's newest — through every change that lands meanwhile, within {@link PATH_TABLES_READY_BOUND_MS} —
* or at `signal` or the run's cut ({@link PersonPathRowsConfig.signal}). Never rejects: a reader whose wait was cut
* checks its own signal and refuses; tables still compiling, or that could not be compiled, refuse at the read
* ({@link PathTablesFacade.forCall}). */
tablesReady(toolCallId?: string, signal?: AbortSignal): Promise<void>;
/** Whether `toolCallId` is a call of the leg (an attempt its occurrence table holds): the judgments that read a call's
* one read. A read station's judgment that is no call reads the run's latest tables and takes no read. */
holds(toolCallId: string): boolean;
/** Whether a partition of the rows a judgment stands on could not be read — a pinned call's read's, or the run's latest
* read of each partition: there "no row forbids this" cannot be told from "a row it cannot see does" (`false` for a run
* with no person's rows). */
unreadFor(toolCallId?: string): boolean;
/** The tables over the unit's latest and every call's read, compiled by `compile` (the leg's own rows beside the
* person's): compiled now from the unit's latest. */
attachTables(compile: (person: PersonTableRows) => Promise<PathTables>): Promise<PathTablesFacade>;
}
export interface PersonPathRowsConfig {
provider: PermissionRuleStoreProvider | undefined;
localOwnerDeclared: boolean;
principal: string | undefined;
/** The session whose session-scoped rows are read; `undefined` ⇒ none. */
sessionId: string | undefined;
/** The leg's own root: which project rows apply (either spelling of it — {@link projectScopeIdentity}); `undefined` ⇒ none. */
root: string | undefined;
rootCanonical: string | undefined;
/** The leg's resolution of locations (a project row's scope root, where it leads). */
leads: HeadLeads;
/** The anchors of the global / session rows and of `~/`: the chain's top leg's root and home. */
chainTop: PathRuleBases;
/** The leg's calls: a call is pinned to its attempt here, and released when the table no longer holds that attempt. */
occurrences?: {
get(toolCallId: string): unknown;
};
/** An engine defect the run must hear of beyond its trace (a table that could not be compiled). */
onDefect?: (err: Error) => void;
/** The operator's line for each read the store could not answer, each disclosure line a read carries, each row a table
* could not anchor. */
onDisclosure: (message: string) => void;
/** The run's one notice for a store that could not be read ({@link ruleStoreUnreadableNotice}). */
onUnread: (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
/** The run's one notice for rows left out ({@link ruleRowsDroppedNotice}). */
onRowsDropped: (rows: EffectivePermissionRules["droppedRows"]) => void;
/** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store — and every wait
* on the tables ends at it. */
signal?: AbortSignal;
}
export declare function createPersonPathRows(cfg: PersonPathRowsConfig): PersonPathRows;
/** The compile of a run's path tables over the leg's own rows beside one set of the person's: every row also judged where
* its head leads in this environment; the home-anchored and rooted rows placed at their homes and where they lead (the
* host's declared configuration root among them); the write face over the same rows with the read table's home views;
* the person's read directories canonicalized by `canonicalize` (the leg's admission of a declared read root). */
export declare function pathTablesCompile(seats: {
readonly env: ExecutionEnv;
readonly additions: readonly ReadDenyEntry[];
readonly builtin: ReadDenyBuiltinConfig;
readonly legRows: readonly PathRuleDenyRow[];
readonly leads: HeadLeads;
readonly configRoot: string | undefined;
readonly canonicalize: (dirs: readonly string[]) => Promise<readonly string[]>;
}): (person: PersonTableRows) => Promise<PathTables>;
/** The read face's root set over the leg's own roots (`own`, read per judgment) and the person's read directories — a
* call's own (`forCall`) or the run's latest. A person's directory that admits any part of the local artifact store
* (`store`, once known) is no read root: the store rule an in-flight grant meets; `onHeld` hears each such directory once. */
export declare function readRootsOver(seats: {
readonly own: () => readonly string[];
readonly tables: PathTablesFacade;
readonly ready: PersonPathRows["tablesReady"];
readonly store: () => string | undefined;
readonly onHeld: (dir: string, store: string) => void;
}): ReadRootsSeat;
export declare function denySeatsOf(t: PathTables): {
readonly read: ReadDenySeat;
readonly write: {
readonly writeDenyMatch?: (p: string) => string | null;
};
};
/**
* The leg's read stations as ONE gate input, each judgment on its call's pinned read of the person's rules: a station is
* a call's first consumer of the person's rows (it sits in the fold, ahead of the rule lanes), so it takes the call's
* read before it judges — the read every later consumer of the call is handed — and judges with that read's tables. A
* judgment that is no call of the leg (no id, or one the leg does not hold) takes no read and judges with the run's
* newest tables once they are ready, as every reader that is no call does; a judged call the station finds nothing to
* read in is vouched for, as it would have been. (A partition of those rows that could not be read reaches the station on
* the boundary it judges with — {@link PersonPathRows.unreadFor} — where it vouches for nothing.)
*/
export declare function onPinnedRead(rows: PersonPathRows, stations: ReadonlyArray<{
readonly check: ShellReadCheck;
readonly tools: ReadonlySet<string>;
}>): ShellReadCheck | undefined;
/** A tool that holds the run's tables, run once the tables it reads are compiled (or the call is cancelled) — the tool
* reads its call's own (`seatForCall`) at the start of its execution; a call no gate consumer pinned reads the run's
* newest. The wait is bounded ({@link PATH_TABLES_READY_BOUND_MS}). */
export declare function afterCallTables(rows: PersonPathRows, tool: AgentTool): AgentTool;
/** A reader for one task's prepare: the deployment's store and owner, its notices on the run's one notice seats (`notices`,
* minted once per run: every reader of the run tells through them), its lines on the run's trace, cut by `signals` (the
* run's own; none for the judgment asked after the run has ended), released with the leg's calls. */
export declare function personPathRowsOfTask(t: {
readonly deps: Pick<RunnerDeps, "permissionRuleStore" | "localOwnerRules" | "tracer" | "onError">;
readonly notices: Pick<PersonPathRowsConfig, "onUnread" | "onRowsDropped">;
readonly principal: string | undefined;
readonly ids: {
readonly sessionId: string;
readonly runId: string;
readonly taskId: string;
};
readonly root: string;
readonly rootCanonical: string | undefined;
readonly leads: HeadLeads;
readonly chainTop: PathRuleBases;
readonly occurrences: {
get(toolCallId: string): unknown;
};
readonly signals: readonly AbortSignal[];
}): PersonPathRows;
/**
* The run's ONE `permission.rule_store_unavailable` notice (audience user): the person's rules — the whole store, or the
* sources of it whose partition could not be read — could not be read; minted by whichever read found it first (the
* prepare read, or a call's), once per run however many reads fail after it. The trace keeps every read's line.
*/
export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice"], ids: {
readonly sessionId: string;
readonly runId: string;
}): (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
/**
* The run's ONE `permission.rule_row_dropped` notice (audience user): the person's store served project-scoped rows whose
* scope root is not a complete absolute path, left out of the checks; minted by whichever read met them first, once per
* run. The sentence names no path; the rows ride the detail: `rule` is the rule text as the store served it, through the one
* reading ({@link noticeRuleText}) that `permission.allowed_tool_not_granted`'s `entries[].entry` uses too — the machine-read side carries the
* original, the sentence spells — and `scopeRoot` neutralized and bounded. The trace keeps every read's line.
*/
export declare function ruleRowsDroppedNotice(onNotice: RunnerDeps["onNotice"], ids: {
readonly sessionId: string;
readonly runId: string;
}): (rows: EffectivePermissionRules["droppedRows"]) => void;