@sema-agent/core
Version:
Stateless, task-oriented AI agent core
337 lines (336 loc) • 17.8 kB
JavaScript
import { createHash } from "node:crypto";
import { canonicalJsonStringify } from "./file-backend.js";
import { computeEntryRev } from "./frontmatter.js";
import { MEMORY_ORIGIN_CAUSES } from "./types.js";
import { ambiguousOriginRepresentation, committedOriginOf } from "./frontmatter.js";
export const BUNDLE_SECTION_KEYS = Object.freeze(["meta", "entries", "governance", "residuals"]);
function sha256Line(text) {
return createHash("sha256").update(`${text}\n`, "utf8").digest("hex");
}
export function computeBundleSectionHashes(bundle) {
return {
meta: sha256Line(canonicalJsonStringify({ bundle: bundle.bundle, at: bundle.at, scopes: bundle.scopes, storeId: bundle.storeId })),
entries: sha256Line(canonicalJsonStringify(bundle.entries)),
governance: sha256Line(canonicalJsonStringify(bundle.governance)),
residuals: sha256Line(canonicalJsonStringify(bundle.residuals)),
};
}
export function computeMemoryBundleHash(sectionHashes) {
return sha256Line(canonicalJsonStringify(sectionHashes));
}
const HEX64_RE = /^[0-9a-f]{64}$/;
function isRecord(v) {
return typeof v === "object" && v !== null && !Array.isArray(v);
}
function stringArrayInvalid(v, what) {
if (!Array.isArray(v))
return `${what} is not an array`;
for (const s of v)
if (typeof s !== "string")
return `${what} carries a non-string member`;
return undefined;
}
function countInvalid(v, what) {
return typeof v === "number" && Number.isInteger(v) && v >= 0 ? undefined : `${what} is not a non-negative integer`;
}
function entryInvalid(raw, scopes) {
if (!isRecord(raw))
return "entry is not an object";
const e = raw;
if (typeof e.id !== "string" || e.id.length === 0)
return "entry has no id";
if (typeof e.slug !== "string" || e.slug.length === 0)
return `entry ${JSON.stringify(e.id)} has no slug`;
if (typeof e.scope !== "string" || e.scope.length === 0)
return `entry ${JSON.stringify(e.id)} has no scope`;
if (!scopes.has(e.scope))
return `entry ${JSON.stringify(e.id)} carries scope ${JSON.stringify(e.scope)} outside bundle.scopes (self-consistency)`;
if (typeof e.body !== "string")
return `entry ${JSON.stringify(e.id)} has no string body`;
if (typeof e.rev !== "string" || e.rev.length === 0)
return `entry ${JSON.stringify(e.id)} has no rev`;
const fm = e.frontmatter;
if (!isRecord(fm))
return `entry ${JSON.stringify(e.id)} has no frontmatter object`;
for (const key of ["name", "description", "type"]) {
if (fm[key] !== undefined && typeof fm[key] !== "string")
return `entry ${JSON.stringify(e.id)} frontmatter.${key} is not a string`;
}
if (fm.deleted !== undefined && typeof fm.deleted !== "boolean")
return `entry ${JSON.stringify(e.id)} frontmatter.deleted is not a boolean`;
if (fm.extra !== undefined) {
const bad = stringArrayInvalid(fm.extra, `entry ${JSON.stringify(e.id)} frontmatter.extra`);
if (bad !== undefined)
return bad;
}
if (fm.trust !== undefined && fm.trust !== "untrusted")
return `entry ${JSON.stringify(e.id)} frontmatter.trust is not "untrusted"`;
if (fm.provenance !== undefined) {
const p = fm.provenance;
if (!isRecord(p) || p.kind !== "repo_file" || typeof p.path !== "string" || typeof p.contentHash !== "string" || typeof p.ingestedAt !== "number") {
return `entry ${JSON.stringify(e.id)} frontmatter.provenance is malformed`;
}
}
if (fm.origin !== undefined) {
const o = fm.origin;
if (!isRecord(o) || o.taint !== "external" || typeof o.at !== "number" || !Number.isFinite(o.at) || (o.cause !== undefined && !(typeof o.cause === "string" && MEMORY_ORIGIN_CAUSES.includes(o.cause)))) {
return `entry ${JSON.stringify(e.id)} frontmatter.origin is malformed`;
}
}
if (fm.distilled !== undefined) {
const d = fm.distilled;
const inputsOk = (v) => Array.isArray(v) &&
v.length > 0 &&
v.every((row) => isRecord(row) && typeof row.id === "string" && row.id.length > 0 && typeof row.rev === "string" && row.rev.length > 0 && (row.superseded === undefined || row.superseded === true));
if (!isRecord(d) || typeof d.planId !== "string" || d.planId.length === 0 || typeof d.at !== "number" || !Number.isFinite(d.at) || typeof d.carrierRev !== "string" || d.carrierRev.length === 0 || !inputsOk(d.inputs)) {
return `entry ${JSON.stringify(e.id)} frontmatter.distilled is malformed`;
}
}
if (ambiguousOriginRepresentation(fm)) {
return `entry ${JSON.stringify(e.id)} carries conflicting or duplicated origin representations (ambiguous marker representation)`;
}
const recomputed = computeEntryRev({ id: e.id, frontmatter: fm, body: e.body });
if (recomputed !== e.rev)
return `entry ${JSON.stringify(e.id)} rev ${JSON.stringify(e.rev)} does not equal its recomputed content rev ${JSON.stringify(recomputed)} (forged or corrupted package)`;
return undefined;
}
export function memoryBundleInvalid(raw, opts = {}) {
if (!isRecord(raw))
return "bundle is not an object";
const b = raw;
if (typeof b.bundle !== "number")
return "bundle envelope version is missing";
if (b.bundle > 1)
return `bundle envelope version ${String(b.bundle)} is newer than this engine supports — refusing to reinterpret it`;
if (b.bundle !== 1)
return `bundle envelope version ${String(b.bundle)} is not recognized`;
if (typeof b.at !== "number" || !Number.isInteger(b.at))
return "bundle.at is not an integer";
const scopesBad = stringArrayInvalid(b.scopes, "bundle.scopes");
if (scopesBad !== undefined)
return scopesBad;
const scopes = b.scopes;
if (scopes.length === 0)
return "bundle.scopes is empty";
if (scopes.some((s) => s.length === 0))
return "bundle.scopes carries an empty scope";
if (new Set(scopes).size !== scopes.length)
return "bundle.scopes carries a duplicate scope";
if (typeof b.storeId !== "string" || b.storeId.length === 0)
return "bundle.storeId is missing";
if (opts.expectedScopes !== undefined) {
const allowed = new Set(opts.expectedScopes);
const outside = scopes.find((s) => !allowed.has(s));
if (outside !== undefined)
return `bundle scope ${JSON.stringify(outside)} is outside the expected scope set — whole package refused`;
}
const entriesArr = Array.isArray(b.entries) && b.entries;
if (entriesArr === false)
return "bundle.entries is not an array";
const scopeSet = new Set(scopes);
const entryIds = new Set();
for (const raw2 of entriesArr) {
const bad = entryInvalid(raw2, scopeSet);
if (bad !== undefined)
return bad;
const id = raw2.id;
if (entryIds.has(id))
return `bundle.entries carries id ${JSON.stringify(id)} twice (bad package)`;
entryIds.add(id);
}
const gov = b.governance;
if (!isRecord(gov))
return "bundle.governance is missing (the governance section is a structural requirement, not an attachment)";
const challengesArr = Array.isArray(gov.challenges) && gov.challenges;
if (challengesArr === false)
return "bundle.governance.challenges is not an array";
const challengeEventIds = new Set();
for (const raw2 of challengesArr) {
if (!isRecord(raw2))
return "challenge row is not an object";
const c = raw2;
if (typeof c.eventId !== "string" || c.eventId.length === 0)
return "challenge row has no eventId";
if (typeof c.entryId !== "string" || c.entryId.length === 0)
return "challenge row has no entryId";
if (typeof c.reason !== "string")
return "challenge row has no reason";
if (typeof c.at !== "number" || !Number.isInteger(c.at))
return "challenge row has no integer at";
if (c.challengedRev !== undefined && typeof c.challengedRev !== "string")
return "challenge row challengedRev is not a string";
if (challengeEventIds.has(c.eventId))
return `bundle.governance.challenges carries eventId ${JSON.stringify(c.eventId)} twice (bad package)`;
challengeEventIds.add(c.eventId);
}
const pollutedSessionsArr = Array.isArray(gov.pollutedSessions) && gov.pollutedSessions;
if (pollutedSessionsArr === false)
return "bundle.governance.pollutedSessions is not an array";
const pollutedIds = new Set();
for (const raw2 of pollutedSessionsArr) {
if (!isRecord(raw2))
return "polluted-session row is not an object";
if (typeof raw2.sessionId !== "string" || raw2.sessionId.length === 0)
return "polluted-session row has no sessionId";
if (typeof raw2.at !== "number")
return "polluted-session row has no at";
if (typeof raw2.reason !== "string")
return "polluted-session row has no reason";
if (raw2.cause !== undefined && !(typeof raw2.cause === "string" && MEMORY_ORIGIN_CAUSES.includes(raw2.cause)))
return "polluted-session row cause is not a known spelling";
if (pollutedIds.has(raw2.sessionId))
return `bundle.governance.pollutedSessions carries sessionId ${JSON.stringify(raw2.sessionId)} twice (bad package)`;
pollutedIds.add(raw2.sessionId);
}
const lineageArr = Array.isArray(gov.lineage) && gov.lineage;
if (lineageArr === false)
return "bundle.governance.lineage is not an array";
const lineagePairs = new Set();
for (const raw2 of lineageArr) {
if (!isRecord(raw2))
return "lineage row is not an object";
if (typeof raw2.entryId !== "string" || raw2.entryId.length === 0)
return "lineage row has no entryId";
if (typeof raw2.sessionId !== "string" || raw2.sessionId.length === 0)
return "lineage row has no sessionId";
if (typeof raw2.lastRev !== "string" || raw2.lastRev.length === 0)
return "lineage row has no lastRev";
if (typeof raw2.lastAt !== "number")
return "lineage row has no lastAt";
if (raw2.marked !== undefined && raw2.marked !== true)
return "lineage row marked is not the literal true";
if (raw2.marked === true) {
const carried = entriesArr.find((e) => e.id === raw2.entryId);
if (carried !== undefined && committedOriginOf(carried.frontmatter) === undefined) {
return `bundle.governance.lineage row (${String(raw2.entryId)}, ${String(raw2.sessionId)}) claims marked:true but the carried entry has no origin marker (dishonest package)`;
}
}
const key = `${raw2.entryId}\u0000${raw2.sessionId}`;
if (lineagePairs.has(key))
return `bundle.governance.lineage carries (${raw2.entryId}, ${raw2.sessionId}) twice (bad package)`;
lineagePairs.add(key);
}
const custodyArr = Array.isArray(gov.custody) && gov.custody;
if (custodyArr === false)
return "bundle.governance.custody is not an array";
const custodyByEv = new Map();
const scopeLiteralOutside = (row) => {
const sideScope = (v) => {
if (!isRecord(v))
return undefined;
return typeof v.scope === "string" ? v.scope : undefined;
};
const literals = [
sideScope(row.from),
sideScope(row.to),
typeof row.scope === "string" ? row.scope : undefined,
sideScope(row.select),
];
return literals.find((s) => s !== undefined && !scopeSet.has(s));
};
for (const raw2 of custodyArr) {
if (!isRecord(raw2))
return "custody row is not an object";
if (typeof raw2.ev !== "string" || raw2.ev.length === 0)
return "custody row has no ev";
if (typeof raw2.channel !== "string" || raw2.channel.length === 0)
return "custody row has no channel";
if (typeof raw2.at !== "number" || !Number.isInteger(raw2.at))
return "custody row has no integer at";
const outside = scopeLiteralOutside(raw2);
if (outside !== undefined)
return `custody row ${JSON.stringify(raw2.ev)} names scope ${JSON.stringify(outside)} outside bundle.scopes (self-consistency: every in-package scope literal ⊆ bundle.scopes)`;
const canonical = canonicalJsonStringify(raw2);
const prior = custodyByEv.get(raw2.ev);
if (prior !== undefined && prior !== canonical)
return `bundle.governance.custody carries ev ${JSON.stringify(raw2.ev)} twice with DIFFERENT payloads (bad package)`;
custodyByEv.set(raw2.ev, canonical);
}
const res = b.residuals;
if (!isRecord(res))
return "bundle.residuals is missing";
for (const key of ["quarantined", "unbound", "pendingLatch"]) {
const bad = stringArrayInvalid(res[key], `bundle.residuals.${key}`);
if (bad !== undefined)
return bad;
}
const contentUnavailableArr = Array.isArray(res.contentUnavailable) && res.contentUnavailable;
if (contentUnavailableArr === false)
return "bundle.residuals.contentUnavailable is not an array";
for (const raw2 of contentUnavailableArr) {
if (!isRecord(raw2) || typeof raw2.id !== "string" || typeof raw2.scope !== "string" || typeof raw2.slug !== "string" || typeof raw2.rev !== "string" || typeof raw2.reason !== "string") {
return "bundle.residuals.contentUnavailable carries a malformed row";
}
if (!scopeSet.has(raw2.scope))
return `bundle.residuals.contentUnavailable row ${JSON.stringify(raw2.id)} names scope ${JSON.stringify(raw2.scope)} outside bundle.scopes (self-consistency)`;
}
for (const key of ["quarantineOpaque", "unsliceableCustody", "unboundCount", "pendingLatchCount"]) {
const bad = countInvalid(res[key], `bundle.residuals.${key}`);
if (bad !== undefined)
return bad;
}
const integrity = b.integrity;
if (!isRecord(integrity))
return "bundle.integrity is missing";
const sh = integrity.sectionHashes;
if (!isRecord(sh))
return "bundle.integrity.sectionHashes is missing";
const shKeys = Object.keys(sh).sort();
if (shKeys.length !== BUNDLE_SECTION_KEYS.length || BUNDLE_SECTION_KEYS.some((k) => !Object.prototype.hasOwnProperty.call(sh, k))) {
return `bundle.integrity.sectionHashes must carry exactly the keys ${BUNDLE_SECTION_KEYS.join("/")} (a stripped or padded section set is a bad package)`;
}
for (const k of BUNDLE_SECTION_KEYS) {
if (typeof sh[k] !== "string" || !HEX64_RE.test(sh[k]))
return `bundle.integrity.sectionHashes.${k} is not a sha256 hex`;
}
const recomputed = computeBundleSectionHashes(b);
for (const k of BUNDLE_SECTION_KEYS) {
if (recomputed[k] !== sh[k])
return `bundle.integrity.sectionHashes.${k} does not match the section's canonical content (damaged or tampered package)`;
}
const entryCount = integrity.entryCount;
if (typeof entryCount !== "number" || entryCount !== entriesArr.length)
return "bundle.integrity.entryCount does not equal entries.length";
const revs = integrity.revs;
if (!isRecord(revs))
return "bundle.integrity.revs is missing";
const revKeys = Object.keys(revs);
if (revKeys.length !== entryIds.size)
return "bundle.integrity.revs cardinality does not equal the entry id set";
for (const e of b.entries) {
if (revs[e.id] !== e.rev)
return `bundle.integrity.revs[${JSON.stringify(e.id)}] does not equal the entry's rev`;
}
return undefined;
}
export const BUNDLE_FREE_TEXT_DISCLOSURE = "Free-string fields in this bundle (pollution/challenge reasons, challenge event ids, custody request ids, session ids) are carried verbatim and may contain text the exporting deployment put there; subset-export tenant isolation does not extend to those literals.";
export function assembleMemoryExportBundle(input) {
const sections = {
bundle: 1,
at: input.at,
scopes: [...input.scopes].sort(),
storeId: input.storeId,
entries: [...input.entries].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),
governance: {
challenges: input.challenges,
pollutedSessions: [...input.pollutedSessions].sort((a, b) => (a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0)),
lineage: [...input.lineage].sort((a, b) => (a.entryId < b.entryId ? -1 : a.entryId > b.entryId ? 1 : 0) || (a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0)),
custody: input.custody,
},
residuals: input.residuals,
};
const sectionHashes = computeBundleSectionHashes(sections);
const doc = [BUNDLE_FREE_TEXT_DISCLOSURE];
if (input.residuals.unsliceableCustody > 0) {
doc.push(`This subset export could not carry ${input.residuals.unsliceableCustody} custody row(s) that have no scope to slice by (unbound-deletion history among them): the anti-resurrection guarantee for those ids does not travel with this bundle — a full-store export carries them.`);
}
return {
...sections,
integrity: {
entryCount: sections.entries.length,
revs: Object.fromEntries(sections.entries.map((e) => [e.id, e.rev])),
sectionHashes,
},
doc,
};
}