@sema-agent/core
Version:
Stateless, task-oriented AI agent core
851 lines (850 loc) • 42 kB
JavaScript
import { randomBytes } from "node:crypto";
import { canonicalize } from "./canonical-json.js";
import { escapeForDisclosure, isRuleBehavior, isRuleLive, parseRuleText } from "./permission-rule-model.js";
import { thrownText } from "./thrown-value.js";
export function sameRuleOwner(a, b) {
return a.kind === "principal" ? b.kind === "principal" && a.principal === b.principal : b.kind === "local-owner";
}
export function isRuleRevision(rev) {
return typeof rev === "number" && Number.isSafeInteger(rev) && rev >= 0;
}
function outcomeWord(value) {
if (value === undefined)
return "no revision at all";
if (value === null)
return "a null revision";
if (typeof value === "number")
return `the number ${String(value)}, which is not a revision`;
return `a ${typeof value} where a revision belongs`;
}
function isReadableDrop(drop) {
if (drop === null || typeof drop !== "object")
return false;
const dot = drop.dot;
if (dot === null || typeof dot !== "object")
return false;
return typeof dot.actor === "string" && typeof dot.counter === "number" && typeof drop.reason === "string";
}
function readSyncReport(sync) {
if (sync === undefined)
return {};
if (sync === null || typeof sync !== "object")
return { unreadable: `the landing carried a ${sync === null ? "null" : typeof sync} where its sync report belongs` };
for (const member of ["droppedInbound", "quarantined"]) {
const value = sync[member];
if (value === undefined)
continue;
if (!Array.isArray(value))
return { unreadable: `the landing's sync report carried a ${typeof value} as \`${member}\`, which a reader cannot walk` };
const bad = value.findIndex((row) => !isReadableDrop(row));
if (bad !== -1)
return { unreadable: `the landing's sync report carried a \`${member}\` entry (#${String(bad)}) that is not a drop record, so what was dropped cannot be read` };
}
return { report: sync };
}
export function readRuleWriteOutcome(res) {
if (res === null || typeof res !== "object") {
return { kind: "unreadable", why: `the store answered ${res === null ? "null" : `a ${typeof res}`} where a write outcome belongs` };
}
const tagged = res;
const refusal = tagged.refused;
const conflict = tagged.conflict;
if (refusal !== undefined && conflict !== undefined) {
return { kind: "unreadable", why: "the store answered a refusal and a decline at once, and an answer that is two arms is not an answer about either" };
}
if (refusal !== undefined) {
return typeof refusal === "string" && refusal !== ""
? { kind: "refused", refused: refusal }
: { kind: "unreadable", why: `the store tagged its answer as a refusal and named it with a ${refusal === null ? "null" : typeof refusal}, so which refusal it is cannot be read` };
}
if (conflict !== undefined) {
if (conflict !== true)
return { kind: "unreadable", why: `the store tagged its answer as a decline with \`${String(conflict)}\`, which does not say a compare-and-set lost` };
return isRuleRevision(tagged.rev)
? { kind: "conflict", rev: tagged.rev }
: { kind: "unreadable", why: `the store declined the write with ${outcomeWord(tagged.rev)}, so the revision to re-anchor the next attempt to is unknown` };
}
if (!isRuleRevision(tagged.rev)) {
return { kind: "unreadable", why: `the store answered neither a conflict nor a refusal, and carried ${outcomeWord(tagged.rev)} — a landing reports the revision it committed at, so this answer cannot say whether the write landed` };
}
const { report, unreadable } = readSyncReport(tagged.sync);
return { kind: "landed", rev: tagged.rev, ...(report !== undefined ? { sync: report } : {}), ...(unreadable !== undefined ? { reportUnreadable: unreadable } : {}) };
}
export function unknownRuleWriteDeltaError(delta) {
const kind = delta?.kind;
return new Error(`the permission-rule store cannot apply a write delta of kind ${escapeForDisclosure(typeof kind === "string" ? kind : String(kind))} — the write face folds exactly four named arms (redemption-add, tighten-add, tighten-delete, sync-join), and an unnamed one is refused rather than read as any of them`);
}
export const PERMISSION_RULE_WRITER = "__semaPermissionRuleWriter";
export function writerOf(store) {
const w = store[PERMISSION_RULE_WRITER];
return w !== undefined && typeof w.apply === "function" && typeof w.nextDot === "function" ? w : undefined;
}
export function sameScope(a, b) {
if (a.kind === "global")
return b.kind === "global";
if (a.kind === "session")
return b.kind === "session" && a.sessionId === b.sessionId;
return b.kind === "project" && a.root === b.root;
}
function sameDot(a, b) {
return a.actor === b.actor && a.counter === b.counter;
}
export function sameRuleIdentity(a, b) {
return a.behavior === b.behavior && a.rule === b.rule && sameScope(a.scope, b.scope);
}
export function applyTombstones(rules, tombstones) {
return rules
.map((r) => {
const removed = tombstones.filter((t) => sameRuleIdentity(t, r)).flatMap((t) => t.removedDots);
return removed.length === 0 ? r : { ...r, adds: r.adds.filter((a) => !removed.some((d) => sameDot(d, a.dot))) };
})
.filter(isRuleLive);
}
export function foldDelta(rules, delta) {
const next = rules.map((r) => ({ ...r, adds: [...r.adds] }));
const existing = next.find((r) => sameRuleIdentity(r, delta));
if (existing === undefined) {
next.push({ behavior: delta.behavior, rule: delta.rule, tool: delta.tool, match: delta.match, command: delta.command, scope: delta.scope, adds: [delta.add] });
return next;
}
if (!existing.adds.some((a) => sameDot(a.dot, delta.add.dot)))
existing.adds.push(delta.add);
return next;
}
export function addDotsOf(rules) {
return rules.flatMap((r) => r.adds.map((a) => a.dot));
}
const ADD_ONLY_KEYS = ["add", "redemption", "rule", "command", "match", "tool", "behavior"];
export function assertDeleteDeltaCarriesNoAdd(delta) {
const smuggled = ADD_ONLY_KEYS.filter((k) => Object.hasOwn(delta, k));
if (smuggled.length > 0) {
throw new Error(`a tighten-delete may not introduce a new add (it carried: ${smuggled.join(", ")})`);
}
if (!Array.isArray(delta.tombstone?.removedDots) || delta.tombstone.removedDots.length === 0) {
throw new Error("a tighten-delete must carry a tombstone naming at least one observed add dot");
}
}
export function assertWriteDeltaScopeDurable(delta) {
const scope = delta.kind === "tighten-delete" ? delta.tombstone?.scope : delta.scope;
if (scope?.kind === "session") {
const e = new Error(delta.kind === "tighten-delete"
? "a session-scope tombstone cannot enter the persisted store — session rows are not individually deletable and die with their session (design/382 §4.3/§10); refusing the tighten-delete"
: `a session-scope rule cannot enter the persisted store — its home is the session's own overlay (design/382 §4.3); refusing the ${delta.kind}`);
e.code = "unsupported.session_scope_store";
throw e;
}
if (!isValidDurableScope(scope)) {
throw new Error(delta.kind === "tighten-delete"
?
"a tighten-delete must carry a tombstone whose scope is a well-formed durable scope (design/382 §4.3: the face is {global, project-with-a-root}) — refusing the write"
: `a ${delta.kind} whose scope is not a well-formed durable scope cannot enter the persisted store (design/382 §4.3: the face is {global, project-with-a-root}) — refusing the write`);
}
}
export function isValidConsentScope(scope) {
const sc = scope;
return (sc?.kind === "global" ||
(sc?.kind === "project" && typeof sc.root === "string" && sc.root !== "") ||
(sc?.kind === "session" && typeof sc.sessionId === "string" && sc.sessionId !== ""));
}
export function isValidDurableScope(scope) {
const sc = scope;
return sc?.kind === "global" || (sc?.kind === "project" && typeof sc.root === "string" && sc.root !== "");
}
function isValidDot(dot) {
const d = dot;
return typeof d?.actor === "string" && d.actor !== "" && typeof d.counter === "number" && Number.isFinite(d.counter);
}
function scopeKey(scope) {
return JSON.stringify(scope.kind === "global" ? ["g"] : scope.kind === "session" ? ["s", scope.sessionId] : ["p", scope.root]);
}
function dotKey(dot) {
return JSON.stringify([dot.actor, dot.counter]);
}
function ruleIdentityKey(behavior, rule, scope) {
return JSON.stringify([behavior, rule, scopeKey(scope)]);
}
function compareDots(a, b) {
return a.actor < b.actor ? -1 : a.actor > b.actor ? 1 : a.counter - b.counter;
}
export function dotAtOrBelowFrontier(dot, frontier) {
const cap = frontier[dot.actor];
return typeof cap === "number" && Number.isFinite(cap) && dot.counter <= cap;
}
export function ruleSyncVector(state) {
const out = {};
const feed = (dot) => {
const cur = out[dot.actor];
if (cur === undefined || dot.counter > cur)
out[dot.actor] = dot.counter;
};
for (const r of state.rules)
for (const a of r.adds)
feed(a.dot);
for (const t of state.tombstones) {
feed(t.deletedBy);
for (const d of t.removedDots)
feed(d);
}
return out;
}
export function joinFrontiers(a, b) {
const out = { ...a };
for (const [actor, counter] of Object.entries(b)) {
const cur = out[actor];
if (cur === undefined || counter > cur)
out[actor] = counter;
}
return out;
}
export function normalizePersistedRule(r) {
if (!isRuleBehavior(r.behavior))
return { reject: "invalid_rule_text" };
const parsed = parseRuleText(r.rule, r.behavior);
if ("reject" in parsed)
return { reject: "invalid_rule_text" };
const p = parsed.rule;
if (p.rule !== r.rule)
return { reject: "invalid_rule_text" };
if (r.tool !== p.tool || r.match !== p.match || r.command !== p.command)
return { reject: "metadata_mismatch" };
return { rule: r };
}
export function screenRuleSyncState(state) {
const rejected = [];
const rules = [];
for (const r of state.rules) {
if (!Array.isArray(r.adds) || r.adds.length === 0)
continue;
if (r.scope?.kind === "session") {
for (const a of r.adds)
rejected.push({ behavior: r.behavior, rule: r.rule, scope: r.scope, dot: a.dot, reason: "session_scope_not_durable" });
continue;
}
const n = normalizePersistedRule(r);
if ("reject" in n) {
for (const a of r.adds)
rejected.push({ behavior: r.behavior, rule: r.rule, scope: r.scope, dot: a.dot, reason: n.reject });
continue;
}
rules.push(r);
}
const tombstones = [];
for (const t of state.tombstones) {
if (t?.scope?.kind === "session") {
rejected.push({ behavior: t.behavior, rule: t.rule, scope: t.scope, dot: t.deletedBy, reason: "session_scope_not_durable" });
continue;
}
if (!isRuleBehavior(t.behavior)) {
rejected.push({ behavior: t.behavior, rule: t.rule, scope: t.scope, dot: t.deletedBy, reason: "invalid_rule_text" });
continue;
}
tombstones.push(t);
}
return { state: { rules, tombstones }, rejected };
}
export function joinRuleStates(a, b) {
const sa = screenRuleSyncState(a).state;
const sb = screenRuleSyncState(b).state;
const ruleGroups = new Map();
for (const r of [...sa.rules, ...sb.rules]) {
const key = ruleIdentityKey(r.behavior, r.rule, r.scope);
const existing = ruleGroups.get(key);
if (existing === undefined) {
ruleGroups.set(key, { ...r, adds: [...r.adds] });
continue;
}
for (const add of r.adds) {
const i = existing.adds.findIndex((x) => sameDot(x.dot, add.dot));
if (i === -1)
existing.adds.push({ ...add });
else if (canonicalize(add) < canonicalize(existing.adds[i]))
existing.adds[i] = { ...add };
}
}
const rules = [...ruleGroups.values()]
.map((r) => ({ ...r, adds: [...r.adds].sort((x, y) => compareDots(x.dot, y.dot)) }))
.sort((x, y) => compareIdentity(x, y));
const tombGroups = new Map();
for (const t of [...sa.tombstones, ...sb.tombstones]) {
if (!Array.isArray(t.removedDots) || t.removedDots.length === 0)
continue;
const key = JSON.stringify([t.behavior, t.rule, scopeKey(t.scope), dotKey(t.deletedBy)]);
const existing = tombGroups.get(key);
if (existing === undefined) {
tombGroups.set(key, { ...t, removedDots: [...t.removedDots] });
continue;
}
for (const d of t.removedDots)
if (!existing.removedDots.some((x) => sameDot(x, d)))
existing.removedDots.push(d);
}
const tombstones = [...tombGroups.values()]
.map((t) => ({ ...t, removedDots: [...t.removedDots].sort(compareDots) }))
.sort((x, y) => compareIdentity(x, y) || compareDots(x.deletedBy, y.deletedBy));
return { rules, tombstones };
}
function compareIdentity(x, y) {
if (x.rule !== y.rule)
return x.rule < y.rule ? -1 : 1;
const sx = scopeKey(x.scope);
const sy = scopeKey(y.scope);
if (sx !== sy)
return sx < sy ? -1 : 1;
return x.behavior === y.behavior ? 0 : x.behavior < y.behavior ? -1 : 1;
}
export function collectBelowFrontier(state, frontier) {
const collectable = state.tombstones.filter((t) => dotAtOrBelowFrontier(t.deletedBy, frontier) && t.removedDots.every((d) => dotAtOrBelowFrontier(d, frontier)));
if (collectable.length === 0)
return { state, collectedTombstones: 0 };
const keep = state.tombstones.filter((t) => !collectable.includes(t));
const rules = state.rules
.map((r) => {
const covered = collectable.filter((t) => sameRuleIdentity(t, r)).flatMap((t) => t.removedDots);
return covered.length === 0 ? r : { ...r, adds: r.adds.filter((a) => !covered.some((d) => sameDot(d, a.dot))) };
})
.filter((r) => r.adds.length > 0);
return { state: { rules, tombstones: keep }, collectedTombstones: collectable.length };
}
export function applySyncJoin(cur, delta, nowMs) {
if (delta.kind !== "sync-join" || delta.inbound === null || typeof delta.inbound !== "object" || !Array.isArray(delta.inbound.rules) || !Array.isArray(delta.inbound.tombstones)) {
throw new Error("a sync-join delta must carry an inbound state with rules and tombstones arrays");
}
for (const t of delta.inbound.tombstones) {
if (t?.scope?.kind === "session")
continue;
if (!Array.isArray(t?.removedDots) || t.removedDots.length === 0) {
throw new Error("an inbound tombstone must name at least one observed add dot — refusing the whole sync-join (the client's partitioning must withhold the round's adds alongside a malformed tombstone)");
}
if (!isValidDurableScope(t.scope) || !isValidDot(t.deletedBy) || !t.removedDots.every(isValidDot)) {
throw new Error("an inbound tombstone carries a malformed scope or dot — refusing the whole sync-join (a tombstone that cannot identity-match what it deletes would land its covered adds live)");
}
if (!isRuleBehavior(t.behavior)) {
throw new Error("an inbound tombstone carries a behavior word outside deny/ask/allow — refusing the whole sync-join (a tombstone that cannot identity-match what it deletes would land its covered adds live)");
}
const canonicalTomb = parseRuleText(t.rule, t.behavior);
if ("reject" in canonicalTomb || canonicalTomb.rule.rule !== t.rule) {
throw new Error(`an inbound tombstone names a non-canonical or invalid rule text ("${escapeForDisclosure(t.rule)}") — refusing the whole sync-join (the client's partitioning must withhold the round's adds alongside a malformed tombstone)`);
}
}
const droppedInbound = [];
const quarantinedNow = [];
const screened = screenRuleSyncState(delta.inbound);
droppedInbound.push(...screened.rejected);
const forgedDot = (dot) => dot.actor === cur.actor && dot.counter > cur.counter;
const dotIdentity = new Map();
for (const r of cur.rules)
for (const a of r.adds)
dotIdentity.set(dotKey(a.dot), ruleIdentityKey(r.behavior, r.rule, r.scope));
for (const q of cur.quarantined)
dotIdentity.set(dotKey(q.add.dot), ruleIdentityKey(q.behavior, q.rule, q.scope));
const inboundClaims = new Map();
for (const r of screened.state.rules) {
const identity = ruleIdentityKey(r.behavior, r.rule, r.scope);
for (const a of r.adds) {
const claims = inboundClaims.get(dotKey(a.dot)) ?? new Set();
claims.add(identity);
inboundClaims.set(dotKey(a.dot), claims);
}
}
const inboundTombstones = screened.state.tombstones.filter((t) => {
const forged = forgedDot(t.deletedBy) ? t.deletedBy : t.removedDots.find(forgedDot);
if (forged === undefined)
return true;
droppedInbound.push({ behavior: t.behavior, rule: t.rule, scope: t.scope, dot: forged, reason: "own_actor_forged" });
return false;
});
const tombstoneRejected = inboundTombstones.length !== screened.state.tombstones.length;
let withheldAdds = 0;
const inboundRules = [];
for (const r of screened.state.rules) {
if (tombstoneRejected) {
withheldAdds += r.adds.length;
continue;
}
if (!isValidDurableScope(r.scope)) {
for (const a of r.adds)
droppedInbound.push({ behavior: r.behavior, rule: r.rule, scope: r.scope, dot: a.dot, reason: "metadata_mismatch" });
continue;
}
const identity = ruleIdentityKey(r.behavior, r.rule, r.scope);
const kept = r.adds.filter((a) => {
if (!isValidDot(a.dot)) {
droppedInbound.push({ behavior: r.behavior, rule: r.rule, scope: r.scope, dot: a.dot, reason: "metadata_mismatch" });
return false;
}
if (forgedDot(a.dot)) {
droppedInbound.push({ behavior: r.behavior, rule: r.rule, scope: r.scope, dot: a.dot, reason: "own_actor_forged" });
return false;
}
const known = dotIdentity.get(dotKey(a.dot));
if ((known !== undefined && known !== identity) || (inboundClaims.get(dotKey(a.dot))?.size ?? 0) > 1) {
droppedInbound.push({ behavior: r.behavior, rule: r.rule, scope: r.scope, dot: a.dot, reason: "dot_identity_conflict" });
return false;
}
return true;
});
if (kept.length > 0)
inboundRules.push({ ...r, adds: kept });
}
const inbound = { rules: inboundRules, tombstones: inboundTombstones };
const localScreen = screenRuleSyncState({ rules: cur.rules, tombstones: cur.tombstones });
const quarantined = [...cur.quarantined];
const quarantineKey = (behavior, rule, scope, dot) => JSON.stringify([behavior, rule, scopeKey(scope), dotKey(dot)]);
const pushQuarantine = (behavior, rule, scope, add, reason) => {
if (quarantined.some((q) => sameRuleIdentity(q, { behavior, rule, scope }) && sameDot(q.add.dot, add.dot)))
return;
quarantined.push({ behavior, rule, scope, add, reason, atMs: nowMs });
quarantinedNow.push({ behavior, rule, scope, dot: add.dot, reason });
};
for (const rej of localScreen.rejected) {
const row = cur.rules.find((r) => sameRuleIdentity(r, rej));
const add = row?.adds.find((a) => sameDot(a.dot, rej.dot));
if (add !== undefined && (rej.reason === "invalid_rule_text" || rej.reason === "metadata_mismatch")) {
pushQuarantine(rej.behavior, rej.rule, rej.scope, add, rej.reason);
}
}
let joined = joinRuleStates(localScreen.state, inbound);
let collectedTombstones = 0;
if (delta.gcFrontier !== undefined) {
const collected = collectBelowFrontier(joined, delta.gcFrontier);
joined = collected.state;
collectedTombstones = collected.collectedTombstones;
const inboundDots = new Set(inbound.rules.flatMap((r) => r.adds.map((a) => dotKey(a.dot))));
const live = applyTombstones(joined.rules, joined.tombstones);
for (const r of live) {
for (const a of r.adds) {
if (dotAtOrBelowFrontier(a.dot, delta.gcFrontier) && !inboundDots.has(dotKey(a.dot))) {
pushQuarantine(r.behavior, r.rule, r.scope, a, "below_gc_frontier");
}
}
}
}
for (const q of delta.quarantine ?? []) {
if (q.scope?.kind === "session") {
const e = new Error("a quarantine instruction names a session scope — the persisted store never holds a session row (design/382 §4.3); refusing the instruction");
e.code = "unsupported.session_scope_store";
throw e;
}
for (const dot of q.dots) {
if (joined.tombstones.some((t) => sameDot(t.deletedBy, dot))) {
throw new Error("a quarantine instruction may not touch a tombstone — it moves adds only");
}
const row = joined.rules.find((r) => sameRuleIdentity(r, q));
const add = row?.adds.find((a) => sameDot(a.dot, dot));
if (add === undefined)
continue;
pushQuarantine(q.behavior, q.rule, q.scope, add, q.reason);
}
}
const quarantinedRows = new Set(quarantined.map((q) => quarantineKey(q.behavior, q.rule, q.scope, q.add.dot)));
const rules = joined.rules
.map((r) => ({ ...r, adds: r.adds.filter((a) => !quarantinedRows.has(quarantineKey(r.behavior, r.rule, r.scope, a.dot))) }))
.filter((r) => r.adds.length > 0);
return {
next: {
actor: cur.actor,
counter: cur.counter,
rules,
tombstones: joined.tombstones,
quarantined,
observedVector: delta.observedVector !== undefined ? joinFrontiers(cur.observedVector ?? {}, delta.observedVector) : cur.observedVector,
},
report: structuredClone({ droppedInbound, quarantined: quarantinedNow, collectedTombstones, withheldAdds }),
};
}
export function assertRedemptionNotQuarantined(quarantined, delta) {
const hit = quarantined.find((q) => sameDot(q.add.dot, delta.add.dot));
if (hit !== undefined) {
throw new Error(`this approval's add (dot ${delta.add.dot.actor}#${delta.add.dot.counter}) was quarantined (${hit.reason}) — replaying the old record cannot revive it; a new consent (a new ask, a new record, a new dot) is required`);
}
}
export async function ruleStoreChecksum(payload) {
const { createHash } = await import("node:crypto");
return `sha256:${createHash("sha256").update(canonicalize(payload), "utf8").digest("hex")}`;
}
export const REMOVAL_LIVENESS_WORDS = Object.freeze(["yes", "no", "unknown"]);
export const RULE_WRITE_COMMITTED_WORDS = Object.freeze(["no", "unknown"]);
function resolveRuleWriter(store) {
let w;
try {
w = writerOf(store);
}
catch (err) {
return { unavailable: `the permission-rule store's write face could not be acquired (${errText(err)})`, committed: "unknown" };
}
return w === undefined ? { unavailable: "the resolved permission-rule store has no write face", committed: "no" } : { writer: w };
}
function ruleIdentityStands(rules, identity) {
return rules.some((r) => sameRuleIdentity(r, identity) && isRuleLive(r));
}
async function witnessRuleAdd(store, writer, delta) {
const identity = { behavior: delta.behavior, rule: delta.rule, scope: delta.scope };
const dot = delta.add.dot;
try {
if (delta.scope.kind === "session") {
const view = await store.effective({ sessionId: delta.scope.sessionId });
if (view.unreadable !== undefined)
return { unreadable: view.unreadable };
return { seen: view.rules.some((r) => sameRuleIdentity(r, identity) && r.adds.some((a) => sameDot(a.dot, dot))), rev: view.rev };
}
const raw = await writer.readRaw();
const inRows = raw.rules.some((r) => sameRuleIdentity(r, identity) && r.adds.some((a) => sameDot(a.dot, dot)));
const inQuarantine = (raw.quarantined ?? []).some((q) => sameDot(q.add.dot, dot));
return { seen: inRows || inQuarantine, rev: raw.rev };
}
catch (err) {
return { unreadable: errText(err) };
}
}
async function readRuleLiveness(store, identity) {
try {
const after = await store.effective();
if (after.unreadable !== undefined)
throw new Error(after.unreadable);
return ruleIdentityStands(after.rules, identity) ? "yes" : "no";
}
catch {
return "unknown";
}
}
const REMOVE_MAX_ATTEMPTS = 8;
export async function removePersistedRule(opts) {
const behavior = opts.behavior;
const rule = opts.rule;
const scope = structuredClone(opts.scope);
const target = opts.principal;
if (!isRuleBehavior(behavior)) {
return { status: "failed", error: `removePersistedRule: behavior ${escapeForDisclosure(behavior)} is not one of ${["deny", "ask", "allow"].join("/")} — refusing to name a rule identity nothing can carry`, committed: "no" };
}
const identity = { behavior, rule, scope };
const owner = typeof target === "string" ? { kind: "principal", principal: target } : structuredClone(target);
const provider = opts.provider;
if (scope?.kind === "session") {
return {
status: "failed",
error: "unsupported.session_scope_store: a session-scope rule cannot be removed through the persisted-store entry — session authorizations live in the session's own overlay and end with the session (design/382 §4.3)",
committed: "no",
};
}
if (owner.kind === "local-owner" && provider.forLocalOwner === undefined) {
return { status: "failed", error: "this provider has no local-owner bucket (forLocalOwner is not implemented) — a local-owner rule cannot be removed through it", committed: "no" };
}
const store = owner.kind === "local-owner" ? provider.forLocalOwner() : provider.forPrincipal(owner.principal);
const face = resolveRuleWriter(store);
if ("unavailable" in face) {
return { status: "failed", error: `${face.unavailable} — rules cannot be removed through it`, committed: face.committed };
}
const writer = face.writer;
let snapshot;
try {
snapshot = await store.effective();
}
catch (err) {
return { status: "failed", error: `could not read the permission-rule store: ${errText(err)}`, committed: "no" };
}
if (snapshot.unreadable !== undefined)
return { status: "failed", error: `could not read the permission-rule store: ${snapshot.unreadable}`, committed: "no" };
const live = snapshot.rules.filter((r) => sameRuleIdentity(r, identity));
const removedDots = live.flatMap((r) => r.adds.map((a) => a.dot));
if (removedDots.length === 0)
return { status: "no-op", rev: snapshot.rev, stillLive: await readRuleLiveness(store, identity) };
let deletedBy;
try {
deletedBy = await writer.nextDot();
}
catch (err) {
return { status: "failed", error: `could not mint the removal's delete dot: ${errText(err)}`, committed: "no" };
}
const tombstone = { behavior, rule, scope, removedDots, deletedBy };
let expectedRev = snapshot.rev;
let lastError = "";
let settlement = "no";
for (let attempt = 0; attempt < REMOVE_MAX_ATTEMPTS; attempt++) {
try {
const reading = readRuleWriteOutcome(await writer.apply({ kind: "tighten-delete", tombstone }, { expectedRev }));
if (reading.kind === "refused") {
return { status: "failed", error: `the store refused the tombstone (${reading.refused}) — nothing was removed`, committed: "no" };
}
if (reading.kind === "unreadable") {
settlement = "unknown";
lastError = `the permission-rule store answered the write unreadably: ${reading.why}`;
break;
}
if (reading.kind === "landed") {
return { status: "removed", rev: reading.rev, stillLive: await readRuleLiveness(store, identity) };
}
expectedRev = reading.rev;
}
catch (err) {
settlement = "unknown";
lastError = errText(err);
break;
}
}
const reason = settlement === "unknown"
? lastError !== ""
? lastError
: "the permission-rule store threw while applying the tombstone, with no message"
: `optimistic-concurrency retries exhausted after ${REMOVE_MAX_ATTEMPTS} attempts`;
const committed = settlement;
try {
const after = await writer.readRaw();
const landed = after.tombstones.some((t) => t.deletedBy.actor === tombstone.deletedBy.actor && t.deletedBy.counter === tombstone.deletedBy.counter);
if (landed) {
return { status: "removed", rev: after.rev, stillLive: ruleIdentityStands(applyTombstones(after.rules, after.tombstones), identity) ? "yes" : "no" };
}
return {
status: "failed",
error: settlement === "unknown"
? `${reason}; the store retains no tombstone for this removal, which cannot tell a write that never landed from one whose evidence was collected — the outcome is indeterminate, reconcile with list()`
: `${reason} (read-back confirms no tombstone was written)`,
committed,
};
}
catch (err) {
return {
status: "failed",
error: settlement === "unknown"
? `${reason}; the read-back also failed (${errText(err)}) so this outcome is indeterminate — reconcile with list()`
: `${reason}; the read-back also failed (${errText(err)}), but every attempt was answered by the store, so nothing was written`,
committed,
};
}
}
const RULE_ADD_MAX_ATTEMPTS = 8;
const SESSION_ENDED_REFUSAL = "the session this authorization targets has ended — a session rule dies with its session, and a late or replayed redemption cannot revive it (re-trigger the command in a live session for a fresh card)";
export async function landRuleAdd(args) {
switch (args.delta.kind) {
case "redemption-add":
case "tighten-add":
break;
default: {
const unreachable = args.delta;
throw new Error(`landRuleAdd: unhandled add delta ${JSON.stringify(unreachable)}`);
}
}
for (let attempt = 0; attempt < RULE_ADD_MAX_ATTEMPTS; attempt++) {
let rev;
try {
const snapshot = await args.store.effective();
if (snapshot.unreadable !== undefined)
throw new Error(snapshot.unreadable);
rev = snapshot.rev;
const refusal = await args.preflight?.(snapshot);
if (refusal !== undefined)
return { landed: false, reason: refusal, committed: "no" };
}
catch (err) {
return { landed: false, reason: `could not read the permission-rule store: ${errText(err)}`, committed: "no" };
}
let res;
try {
res = await args.writer.apply(args.delta, { expectedRev: rev });
}
catch (err) {
return { landed: false, reason: `the permission-rule store refused the write: ${errText(err)}`, committed: "unknown" };
}
const reading = readRuleWriteOutcome(res);
if (reading.kind === "refused")
return { landed: false, reason: SESSION_ENDED_REFUSAL, committed: "no" };
if (reading.kind === "unreadable")
return { landed: false, reason: `the permission-rule store answered the write unreadably: ${reading.why}`, committed: "unknown" };
if (reading.kind === "landed") {
const witness = await witnessRuleAdd(args.store, args.writer, args.delta);
if ("unreadable" in witness) {
return { landed: false, reason: `the permission-rule store reported a landing at revision ${String(reading.rev)}, and the read-back that would have witnessed it could not be taken (${witness.unreadable})`, committed: "unknown" };
}
if (!witness.seen) {
return { landed: false, reason: `the permission-rule store reported a landing at revision ${String(reading.rev)}, and a read-back of this add's own dot finds no trace of it — a revision says the ANSWER was well formed, not that a row exists, so nothing here can say the write landed`, committed: "unknown" };
}
return { landed: true, rev: reading.rev };
}
}
return { landed: false, reason: `optimistic-concurrency retries exhausted after ${RULE_ADD_MAX_ATTEMPTS} attempts`, committed: "no" };
}
export async function addPersistedRule(opts) {
const behavior = opts.behavior;
const ruleText = opts.rule;
const scope = structuredClone(opts.scope);
const target = opts.principal;
if (!isRuleBehavior(behavior)) {
return { status: "failed", error: `addPersistedRule: behavior ${escapeForDisclosure(behavior)} is not one of ${["deny", "ask", "allow"].join("/")} — refusing to name a rule identity nothing can carry`, committed: "no" };
}
if (behavior === "allow") {
return {
status: "failed",
error: "addPersistedRule: an allow cannot be written through this entry — a standing approval is a person's yes and takes a consent record (prepare → confirm → redeem); this entry writes the tightening direction (deny/ask) only",
committed: "no",
};
}
if (!isValidConsentScope(scope)) {
return { status: "failed", error: `addPersistedRule: ${escapeForDisclosure(JSON.stringify(scope))} is not a valid rule scope — refusing to write a row nothing can read back`, committed: "no" };
}
const parsed = parseRuleText(ruleText, behavior);
if ("reject" in parsed)
return { status: "failed", error: `addPersistedRule: ${parsed.reject.code}: ${parsed.reject.message}`, committed: "no" };
const identity = { behavior, rule: parsed.rule.rule, scope };
const owner = typeof target === "string" ? { kind: "principal", principal: target } : structuredClone(target);
const provider = opts.provider;
if (owner.kind === "local-owner" && provider.forLocalOwner === undefined) {
return { status: "failed", error: "this provider has no local-owner bucket (forLocalOwner is not implemented) — a local-owner rule cannot be added through it", committed: "no" };
}
const store = owner.kind === "local-owner" ? provider.forLocalOwner() : provider.forPrincipal(owner.principal);
const face = resolveRuleWriter(store);
if ("unavailable" in face) {
return { status: "failed", error: `${face.unavailable} — rules cannot be added through it`, committed: face.committed };
}
const writer = face.writer;
const query = scope.kind === "session" ? { sessionId: scope.sessionId } : undefined;
let snapshot;
try {
snapshot = await store.effective(query);
}
catch (err) {
return { status: "failed", error: `could not read the permission-rule store: ${errText(err)}`, committed: "no" };
}
if (snapshot.unreadable !== undefined)
return { status: "failed", error: `could not read the permission-rule store: ${snapshot.unreadable}`, committed: "no" };
if (snapshot.rules.some((r) => sameRuleIdentity(r, identity) && isRuleLive(r)))
return { status: "no-op", rev: snapshot.rev };
let dot;
try {
dot = await writer.nextDot();
}
catch (err) {
return { status: "failed", error: `could not mint the add's dot: ${errText(err)}`, committed: "no" };
}
const landing = await landRuleAdd({
store,
writer,
delta: {
kind: "tighten-add",
behavior,
rule: parsed.rule.rule,
scope,
tool: parsed.rule.tool,
match: parsed.rule.match,
command: parsed.rule.command,
add: { dot, origin: "user", createdAt: new Date().toISOString() },
},
});
if (landing.landed)
return { status: "added", rev: landing.rev, dot };
if (landing.committed === "no")
return { status: "failed", error: landing.reason, committed: landing.committed };
try {
let rev;
let rows;
let quarantinedRows = [];
if (scope.kind === "session") {
const view = await store.effective(query);
if (view.unreadable !== undefined)
throw new Error(view.unreadable);
({ rev, rules: rows } = view);
}
else {
const raw = await writer.readRaw();
({ rev, rules: rows } = raw);
quarantinedRows = raw.quarantined ?? [];
}
const stood = rows.some((r) => sameRuleIdentity(r, identity) && r.adds.some((a) => sameDot(a.dot, dot))) ||
quarantinedRows.some((q) => sameDot(q.add.dot, dot));
return stood
? { status: "added", rev, dot }
: { status: "failed", error: `${landing.reason}; the store retains no trace of this add, which cannot tell a write that never landed from one whose evidence was collected — the outcome is indeterminate, reconcile with list()`, committed: landing.committed };
}
catch (err) {
return { status: "failed", error: `${landing.reason}; the read-back also failed (${errText(err)}) so this outcome is indeterminate — reconcile with list()`, committed: landing.committed };
}
}
export function errText(err) {
return thrownText(err);
}
export const EMPTY_DURABLE_RULE_PARTITION = {
list: async () => ({ rules: [], tombstones: [], rev: 0 }),
durability: "process-local",
};
export class InMemoryDurableRulePartition {
actor;
now;
durability = "process-local";
fidelity = "structured-clone";
rules = [];
tombstones = [];
quarantinedRows = [];
observedVector;
rev = 0;
counter = 0;
constructor(actor = `mem-${randomBytes(6).toString("base64url")}`, now = Date.now) {
this.actor = actor;
this.now = now;
}
async list() {
return {
rules: structuredClone(applyTombstones(this.rules, this.tombstones)),
tombstones: structuredClone(this.tombstones),
rev: this.rev,
};
}
async quarantined() {
return structuredClone(this.quarantinedRows);
}
[PERMISSION_RULE_WRITER] = {
nextDot: async () => ({ actor: this.actor, counter: ++this.counter }),
readRaw: async () => structuredClone({
actor: this.actor,
counter: this.counter,
rev: this.rev,
rules: this.rules,
tombstones: this.tombstones,
...(this.observedVector !== undefined ? { observedVector: this.observedVector } : {}),
quarantined: this.quarantinedRows,
}),
apply: async (delta, opts) => {
delta = structuredClone(delta);
if (opts.expectedRev !== this.rev)
return { conflict: true, rev: this.rev };
switch (delta.kind) {
case "redemption-add": {
assertWriteDeltaScopeDurable(delta);
assertRedemptionNotQuarantined(this.quarantinedRows, delta);
this.rules = foldDelta(this.rules, delta);
break;
}
case "tighten-add": {
assertWriteDeltaScopeDurable(delta);
this.rules = foldDelta(this.rules, delta);
break;
}
case "tighten-delete": {
assertWriteDeltaScopeDurable(delta);
assertDeleteDeltaCarriesNoAdd(delta);
this.tombstones = [...this.tombstones, delta.tombstone];
break;
}
case "sync-join": {
const { next, report } = applySyncJoin({
actor: this.actor,
counter: this.counter,
rules: this.rules,
tombstones: this.tombstones,
quarantined: this.quarantinedRows,
...(this.observedVector !== undefined ? { observedVector: this.observedVector } : {}),
}, delta, this.now());
this.rules = next.rules;
this.tombstones = next.tombstones;
this.quarantinedRows = next.quarantined;
this.observedVector = next.observedVector;
this.rev += 1;
return { rev: this.rev, sync: report };
}
default: {
const unhandled = delta;
throw unknownRuleWriteDeltaError(unhandled);
}
}
this.rev += 1;
return { rev: this.rev };
},
};
}