@sema-agent/core
Version:
Stateless, task-oriented AI agent core
585 lines (584 loc) • 26.3 kB
JavaScript
import { closeSync, constants as FS, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { isRuleBehavior } from "../../core/permission-rule-model.js";
import { posixModeBitsMeaningful } from "../../core/posix-mode-face.js";
import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, unknownRuleWriteDeltaError, } from "../../core/permission-rule-store.js";
import { sameOrgPolicyContent } from "../../core/permission-rule-org.js";
import { canonicalize } from "../../core/canonical-json.js";
import { BootLock, FileStoreLockError } from "./fs-atomic.js";
import { assertAdoptionBootGate } from "./adoption/marker.js";
import { thrownText } from "../../core/thrown-value.js";
function isRuleFile(v) {
const p = v;
return typeof p === "object" && p !== null && p.schemaVersion === 4 && Array.isArray(p.rules) && Array.isArray(p.tombstones) && typeof p.rev === "number" && (p.quarantined === undefined || Array.isArray(p.quarantined));
}
function isOrgStateFile(v) {
const p = v;
return typeof p === "object" && p !== null && p.schemaVersion === 1 && typeof p.state === "object" && p.state !== null;
}
function schemaVersionWord(v) {
return typeof v === "object" && v !== null ? v.schemaVersion : undefined;
}
function jsonForm(v) {
return JSON.parse(JSON.stringify(v));
}
export const ORG_STATE_FILE = "org.json";
export class OrgRevisionRefusal extends Error {
}
const LOCAL_OWNER_FILE = "local-owner.json";
const ADOPTION_FILE = "PERMISSION-RULES-ADOPTION.json";
function judgeRuleAdoptionFile(dir) {
let raw;
try {
raw = readFileSync(join(dir, ADOPTION_FILE), "utf8");
}
catch (err) {
if (err?.code === "ENOENT")
return { marker: undefined };
return { corrupt: `could not be read (${err?.code ?? err?.name ?? "unknown"})`, detail: thrownText(err) };
}
let parsed;
try {
parsed = JSON.parse(raw);
}
catch (err) {
return { corrupt: `is not valid JSON (${err.message})` };
}
return isRuleAdoptionFileShape(parsed) ? { marker: parsed } : { corrupt: "does not carry a readable shape" };
}
export function readRuleAdoptionFile(dir) {
const judged = judgeRuleAdoptionFile(dir);
if ("corrupt" in judged) {
throw new Error(`the permission-rule adoption marker in ${dir} ${judged.corrupt}${judged.detail !== undefined ? `: ${judged.detail}` : ""} — refusing to resolve owners until it is repaired`);
}
return judged.marker;
}
function isRuleOwnerShape(v) {
if (v === null || typeof v !== "object")
return false;
const owner = v;
return owner.kind === "local-owner" || (owner.kind === "principal" && typeof owner.principal === "string");
}
function isRuleAdoptionFileShape(v) {
if (v === null || typeof v !== "object" || v.schemaVersion !== 1)
return false;
const hasMarker = "marker" in v;
const hasAdopted = "adopted" in v;
if (hasMarker === hasAdopted)
return false;
const rec = (hasMarker ? v.marker : v.adopted);
if (rec === null || typeof rec !== "object")
return false;
if (!isRuleOwnerShape(rec.from) || typeof rec.toPrincipal !== "string" || rec.toPrincipal === "")
return false;
return hasMarker ? rec.phase === 2 || rec.phase === 3 || rec.phase === 4 || rec.phase === 5 : typeof rec.atMs === "number" && Number.isFinite(rec.atMs);
}
export function writeRuleAdoptionFile(dir, content) {
assertSafeDir(dir);
atomicPublish(dir, join(dir, ADOPTION_FILE), JSON.stringify(content, null, 2));
}
function atomicPublish(dir, file, payload) {
const tmp = join(dir, `.${randomBytes(8).toString("hex")}.tmp`);
let fd;
try {
fd = openSync(tmp, FS.O_WRONLY | FS.O_CREAT | FS.O_EXCL | (FS.O_NOFOLLOW ?? 0), 0o600);
writeFileSync(fd, payload);
fsyncSync(fd);
closeSync(fd);
fd = undefined;
renameSync(tmp, file);
let dfd;
try {
dfd = openSync(dir, "r");
fsyncSync(dfd);
}
catch {
}
finally {
if (dfd !== undefined)
closeSync(dfd);
}
}
catch (err) {
if (fd !== undefined) {
try {
closeSync(fd);
}
catch {
}
}
try {
unlinkSync(tmp);
}
catch {
}
throw err;
}
}
const EMPTY_READ = { rules: [], tombstones: [], rev: 0 };
function checksumOf(body) {
return `sha256:${createHash("sha256").update(canonicalize(body), "utf8").digest("hex")}`;
}
function unsafeDirFault(dir, vetting) {
mkdirSync(dir, { recursive: true, mode: 0o700 });
const st = lstatSync(dir);
if (st.isSymbolicLink() || !st.isDirectory())
return "is a symlink or not a directory";
if (typeof process.getuid === "function" && st.uid !== process.getuid())
return "is owned by another user";
if (!posixModeBitsMeaningful(vetting?.platform)) {
vetting?.modeBitsUnjudged?.(dir);
return undefined;
}
if ((st.mode & 0o022) !== 0)
return `is writable by group or others (mode ${(st.mode & 0o777).toString(8)}) — anyone who can write there can grant themselves permissions`;
return undefined;
}
function assertSafeDir(dir) {
const fault = unsafeDirFault(dir);
if (fault !== undefined)
throw new Error(`permission-rule store directory ${dir} ${fault}; refusing to use it`);
}
function refusalText(onError, sentence, location) {
discloseTo(onError, `${sentence} (at ${location})`);
return `${sentence}. The location is reported to the store's error disclosure (onError), not in this message.`;
}
function discloseTo(onError, message) {
try {
onError?.(message);
}
catch {
}
}
function assertSafeDirAtRequest(dir, onError, act, vetting) {
const fault = unsafeDirFault(dir, vetting);
if (fault !== undefined)
throw new Error(refusalText(onError, `the permission-rule store directory ${fault}; refusing to ${act} it`, dir));
}
function writerLockRefusal(err, onError) {
let sentence;
switch (err.code) {
case "store.dir_in_use":
sentence = `another live process${err.ownerPid !== undefined ? ` (pid ${err.ownerPid})` : ""} holds the permission-rule store's writer lock — refusing to interleave writes with it; write through that process, or stop it`;
break;
case "store.dir_claiming":
sentence = "another process is reclaiming the permission-rule store's writer lock from a crashed owner — retry";
break;
case "store.lock_unreadable":
sentence = "the permission-rule store's writer lock is in a state only a person should resolve — remove it by hand if no process holds it";
break;
default: {
const unhandled = err.code;
sentence = `the permission-rule store's writer lock refused (${String(unhandled)})`;
}
}
return new FileStoreLockError(err.code, refusalText(onError, sentence, `${err.lockPath}: ${err.message}`), err.lockPath, err.ownerPid);
}
class FileDurableRulePartition {
dir;
file;
acquireWriteLock;
onError;
vetting;
durability = "durable";
fidelity = "json";
constructor(dir, file, acquireWriteLock, onError, vetting) {
this.dir = dir;
this.file = file;
this.acquireWriteLock = acquireWriteLock;
this.onError = onError;
this.vetting = vetting;
}
read() {
const bucket = "this owner's permission-rule bucket";
const whole = "refusing the whole file and loading zero rules — repair the file, or remove it (removing it gives up every rule and tombstone it held)";
const unreadable = (sentence, location = this.file) => ({ unreadable: refusalText(this.onError, sentence, location) });
let raw;
try {
const dirFault = unsafeDirFault(this.dir, this.vetting);
if (dirFault !== undefined)
return unreadable(`the permission-rule store directory ${dirFault}; loading zero rules`, this.dir);
const st = lstatSync(this.file);
if (st.isSymbolicLink() || !st.isFile())
return unreadable(`${bucket} is a symlink or not a regular file; refusing to load any rule from it`);
raw = readFileSync(this.file, "utf8");
}
catch (err) {
const e = err;
if (e?.code === "ENOENT")
return { absent: true };
return unreadable(`${bucket} could not be read (${e?.code ?? err?.name ?? "unknown"}); loading zero rules`, `${this.file}: ${thrownText(err)}`);
}
let parsed;
try {
parsed = JSON.parse(raw);
}
catch (err) {
return unreadable(`${bucket} is not valid JSON (${err.message}); ${whole}`);
}
if (!isRuleFile(parsed)) {
const sv = schemaVersionWord(parsed);
return unreadable(typeof sv === "number"
? `${bucket} is a schema ${String(sv)} bucket; this engine reads schema 4 only and does not migrate an older engine's bucket — refusing the whole file and loading zero rules (the bytes are left for a person to look at)`
: `${bucket} does not carry a readable rule-file shape; ${whole}`);
}
const { checksum, ...body } = parsed;
if (checksum !== checksumOf(body))
return unreadable(`${bucket} failed its integrity check; ${whole}`);
const rowKinds = [
["rule", parsed.rules],
["tombstone", parsed.tombstones],
["quarantined", parsed.quarantined ?? []],
];
for (const [kind, rows] of rowKinds) {
const bad = rows.findIndex((r) => !isRuleBehavior(r?.behavior));
if (bad !== -1)
return unreadable(`${bucket} carries a ${kind} row (#${bad}) whose behavior word is outside deny/ask/allow — a schema 4 row must name its behavior; ${whole}`);
}
return { file: parsed };
}
disclose(message) {
discloseTo(this.onError, message);
}
screenDurableRows(file) {
const isSession = (scope) => scope?.kind === "session";
const rules = file.rules.filter((r) => !isSession(r?.scope));
const tombstones = file.tombstones.filter((t) => !isSession(t?.scope));
const quarantined = (file.quarantined ?? []).filter((q) => !isSession(q?.scope));
const dropped = file.rules.length - rules.length + (file.tombstones.length - tombstones.length) + ((file.quarantined ?? []).length - quarantined.length);
if (dropped > 0) {
this.disclose(`${this.file} carries ${dropped} session-scope row(s) — a session authorization cannot live in the durable store (design/382 §4.3); those rows are ignored on every read face and never ride sync`);
}
return { rules, tombstones, quarantined };
}
write(next) {
assertSafeDirAtRequest(this.dir, this.onError, "write through", this.vetting);
atomicPublish(this.dir, this.file, JSON.stringify({ ...next, checksum: checksumOf(next) }, null, 2));
}
async list() {
const r = this.read();
if ("unreadable" in r)
throw new Error(r.unreadable);
if (!("file" in r))
return { ...EMPTY_READ };
const screened = this.screenDurableRows(r.file);
return {
rules: applyTombstones(screened.rules, screened.tombstones),
tombstones: screened.tombstones,
rev: r.file.rev,
checksum: r.file.checksum,
};
}
async quarantined() {
const r = this.read();
if ("unreadable" in r)
throw new Error(r.unreadable);
return "file" in r ? this.screenDurableRows(r.file).quarantined : [];
}
current() {
const r = this.read();
if ("unreadable" in r) {
throw new Error(`refusing to write the permission-rule store while it cannot be read: ${r.unreadable}`);
}
if ("absent" in r) {
return { schemaVersion: 4, actor: `file-${randomBytes(6).toString("hex")}`, counter: 0, rev: 0, rules: [], tombstones: [] };
}
return {
schemaVersion: r.file.schemaVersion,
actor: r.file.actor,
counter: r.file.counter,
rev: r.file.rev,
rules: r.file.rules,
tombstones: r.file.tombstones,
...(r.file.sync !== undefined ? { sync: r.file.sync } : {}),
...(r.file.quarantined !== undefined ? { quarantined: r.file.quarantined } : {}),
};
}
writeAndVerify(next) {
const expected = checksumOf(next);
this.write(next);
const after = this.read();
return "file" in after && after.file.checksum === expected;
}
writeChain = Promise.resolve();
serialize(fn) {
const run = this.writeChain.then(fn, fn);
this.writeChain = run.then(() => undefined, () => undefined);
return run;
}
get [PERMISSION_RULE_WRITER]() {
this.acquireWriteLock();
return this.writer;
}
writer = {
nextDot: async () => this.serialize(() => {
const cur = this.current();
const dot = { actor: cur.actor, counter: cur.counter + 1 };
if (!this.writeAndVerify({ ...cur, counter: dot.counter })) {
throw new Error("the permission-rule store did not survive its own write; refusing to hand out an identity");
}
return dot;
}),
readRaw: async () => this.serialize(() => {
const cur = this.current();
const screened = this.screenDurableRows(cur);
return {
actor: cur.actor,
counter: cur.counter,
rev: cur.rev,
rules: structuredClone(screened.rules),
tombstones: structuredClone(screened.tombstones),
...(cur.sync?.observedVector !== undefined ? { observedVector: structuredClone(cur.sync.observedVector) } : {}),
quarantined: structuredClone(screened.quarantined),
};
}),
apply: async (delta, opts) => this.serialize(() => {
delta = structuredClone(delta);
const cur = this.current();
if (cur.rev !== opts.expectedRev)
return { conflict: true, rev: cur.rev };
let next;
switch (delta.kind) {
case "sync-join": {
const { next: joined, report } = applySyncJoin({
actor: cur.actor,
counter: cur.counter,
rules: cur.rules,
tombstones: cur.tombstones,
quarantined: cur.quarantined ?? [],
...(cur.sync?.observedVector !== undefined ? { observedVector: cur.sync.observedVector } : {}),
}, delta, Date.now());
const landed = {
...cur,
rev: cur.rev + 1,
rules: joined.rules,
tombstones: joined.tombstones,
sync: {
...(joined.observedVector !== undefined ? { observedVector: joined.observedVector } : {}),
lastRoundAtMs: Date.now(),
},
...(joined.quarantined.length > 0 ? { quarantined: joined.quarantined } : {}),
};
if (!this.writeAndVerify(landed)) {
throw new Error("the permission-rule store did not read back as the version just written; whether the change reached the bytes on disk is unobservable from here — reconcile by reading the store");
}
return { rev: cur.rev + 1, sync: report };
}
case "redemption-add": {
assertWriteDeltaScopeDurable(delta);
assertRedemptionNotQuarantined(cur.quarantined ?? [], delta);
next = { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) };
break;
}
case "tighten-add": {
assertWriteDeltaScopeDurable(delta);
next = { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) };
break;
}
case "tighten-delete": {
assertWriteDeltaScopeDurable(delta);
assertDeleteDeltaCarriesNoAdd(delta);
next = { ...cur, rev: cur.rev + 1, tombstones: [...cur.tombstones, delta.tombstone] };
break;
}
default: {
const unhandled = delta;
throw unknownRuleWriteDeltaError(unhandled);
}
}
if (!this.writeAndVerify(next)) {
throw new Error("the permission-rule store did not read back as the version just written; whether the change reached the bytes on disk is unobservable from here — reconcile by reading the store");
}
return { rev: cur.rev + 1 };
}),
};
}
export class FileDurableRulePartitionProvider {
dir;
onError;
lock;
orgPersistence;
vetting;
constructor(dir, onError, opts) {
this.dir = dir;
this.onError = onError;
if (opts?.dataRoot !== undefined)
assertAdoptionBootGate(opts.dataRoot, "FileDurableRulePartitionProvider");
let said = false;
this.vetting = {
platform: opts?.platform,
modeBitsUnjudged: (where) => {
if (said)
return;
said = true;
discloseTo(this.onError, `the permission-rule store directory is not checked for being writable by other accounts: this platform reports no POSIX permission bits, so who else can write there is not verified here (at ${where})`);
},
};
}
adoptionMarker() {
const judged = judgeRuleAdoptionFile(this.dir);
if ("corrupt" in judged) {
const location = `${join(this.dir, ADOPTION_FILE)}${judged.detail !== undefined ? `: ${judged.detail}` : ""}`;
throw new Error(refusalText(this.onError, `the permission-rule store's adoption marker ${judged.corrupt}; refusing to resolve whose bucket this is until it is repaired`, location));
}
return judged.marker;
}
orgStatePersistence() {
return this.fileOrgPersistence();
}
fileOrgPersistence() {
this.orgPersistence ??= new FileOrgRuleStatePersistence(this.dir, this.acquireWriteLock, this.onError, this.vetting);
return this.orgPersistence;
}
acquireWriteLock = () => {
if (this.lock !== undefined)
return;
assertSafeDirAtRequest(this.dir, this.onError, "write through", this.vetting);
const lock = new BootLock(join(this.dir, "PERMISSION-RULES-WRITER-LOCK"));
try {
lock.acquire();
}
catch (err) {
throw err instanceof FileStoreLockError ? writerLockRefusal(err, this.onError) : err;
}
this.lock = lock;
};
forPrincipal(principal) {
if (typeof principal !== "string" || principal === "") {
return { list: async () => ({ ...EMPTY_READ }), durability: "process-local" };
}
const adoption = this.adoptionMarker();
if (adoption !== undefined) {
const rec = "adopted" in adoption ? adoption.adopted : adoption.marker;
if (rec.from.kind === "principal" && rec.from.principal === principal) {
return this.readOnlySourceView(principalRuleFileName(principal), principalRuleFileName(rec.toPrincipal), "adopted" in adoption || adoption.marker.phase >= 4);
}
}
return new FileDurableRulePartition(this.dir, join(this.dir, principalRuleFileName(principal)), this.acquireWriteLock, this.onError, this.vetting);
}
readOnlySourceView(sourceName, targetName, flipped) {
const pick = flipped ? targetName : existsSync(join(this.dir, sourceName)) ? sourceName : targetName;
return readOnlyRuleStoreView(new FileDurableRulePartition(this.dir, join(this.dir, pick), this.acquireWriteLock, this.onError, this.vetting));
}
forLocalOwner() {
const adoption = this.adoptionMarker();
if (adoption !== undefined) {
const rec = "adopted" in adoption ? adoption.adopted : adoption.marker;
if (rec.from.kind === "local-owner") {
return this.readOnlySourceView(LOCAL_OWNER_FILE, principalRuleFileName(rec.toPrincipal), "adopted" in adoption || adoption.marker.phase >= 4);
}
}
return new FileDurableRulePartition(this.dir, join(this.dir, LOCAL_OWNER_FILE), this.acquireWriteLock, this.onError, this.vetting);
}
dispose() {
this.lock?.release();
this.lock = undefined;
}
}
class FileOrgRuleStatePersistence {
dir;
acquireWriteLock;
onError;
vetting;
file;
writeChain = Promise.resolve();
constructor(dir, acquireWriteLock, onError, vetting) {
this.dir = dir;
this.acquireWriteLock = acquireWriteLock;
this.onError = onError;
this.vetting = vetting;
this.file = join(dir, ORG_STATE_FILE);
}
readSync() {
const refuse = (sentence, location = this.file) => {
throw new Error(refusalText(this.onError, `${sentence}; refusing it whole — repair the file, or remove it (removing it gives up the last-known-good org snapshot and its anti-rollback mark)`, location));
};
const ioFault = (err) => `could not be read (${err?.code ?? err?.name ?? "unknown"})`;
let dirFault;
try {
dirFault = unsafeDirFault(this.dir, this.vetting);
}
catch (err) {
return refuse(`the permission-rule store directory ${ioFault(err)}`, `${this.dir}: ${thrownText(err)}`);
}
if (dirFault !== undefined)
return refuse(`the permission-rule store directory ${dirFault}`, this.dir);
const unreadable = (err) => {
if (err?.code === "ENOENT")
return undefined;
return refuse(`the org state file ${ioFault(err)}`, `${this.file}: ${thrownText(err)}`);
};
let st;
try {
st = lstatSync(this.file);
}
catch (err) {
return unreadable(err);
}
if (st.isSymbolicLink() || !st.isFile())
return refuse("the org state file is a symlink or not a regular file");
let raw;
try {
raw = readFileSync(this.file, "utf8");
}
catch (err) {
return unreadable(err);
}
let parsed;
try {
parsed = JSON.parse(raw);
}
catch (err) {
return refuse(`the org state file is not valid JSON (${err.message})`);
}
if (!isOrgStateFile(parsed))
return refuse("the org state file does not carry a readable org-state shape");
const { checksum, ...body } = parsed;
if (checksum !== checksumOf(body))
return refuse("the org state file failed its integrity check");
return parsed.state;
}
async readOrgState() {
return this.readSync();
}
installOrgStateSync(state) {
this.acquireWriteLock();
assertSafeDirAtRequest(this.dir, this.onError, "write through", this.vetting);
state = jsonForm(state);
const cur = this.readSync();
if (cur !== undefined && state.revisionHighWater < cur.revisionHighWater) {
throw new OrgRevisionRefusal(`refusing to lower the org revision high-water mark (${cur.revisionHighWater} → ${state.revisionHighWater}) — an interleaved install must re-read and re-compare`);
}
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision && !sameOrgPolicyContent(state.snapshot, cur.snapshot)) {
throw new OrgRevisionRefusal(`refusing to replace org revision ${state.snapshot.revision} with DIFFERENT policy content under the same revision — content changes must bump the revision`);
}
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision && state.snapshot.fetchedAtMs <= cur.snapshot.fetchedAtMs)
return;
const body = { schemaVersion: 1, state };
atomicPublish(this.dir, this.file, JSON.stringify({ ...body, checksum: checksumOf(body) }, null, 2));
const after = this.readSync();
if (after === undefined || canonicalize(after) !== canonicalize(state)) {
const why = "the org state file did not read back as the version just written; whether the snapshot reached the bytes on disk is unobservable from here — reconcile by reading the file";
this.onError?.(why);
throw new Error(why);
}
}
async installOrgState(state) {
const run = this.writeChain.then(() => this.installOrgStateSync(state), () => this.installOrgStateSync(state));
this.writeChain = run.then(() => undefined, () => undefined);
await run;
}
}
function readOnlyRuleStoreView(store) {
return {
list: () => store.list(),
...(store.quarantined !== undefined ? { quarantined: () => store.quarantined() } : {}),
...(store.durability !== undefined ? { durability: store.durability } : {}),
...(store.fidelity !== undefined ? { fidelity: store.fidelity } : {}),
};
}
export function principalRuleFileName(principal) {
return `${createHash("sha256").update(principal, "utf8").digest("hex")}.json`;
}
export { LOCAL_OWNER_FILE };