UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

95 lines (94 loc) • 5.12 kB
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 }; }); }