UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

114 lines (113 loc) • 6.76 kB
import type { MemoryEntryOrigin } from "./types.js"; export declare const ORIGIN_CLEARANCES_FILE = "origin-clearances.json"; /** One clearance's terminal event (appended, never rewritten). */ export interface OriginClearanceEvent { eventId: string; at: number; to: "done" | "failed"; /** The resolver of THIS event (the opener on the normal path; a later resumer on a crash path). */ requestId: string; /** Mechanical detail (landing slug, refusal reason) — engine-composed, bounded by the writer. */ detail?: string; } export interface OriginClearanceRow { clearanceId: string; /** The entry whose marker was cleared. The re-record keeps the SAME id (§2.3-2's later-batch * arm), so this key joins the clearance to the entry across its whole life. */ entryId: string; scope: string; slug: string; /** The committed rev the clearance was judged against (the tombstone's CAS anchor). */ baseRev: string; /** The cleared marker, verbatim (audit: what the host vouched away). */ origin: MemoryEntryOrigin; /** WHO cleared — the mandatory audit attribution (never defaulted). */ requestId: string; /** WHY — the host's stated ground (never defaulted). */ reason: string; at: number; status: "pending" | "done" | "failed"; /** CUSTODY: the full serialized cleared-entry file text. A crash between the tombstone batch and * the re-record batch leaves this as the only copy — the pending row is the loud recovery seat * (a later clearEntryOrigin call for the same entry resumes from it). */ entryText: string; /** Set the moment THIS clearance's tombstone commits ({@link markOriginClearanceTombstoned}) — * the resume arm's arbiter for an absent entry: with it, absence is the known crash window * (tombstone landed, re-record owed — replay from custody); without it, the absence is an * INDEPENDENT deletion this clearance never made, and replaying the add would silently undo a * deliberate delete. Absent on rows recorded before this member existed — such a row's resume * refuses conservatively (custody stays disclosed on the row; the refusal states it). */ tombstonedAt?: number; events: OriginClearanceEvent[]; } /** * #468② — the ACCOUNT SHADOW a `memory.origin_clear_not_marked` refusal carries when the engine already * held the answer: the entry's most recent COMPLETED clearance, as who asked for it and when it settled. * * It exists to discriminate the refusal's two very different causes without a second full read of the * account: an entry that was NEVER marked (no shadow — nothing to clear, and nothing ever cleared) versus a * RE-SEND of a clear that already succeeded (shadow present — the caller's own earlier call, or another * host's, already did this). Absence is honest absence: the engine never opens an extra read to fill it, so * a refusal raised on a path that did not need the ledger simply omits it. * * `requestId` is the OPENER's attribution (the `clearEntryOrigin` caller's own key — what a re-sending * caller compares against), while `settledAt` is the row's terminal `done` event, which on a crash-resumed * row is a LATER moment than the opening. The full row (reason, origin, custody, every event) stays one * {@link OriginClearanceRow} lookup away — this shadow is a discriminator, never a replacement for it. * * AS-OF SEMANTICS (codex r3): the account is read ONCE per `clearEntryOrigin` call, by the probe that looks * for a resumable pending row, and the committed entry is read after it. A clearance opened AND settled by * ANOTHER caller inside that window is therefore not in the snapshot this shadow is derived from, so its * absence means "no settled clearance as of this call's own read of the account", never "none exists" — and * a fresh call answers with the newer account. The caller's OWN earlier clear (the re-send case this exists * for) settled long before and is always in the snapshot. Deliberately not closed by a second read: the * shadow is a convenience over the authoritative `listOriginClearances`, and buying strictness with an extra * ledger read on a refusal path would cost every caller what only a concurrent third party could observe. */ export interface OriginClearanceShadow { requestId: string; settledAt: number; } /** Lock-free strict read of the whole account (host audit face; journal-aware, corrupt = throw). */ export declare function readOriginClearances(controlDir: string): OriginClearanceRow[]; /** * WRITE-AHEAD open: append the pending row (custody included) before any store mutation. Refuses a * second OPEN while a pending row for the same entry stands — the pending row is a resume seat, * not a queue (the caller resumes it instead). */ export declare function openOriginClearance(controlDir: string, row: Omit<OriginClearanceRow, "status" | "events">): void; /** * Record that THIS clearance's tombstone leg committed (called between the delete's success and * the re-record attempt). The record is what lets a later resume read an ABSENT entry as "the * known crash window — replay the add from custody" instead of guessing: absence alone is * un-attributable (an independent, deliberate deletion through the ordinary tombstone lane leaves * the identical committed state). Idempotent (first write wins); non-pending rows are a no-op * (a settled row's history never rewrites). */ export declare function markOriginClearanceTombstoned(controlDir: string, input: { clearanceId: string; now: () => number; }): void; /** * Terminal event append + status flip. Unknown clearanceId is a corrupt-caller refusal. * * SINGLE-WINNER terminal: a row already terminal ignores a late contradictory * settlement — two concurrent resumers race the store legs (the store's own CAS decides those), * and the loser's stale verdict must not overwrite the winner's audit state (a successfully * cleared entry reading `failed` with contradictory events). The loser's own thrown refusal is * its loud channel; a re-call then answers off the CURRENT committed state. * * `keepPending: true` appends the event WITHOUT flipping the status: the resume-seat form for a * refused re-record leg — the tombstone has committed and the custody row is the ONLY copy of the * bytes, so the row must stay resumable (a `failed` flip would strand them: the resume arm only * completes pending rows). */ export declare function settleOriginClearance(controlDir: string, input: { clearanceId: string; to: "done" | "failed"; requestId: string; eventId: string; now: () => number; detail?: string; keepPending?: boolean; }): void;