@sema-agent/core
Version:
Stateless, task-oriented AI agent core
114 lines (113 loc) • 6.76 kB
TypeScript
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;