@sema-agent/core
Version:
Stateless, task-oriented AI agent core
95 lines (94 loc) • 5.12 kB
JavaScript
import { ControlPlaneCorruptError, lockedStrictUpdate, readStrictSidecar } from "./layout.js";
import { MEMORY_ORIGIN_CAUSES } from "./types.js";
export const ORIGIN_CLEARANCES_FILE = "origin-clearances.json";
function reqStr(v) {
return typeof v === "string" && v.length > 0;
}
function coerceOrigin(v, what) {
const o = v;
if (typeof o !== "object" || o === null || o.taint !== "external" || typeof o.at !== "number" || !Number.isFinite(o.at)) {
throw new ControlPlaneCorruptError(`${what}: clearance row carries a malformed origin member`);
}
if (o.cause !== undefined && !MEMORY_ORIGIN_CAUSES.includes(o.cause)) {
throw new ControlPlaneCorruptError(`${what}: clearance row carries an out-of-vocabulary origin cause (${JSON.stringify(o.cause)})`);
}
return { taint: "external", ...(o.cause !== undefined ? { cause: o.cause } : {}), at: o.at };
}
function coerceClearances(raw) {
if (raw === undefined)
return { version: 1, rows: [] };
const rec = raw;
if (typeof rec !== "object" || rec === null || !Array.isArray(rec.rows)) {
throw new ControlPlaneCorruptError("memory origin-clearance ledger has the wrong shape");
}
if (rec.version !== 1)
throw new ControlPlaneCorruptError(`memory origin-clearance ledger has an unrecognized version (${String(rec.version)}) — refusing (fail-closed)`);
for (const r of rec.rows) {
if (typeof r !== "object" ||
r === null ||
!reqStr(r.clearanceId) ||
!reqStr(r.entryId) ||
!reqStr(r.scope) ||
!reqStr(r.slug) ||
!reqStr(r.baseRev) ||
!reqStr(r.requestId) ||
!reqStr(r.reason) ||
typeof r.at !== "number" ||
!Number.isFinite(r.at) ||
typeof r.entryText !== "string" ||
r.entryText.length === 0 ||
(r.tombstonedAt !== undefined && (typeof r.tombstonedAt !== "number" || !Number.isFinite(r.tombstonedAt))) ||
(r.status !== "pending" && r.status !== "done" && r.status !== "failed") ||
!Array.isArray(r.events)) {
throw new ControlPlaneCorruptError("memory origin-clearance ledger row is malformed (fail-closed)");
}
coerceOrigin(r.origin, "memory origin-clearance ledger");
for (const e of r.events) {
if (typeof e !== "object" || e === null || !reqStr(e.eventId) || typeof e.at !== "number" || !Number.isFinite(e.at) || (e.to !== "done" && e.to !== "failed") || !reqStr(e.requestId) || (e.detail !== undefined && typeof e.detail !== "string")) {
throw new ControlPlaneCorruptError("memory origin-clearance ledger event is malformed (fail-closed)");
}
}
}
return rec;
}
export function readOriginClearances(controlDir) {
return coerceClearances(readStrictSidecar(controlDir, ORIGIN_CLEARANCES_FILE, "memory origin-clearance ledger")).rows.map((r) => ({ ...r, events: [...r.events] }));
}
export function openOriginClearance(controlDir, row) {
lockedStrictUpdate(controlDir, ORIGIN_CLEARANCES_FILE, "memory origin-clearance ledger", coerceClearances, (rec) => {
if (rec.rows.some((r) => r.entryId === row.entryId && r.status === "pending")) {
const e = new Error(`origin clearance for entry ${JSON.stringify(row.entryId)} is already pending — resume it (call clearEntryOrigin again) instead of opening a second row.`);
e.code = "memory.origin_clear_pending";
throw e;
}
rec.rows.push({ ...row, status: "pending", events: [] });
return { next: rec, result: undefined };
});
}
export function markOriginClearanceTombstoned(controlDir, input) {
lockedStrictUpdate(controlDir, ORIGIN_CLEARANCES_FILE, "memory origin-clearance ledger", coerceClearances, (rec) => {
const r = rec.rows.find((x) => x.clearanceId === input.clearanceId);
if (r === undefined || r.status !== "pending" || r.tombstonedAt !== undefined)
return { result: undefined };
r.tombstonedAt = input.now();
return { next: rec, result: undefined };
});
}
export function settleOriginClearance(controlDir, input) {
lockedStrictUpdate(controlDir, ORIGIN_CLEARANCES_FILE, "memory origin-clearance ledger", coerceClearances, (rec) => {
const r = rec.rows.find((x) => x.clearanceId === input.clearanceId);
if (r === undefined) {
const e = new Error(`origin clearance ${JSON.stringify(input.clearanceId)} is unknown`);
e.code = "memory.origin_clear_unknown";
throw e;
}
if (r.status !== "pending")
return { result: undefined };
if (r.events.some((ev) => ev.eventId === input.eventId))
return { result: undefined };
r.events.push({ eventId: input.eventId, at: input.now(), to: input.to, requestId: input.requestId, ...(input.detail !== undefined ? { detail: input.detail } : {}) });
if (input.keepPending !== true)
r.status = input.to;
return { next: rec, result: undefined };
});
}