UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

107 lines (106 loc) • 5.88 kB
import { existsSync, renameSync } from "node:fs"; import { join } from "node:path"; import { writerOf } from "../../core/permission-rule-store.js"; import { createPermissionRuleStoreProvider } from "../../core/permission-rule-provider.js"; import { syncPermissionRules } from "../../core/permission-rule-sync.js"; import { thrownText } from "../../core/thrown-value.js"; import { FileStoreLockError } from "./fs-atomic.js"; import { FileDurableRulePartitionProvider, LOCAL_OWNER_FILE, principalRuleFileName, readRuleAdoptionFile, writeRuleAdoptionFile, } from "./permission-rule-store.js"; export async function adoptFilePermissionRuleStore(opts) { if (typeof opts.toPrincipal !== "string" || opts.toPrincipal === "") { throw new Error("adoptFilePermissionRuleStore requires the adopting principal — adoption is the act of giving this bucket an identity"); } if (opts.from.kind === "principal" && opts.from.principal === opts.toPrincipal) { throw new Error(`the bucket already belongs to "${opts.toPrincipal}" — there is nothing to adopt`); } const sourceFile = join(opts.dir, opts.from.kind === "local-owner" ? LOCAL_OWNER_FILE : principalRuleFileName(opts.from.principal)); const targetFile = join(opts.dir, principalRuleFileName(opts.toPrincipal)); const existing = readRuleAdoptionFile(opts.dir); if (existing !== undefined) { if ("adopted" in existing) { if (existing.adopted.toPrincipal === opts.toPrincipal && sameOwner(existing.adopted.from, opts.from)) return { status: "adopted" }; throw new Error(`this directory already carries a completed adoption (${ownerText(existing.adopted.from)} → "${existing.adopted.toPrincipal}") — a second adoption of the same directory is not a shape this helper supports`); } if (existing.marker.toPrincipal !== opts.toPrincipal || !sameOwner(existing.marker.from, opts.from)) { throw new Error(`an adoption toward "${existing.marker.toPrincipal}" is already in flight in this directory — refusing to start a different one (resume the original, or repair the marker by hand)`); } } else if (existsSync(targetFile)) { throw new Error(`the target bucket for "${opts.toPrincipal}" already exists — merging two existing buckets goes through an explicit sync join, not a rename`); } const provider = new FileDurableRulePartitionProvider(opts.dir, opts.onError); try { let lockProbe; try { lockProbe = writerOf(provider.forPrincipal(opts.toPrincipal)); } catch (err) { if (!(err instanceof FileStoreLockError)) throw err; throw new FileStoreLockError(err.code, `the permission-rule writer lock ${err.lockPath} refuses the adoption (${err.code}${err.ownerPid !== undefined ? `, held by pid ${err.ownerPid}` : ""}) — stop the live writer, then run the adoption again`, err.lockPath, err.ownerPid); } if (lockProbe === undefined) throw new Error("the target store refused a write face"); let phase = existing !== undefined && "marker" in existing ? existing.marker.phase : 2; if (existing === undefined) { writeRuleAdoptionFile(opts.dir, marker(opts.from, opts.toPrincipal, 2)); phase = 2; } if (phase < 3) { const sourcePresent = existsSync(sourceFile); const targetPresent = existsSync(targetFile); if (sourcePresent && targetPresent) { throw new Error(`both the source and the target bucket exist — merging two existing buckets goes through an explicit sync join, not a rename; the adoption marker is left in place for inspection`); } if (sourcePresent) renameSync(sourceFile, targetFile); writeRuleAdoptionFile(opts.dir, marker(opts.from, opts.toPrincipal, 3)); phase = 3; } if (phase < 4) { writeRuleAdoptionFile(opts.dir, marker(opts.from, opts.toPrincipal, 4)); phase = 4; } let syncResult; if (phase < 5) { try { syncResult = await syncPermissionRules({ provider: createPermissionRuleStoreProvider({ durable: provider }), principal: opts.toPrincipal, transport: opts.transport, ...(opts.now !== undefined ? { now: opts.now } : {}), }); } catch (err) { return { status: "stalled", phase: 4, error: `the first sync round failed: ${thrownText(err)}` }; } if (!syncResult.ok) { return { status: "stalled", phase: 4, error: `the first sync round did not land clean (${(syncResult.warnings ?? []).join("; ") || `${syncResult.dropped.length} drop(s)`}) — resume when the endpoint is healthy`, }; } writeRuleAdoptionFile(opts.dir, marker(opts.from, opts.toPrincipal, 5)); phase = 5; } writeRuleAdoptionFile(opts.dir, { schemaVersion: 1, adopted: { from: opts.from, toPrincipal: opts.toPrincipal, atMs: (opts.now ?? Date.now)() }, }); return { status: "adopted", ...(syncResult !== undefined ? { syncResult } : {}) }; } finally { provider.dispose(); } } function marker(from, toPrincipal, phase) { return { schemaVersion: 1, marker: { from, toPrincipal, phase } }; } function sameOwner(a, b) { return a.kind === "principal" ? b.kind === "principal" && a.principal === b.principal : b.kind === "local-owner"; } function ownerText(o) { return o.kind === "principal" ? `principal "${o.principal}"` : "the local owner"; }