UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

110 lines (109 loc) • 7.32 kB
import type { HarvestRejection, MemorySessionHandle } from "./types.js"; export declare const LAYER_OBSERVATIONS_FILE = "layer-observations.json"; /** One recorded unexpected write into a read-only layer. */ export interface LayerObservation { /** The layer (scope) the bytes were observed in. */ scope: string; /** Scope-home-relative, `/`-separated path the bytes were observed at (disclosure only). */ rel: string; /** The entry id the observed file carries; absent ⇔ an id-less note. */ entryId?: string; /** The entry's committed rev when observed; absent ⇔ the id had no committed row. */ rev?: string; /** The observed content's identity: its entry rev (what the read side computes from the file), or * for an id-less note the sha256 of its bytes. */ observedRev: string; /** The observing session, when the harvest was given one. */ sessionId?: string; observedAt: number; /** Whether a commit backs these bytes could not be established when they were observed (a * transaction journal was pending — the committed account could not be read whole). The row — one * per observation — then stands against ANY committed rev (never adopted) until the first session * that may write settles it, once its preflight has recovered the journal * ({@link settleUndecidedLayerObservations}): the committed account backs its bytes at its seat ⇒ * this observation is removed (the bytes are committed content); it does not ⇒ pinned to the * committed rev read, an ordinary row from then on, retired when that rev moves (superseded, as * every ordinary row is); the account still cannot be read ⇒ it stays undecided. Only id-bearing * rows (a note has no commit question) and never with `rev`. */ undecided?: true; /** An undecided row's own identity (random, minted with the row): two observations alike in every * other field — the same session in the same millisecond — stay two rows. Undecided rows only. */ observationId?: string; } /** Record observations (locked, journaled; an existing key keeps its first row). Nothing is pruned * here: the rev a harvest read before this lock need not be current any more, so it cannot decide * which other rows are stale — and a row against a rev the entry has left can never match anyway. */ export declare function recordLayerObservations(controlDir: string, rows: readonly LayerObservation[]): void; /** The table as a read-side lookup. */ export interface LayerObservationLookup { /** Whether any row names `entryId` (a cheap pre-test before the committed rev is fetched). */ covers(entryId: string): boolean; /** The row naming exactly this content (`observedRev`) of `entryId` against its committed rev * `rev` (absent ⇔ the id has no committed row) — or an undecided row for that content, which stands * against any rev until it is settled — else undefined. */ find(entryId: string, rev: string | undefined, observedRev: string): LayerObservation | undefined; /** The row naming exactly these bytes (`digest`) of an id-less note at (`scope`, `rel`), or undefined. */ findNote(scope: string, rel: string, digest: string): LayerObservation | undefined; } /** Read the table (throws the strict family's refusal on a corrupt table). */ export declare function layerObservationLookup(controlDir: string): LayerObservationLookup; /** The disclosure line for a recorded divergence met on the adopting read (one spelling, every * door). The verdict leads: the render boundary caps an item at a label's length. */ export declare function observedDivergenceNotice(rel: string, served: boolean, undecided?: boolean): string; /** The undecided rows ({@link LayerObservation.undecided}) awaiting settlement. */ export declare function undecidedLayerObservations(controlDir: string): LayerObservation[]; /** Settle undecided rows (locked, journaled), each by its own key (one observation — never another * observation of the same entry): `"backed"` removes the row (the committed account backs its bytes * at its seat: those bytes are committed content, so no unexpected write is lost), `{ rev }` pins * it to the committed rev the settlement read (`rev` absent ⇔ no committed row) — an ordinary row, * retired like any other when that rev moves. A row without a verdict stays undecided. */ export declare function settleUndecidedLayerObservations(controlDir: string, verdicts: ReadonlyMap<LayerObservation, "backed" | { rev?: string; }>): void; /** The census digest: whole-file bytes (sha256), so "the bytes that stood when the window opened" * means the bytes. */ export declare function layerDigest(text: string): string; /** The read-only layers' census (materialize, zero-copy): canonical path → {@link layerDigest} of * every entry file under `homes`, scanned at the read side's depth (what the adopting read sees). */ export declare function readOnlyLayerCensus(homes: readonly string[]): Map<string, string>; /** A file of a read-only layer whose bytes changed since the materialize-time census. */ export interface ReadOnlyObservationCandidate { path: string; scope: string; rel: string; slug: string; digest: string; id?: string; /** The entry rev of the bytes, or their digest for an id-less note. */ observedRev: string; } /** The observation half's candidates: files of the layers `handle` holds read-only whose bytes differ * from its materialize-time census (none without a census). Throws when the scope registry cannot * be read (the homes are resolved through it). */ export declare function readOnlyLayerCandidates(memoryDir: string, controlDir: string, handle: Pick<MemorySessionHandle, "layerBaseline" | "scopes" | "writeScope">): ReadOnlyObservationCandidate[]; /** What a harvest's observation half judged at detection: the rows (written AS JUDGED — a retry writes * the same rows, never a re-judgment against a later account), the rejections and undecided * candidates they carry, whether they landed, and the failure of the last attempt that did not. */ export interface ObservationBatch { rows: LayerObservation[]; rejections: HarvestRejection[]; /** Each undecided candidate with the row it was recorded as (its observation identity). */ undecided: Array<{ candidate: ReadOnlyObservationCandidate; row: LayerObservation; }>; landed: boolean; failure?: string; } /** The undecided candidates a LANDED batch recorded (an unlanded one claims none). */ export declare function landedUndecided(batch: ObservationBatch | undefined): ObservationBatch["undecided"]; /** Whether two rows are the same observation (the table's key — for an undecided row, one * observation: session and time included). */ export declare function sameObservation(a: LayerObservation, b: LayerObservation): boolean; /** Whether the committed row backs an observation's bytes at its seat (same rev, bound there) — the * observation was a commit, not an unexpected write. */ export declare function observationBacked(row: Pick<LayerObservation, "scope" | "rel" | "observedRev">, committed: { rev: string; scope?: string; slug?: string; } | undefined): boolean;