UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

248 lines (247 loc) • 16.8 kB
/** * 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;