@sema-agent/core
Version:
Stateless, task-oriented AI agent core
220 lines (219 loc) • 15.9 kB
JavaScript
import assert from "node:assert";
import { collectBelowFrontier, joinRuleStates, ruleSyncVector } from "../permission-rule-store.js";
import { beginContract } from "./contract-harness.js";
const GLOBAL = { kind: "global" };
function add(actor, counter, origin = "user") {
return { dot: { actor, counter }, origin, createdAt: "2026-01-01T00:00:00.000Z" };
}
function rule(text, adds, scope = GLOBAL, behavior = "allow") {
const m = /^Bash\((.+?)(:\*)?\)$/.exec(text);
if (m === null || m[1] === undefined)
throw new Error(`contract fixture rule "${text}" is not a Bash(...) form`);
return { behavior, rule: text, tool: "Bash", match: m[2] !== undefined ? "prefix" : "exact", command: m[1], scope, adds };
}
function tomb(text, removed, deletedBy, scope = GLOBAL, behavior = "allow") {
return {
behavior,
rule: text,
scope,
removedDots: removed.map(([actor, counter]) => ({ actor, counter })),
deletedBy: { actor: deletedBy[0], counter: deletedBy[1] },
};
}
function state(rules = [], tombstones = []) {
return { rules, tombstones };
}
function liveDots(s) {
const out = new Set();
for (const r of s.rules) {
const removed = s.tombstones.filter((t) => t.behavior === r.behavior && t.rule === r.rule && sameScope(t.scope, r.scope)).flatMap((t) => t.removedDots);
for (const a of r.adds) {
if (!removed.some((d) => d.actor === a.dot.actor && d.counter === a.dot.counter))
out.add(`${r.behavior === "allow" ? "" : r.behavior + ":"}${r.rule}|${a.dot.actor}#${a.dot.counter}`);
}
}
return out;
}
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 allDots(s) {
return new Set(s.rules.flatMap((r) => r.adds.map((a) => `${r.behavior === "allow" ? "" : r.behavior + ":"}${r.rule}|${a.dot.actor}#${a.dot.counter}`)));
}
export async function permissionRuleSyncContract(hooks = {}) {
const join = hooks.join ?? joinRuleStates;
const collect = hooks.collect ?? collectBelowFrontier;
const vector = hooks.vector ?? ruleSyncVector;
const { run, settle } = beginContract(hooks.runAssertion);
const samples = () => [
state(),
state([rule("Bash(ls)", [add("a", 1)])]),
state([rule("Bash(ls)", [add("b", 1)]), rule("Bash(git status)", [add("b", 2)])]),
state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]),
state([rule("Bash(pwd)", [add("c", 5)], { kind: "project", root: "/repo" })], [tomb("Bash(date)", [["d", 9]], ["c", 6])]),
];
run("join is idempotent: a ⊔ a = a (M11)", async () => {
for (const s of samples())
assert.deepStrictEqual(join(s, s), join(s, join(s, s)), "a ⊔ a must equal (a ⊔ a) ⊔ a");
for (const s of samples())
assert.deepStrictEqual(liveDots(join(s, s)), liveDots(s), "a ⊔ a must not change the live view");
});
run("join is commutative: a ⊔ b = b ⊔ a", async () => {
const pool = samples();
for (const a of pool)
for (const b of pool)
assert.deepStrictEqual(join(a, b), join(b, a));
});
run("join is associative and order/repetition independent (M12)", async () => {
const [, a, b, c] = samples();
assert.ok(a && b && c);
const one = join(join(a, b), c);
const two = join(a, join(b, c));
const three = join(join(c, a), join(b, a));
assert.deepStrictEqual(one, two);
assert.deepStrictEqual(one, three);
});
run("M1: a single-sided add propagates", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1)])]), state());
assert.ok(liveDots(merged).has("Bash(ls)|a#1"));
});
run("M2: a multi-dot delete covers every observed dot", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1), add("a", 2)])], [tomb("Bash(ls)", [["a", 1], ["a", 2]], ["a", 3])]), state());
assert.strictEqual(liveDots(merged).size, 0, "both observed dots must be dead");
assert.strictEqual(merged.tombstones.length, 1);
assert.strictEqual(merged.tombstones[0]?.removedDots.length, 2, "the tombstone must carry BOTH observed dots");
});
run("M3: concurrent approvals of the same text coexist under their own dots", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1)])]), state([rule("Bash(ls)", [add("b", 1)])]));
assert.deepStrictEqual(liveDots(merged), new Set(["Bash(ls)|a#1", "Bash(ls)|b#1"]));
assert.strictEqual(merged.rules.length, 1, "one logical rule, two adds — not two rule rows");
});
run("M4: add-wins — an add the deleter never observed survives the tombstone", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]), state([rule("Bash(ls)", [add("b", 7)])]));
assert.deepStrictEqual(liveDots(merged), new Set(["Bash(ls)|b#7"]), "dot a#1 dead, unobserved b#7 alive");
});
run("M5: deletes on both sides union to cover everything", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]), state([rule("Bash(ls)", [add("b", 7)])], [tomb("Bash(ls)", [["b", 7]], ["b", 8])]));
assert.strictEqual(liveDots(merged).size, 0);
});
run("M6: a deliberate re-add under a NEW dot survives the old tombstone", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1), add("a", 3)])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]), state());
assert.deepStrictEqual(liveDots(merged), new Set(["Bash(ls)|a#3"]));
});
run("M7: a double delete is idempotent — distinct deletedBy tombstones both stand, semantics equal", async () => {
const a = state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]);
const b = state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["b", 5])]);
const merged = join(a, b);
assert.strictEqual(merged.tombstones.length, 2, "two delete intents, two identities — neither is dropped");
assert.strictEqual(liveDots(merged).size, 0);
assert.deepStrictEqual(join(a, b), join(b, a));
});
run("M8: partially-observed deletes union to full coverage — each tombstone honest about its own view", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1), add("a", 2)])], [tomb("Bash(ls)", [["a", 1], ["a", 2]], ["a", 3])]), state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["b", 1])]));
assert.strictEqual(liveDots(merged).size, 0, "the union of observations must cover both dots");
});
run("M9: provenance rides each add and follows the surviving dot", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1, "imported-cc")])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]), state([rule("Bash(ls)", [add("b", 7, "user")])]));
const survivor = merged.rules.find((r) => r.rule === "Bash(ls)");
assert.ok(survivor);
const liveAdds = survivor.adds.filter((x) => !merged.tombstones.some((t) => t.removedDots.some((d) => d.actor === x.dot.actor && d.counter === x.dot.counter)));
assert.strictEqual(liveAdds.length, 1);
assert.strictEqual(liveAdds[0]?.origin, "user", "the display origin must be the SURVIVING add's own, not a folded one");
});
run("M10: replaying the same dot is deduplicated by identity", async () => {
const merged = join(state([rule("Bash(ls)", [add("a", 1)])]), state([rule("Bash(ls)", [add("a", 1)])]));
assert.strictEqual(merged.rules[0]?.adds.length, 1);
});
run("M13: a tombstone whose (rule, scope) is locally absent is kept — arriving before its add is legal", async () => {
const merged = join(state(), state([], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]));
assert.strictEqual(merged.tombstones.length, 1);
const then = join(merged, state([rule("Bash(ls)", [add("a", 1), add("a", 4)])]));
assert.deepStrictEqual(liveDots(then), new Set(["Bash(ls)|a#4"]), "the pre-arrived tombstone must cover a#1 when it lands");
});
run("tombstone-union (r1-F3): same identity, differing removedDots ⇒ the union, both orders", async () => {
const a = state([], [tomb("Bash(ls)", [["a", 1]], ["a", 9])]);
const b = state([], [tomb("Bash(ls)", [["a", 2]], ["a", 9])]);
const ab = join(a, b);
assert.strictEqual(ab.tombstones.length, 1, "same (rule, scope, deletedBy) is ONE identity");
assert.deepStrictEqual(new Set(ab.tombstones[0]?.removedDots.map((d) => `${d.actor}#${d.counter}`)), new Set(["a#1", "a#2"]), "the union — a one-sided pick can drop an observed removal");
assert.deepStrictEqual(ab, join(b, a));
});
run("behavior is an identity member: the same text as an allow and as a deny are two rows, each with its own adds and its own tombstone", async () => {
const both = state([rule("Bash(ls)", [add("a", 1)]), rule("Bash(ls)", [add("a", 2)], GLOBAL, "deny")], [tomb("Bash(ls)", [["a", 1]], ["a", 3])]);
const merged = join(both, state());
assert.strictEqual(merged.rules.length, 2, "one text, two behaviors — two identity groups, never folded");
assert.deepStrictEqual(liveDots(merged), new Set(["deny:Bash(ls)|a#2"]), "the allow tombstone must not reach the deny's add");
assert.deepStrictEqual(join(state(), both), merged, "commutative across the behavior axis");
const badWord = state([rule("Bash(pwd)", [add("b", 1)])], [Object.assign(tomb("Bash(pwd)", [["b", 1]], ["b", 2]), { behavior: "maybe" })]);
const screened = join(badWord, state());
assert.strictEqual(screened.tombstones.length, 0, "an unreadable behavior word on a tombstone must not land");
assert.ok(liveDots(screened).has("Bash(pwd)|b#1"), "…and must not cover anything");
});
run("closed-set identity: a row carrying a behavior word outside the closed set is refused like invalid text", async () => {
const forgedWord = Object.assign(rule("Bash(ls)", [add("z", 1)]), { behavior: "sometimes" });
const merged = join(state([forgedWord]), state());
assert.strictEqual(allDots(merged).size, 0, "a behavior this engine cannot read must not become an allow (or a deny) by falling through");
});
run("metadata re-projection (r2-F2): a record whose stored fields contradict its rule text is refused, both orders", async () => {
const forged = { behavior: "allow", rule: "Bash(ls)", tool: "Bash", match: "prefix", command: "rm -rf /", scope: GLOBAL, adds: [add("z", 1)] };
const clean = state([rule("Bash(git status)", [add("a", 1)])]);
const ab = join(state([forged]), clean);
const ba = join(clean, state([forged]));
assert.ok(!allDots(ab).has("Bash(ls)|z#1"), "the forged record must not survive the join");
assert.deepStrictEqual(ab, ba, "refusal must preserve commutativity");
assert.ok(allDots(ab).has("Bash(git status)|a#1"), "refusing one record must not disturb the rest");
});
run("single-validator door: an illegal ALLOW shape (bare-interpreter prefix) cannot ride a join — while the same text as a DENY is a legitimate wide tightening and lands", async () => {
const merged = join(state([rule("Bash(node:*)", [add("z", 1)])]), state());
assert.strictEqual(allDots(merged).size, 0, "Bash(node:*) must be refused by the shared validator inside the join");
const denied = join(state([rule("Bash(node:*)", [add("z", 1)], GLOBAL, "deny")]), state());
assert.deepStrictEqual(allDots(denied), new Set(["deny:Bash(node:*)|z#1"]), "the tightening floor admits the interpreter-headed prefix");
});
run("durable two-member face (design/382 §4.3): a session-scope row or tombstone never survives a join, and never poisons the round", async () => {
const sessionScope = { kind: "session", sessionId: "sess-1" };
const dirty = state([rule("Bash(ls)", [add("z", 1)], sessionScope), rule("Bash(git status)", [add("a", 1)])], [tomb("Bash(pwd)", [["z", 2]], ["z", 3], sessionScope)]);
const ab = join(dirty, state());
const ba = join(state(), dirty);
assert.ok(!allDots(ab).has("Bash(ls)|z#1"), "a session-scope add must not survive the join — the durable face holds two members");
assert.strictEqual(ab.tombstones.length, 0, "a session-scope tombstone must not survive the join");
assert.ok(allDots(ab).has("Bash(git status)|a#1"), "dropping the session row must not withhold or disturb the durable rest — per-row, never round-poisoning");
assert.deepStrictEqual(ab, ba, "the session-scope drop must preserve commutativity");
});
run("identity keys are collision-free: roots and actors containing spaces keep DISTINCT tombstone identities, and the covered add stays dead through an identity join", async () => {
const scopeA = { kind: "project", root: "/x" };
const scopeB = { kind: "project", root: "/x a" };
const s = state([rule("Bash(ls)", [add("v", 1)], scopeA), rule("Bash(ls)", [add("w", 1)], scopeB)], [
{ behavior: "allow", rule: "Bash(ls)", scope: scopeA, removedDots: [{ actor: "v", counter: 1 }], deletedBy: { actor: "a 1", counter: 2 } },
{ behavior: "allow", rule: "Bash(ls)", scope: scopeB, removedDots: [{ actor: "w", counter: 1 }], deletedBy: { actor: "1", counter: 2 } },
]);
const merged = join(s, state());
assert.strictEqual(merged.tombstones.length, 2, "two distinct delete intents must both survive — a colliding key folds one away");
assert.strictEqual(liveDots(merged).size, 0, "both covered adds must stay dead");
assert.deepStrictEqual(join(merged, merged), merged, "idempotence must hold with space-carrying roots/actors");
});
run("state vector: max over add dots ∪ deletedBy ∪ removedDots (§5.1)", async () => {
const v = vector(state([rule("Bash(ls)", [add("a", 3)])], [tomb("Bash(git status)", [["a", 7], ["b", 2]], ["c", 4])]));
assert.deepStrictEqual(v, { a: 7, b: 2, c: 4 });
});
run("paired collection: a fully-below tombstone leaves WITH its covered adds; a partial one stays (§5.1)", async () => {
const s = state([rule("Bash(ls)", [add("a", 1)]), rule("Bash(pwd)", [add("a", 2), add("b", 9)])], [tomb("Bash(ls)", [["a", 1]], ["a", 3]), tomb("Bash(pwd)", [["a", 2], ["b", 9]], ["a", 4])]);
const { state: out, collectedTombstones } = collect(s, { a: 5 });
assert.strictEqual(collectedTombstones, 1, "only the fully-below pair is collectable (b#9 is above the frontier)");
assert.ok(!allDots(out).has("Bash(ls)|a#1"), "the covered add leaves WITH its tombstone — never one without the other");
assert.ok(allDots(out).has("Bash(pwd)|b#9"), "an above-frontier dot must never be collected");
assert.strictEqual(out.tombstones.length, 1, "the partially-below tombstone stays");
assert.deepStrictEqual(liveDots(out), liveDots(s));
});
run("collection + rejoin does not resurrect: the recycled pair stays gone through further joins", async () => {
const s = state([rule("Bash(ls)", [add("a", 1)])], [tomb("Bash(ls)", [["a", 1]], ["a", 2])]);
const { state: collected } = collect(s, { a: 2 });
const rejoined = join(collected, collected);
assert.strictEqual(liveDots(rejoined).size, 0);
assert.strictEqual(allDots(rejoined).size, 0, "neither the add nor the tombstone may reappear from a self-join");
});
await settle();
}