@sema-agent/core
Version:
Stateless, task-oriented AI agent core
97 lines (96 loc) • 3.88 kB
JavaScript
import { isWithin } from "./session-rule-policy.js";
export function swallowedFenceRoot(domainCanonical, fenceRootsCanonical) {
for (const root of fenceRootsCanonical) {
if (root && isWithin(domainCanonical, root))
return root;
}
return undefined;
}
export class AdvertisedWritableDirs {
env;
onSkipped;
declaredRootSpellings;
admitted = [];
verdicts = new Map();
fenceRoots;
declaredRootsResolved;
declaredRootAnswers = new Map();
constructor(env, onSkipped, taskRootCanonical, declaredRootSpellings = []) {
this.env = env;
this.onSkipped = onSkipped;
this.declaredRootSpellings = declaredRootSpellings;
this.fenceRoots = [taskRootCanonical];
}
declareFenceRoot(canonical) {
if (canonical && !this.fenceRoots.includes(canonical))
this.fenceRoots.push(canonical);
}
async canonicalizeDeclaredRoot(spelled) {
const memo = this.declaredRootAnswers.get(spelled);
if (memo !== undefined)
return memo;
const c = await this.env.canonicalPath(spelled);
const first = this.declaredRootAnswers.get(spelled);
if (first !== undefined)
return first;
this.declaredRootAnswers.set(spelled, c);
if (c.ok)
this.declareFenceRoot(c.value);
return c;
}
resolveDeclaredRoots() {
this.declaredRootsResolved ??= (async () => {
for (const spelled of this.declaredRootSpellings) {
if (!spelled || !spelled.trim())
continue;
await this.canonicalizeDeclaredRoot(spelled);
}
})();
return this.declaredRootsResolved;
}
get roots() {
return this.admitted;
}
verdictOf(advertiser) {
return this.verdicts.get(advertiser) ?? "not-asked";
}
async admit(dir) {
const ok = await this.judge(dir);
this.verdicts.set(dir.advertiser, ok ? "admitted" : "skipped");
return ok;
}
async judge(dir) {
if (!dir.path || !dir.path.trim()) {
this.onSkipped(dir, "empty path");
return false;
}
let c = await this.env.canonicalPath(dir.path);
if (!c.ok && c.error.code === "not_found" && dir.materializeMarker !== undefined) {
const marker = await this.env.joinPath([dir.path, dir.materializeMarker]);
const exclusiveCreate = this.env.writeFileExclusive?.bind(this.env);
if (marker.ok && exclusiveCreate) {
const w = await exclusiveCreate(marker.value, "");
if (w.ok || w.error.code === "already_exists")
c = await this.env.canonicalPath(dir.path);
}
}
if (!c.ok) {
this.onSkipped(dir, `${c.error.code}: ${c.error.message}`);
return false;
}
if (dir.expectCanonical !== undefined && c.value !== dir.expectCanonical) {
this.onSkipped(dir, `the execution environment resolves it to ${c.value} while the advertiser's gates key on ${dir.expectCanonical} — a fence hole those gates cannot see is not opened`);
return false;
}
if (this.admitted.includes(c.value))
return true;
await this.resolveDeclaredRoots();
const swallowed = swallowedFenceRoot(c.value, [...this.fenceRoots, ...this.admitted]);
if (swallowed !== undefined) {
this.onSkipped(dir, `it resolves to ${c.value}, which covers ${swallowed === this.fenceRoots[0] ? "task root" : "root"} ${swallowed} — a directory that holds a root is not a hole in the fence but the whole of it, and admitting it would auto-approve every write under that root`);
return false;
}
this.admitted.push(c.value);
return true;
}
}