UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

97 lines (96 loc) • 3.88 kB
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; } }