UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

2,663 lines • 113 kB
import { closeSync, constants as fsConstants, copyFileSync, existsSync, fstatSync, fsyncSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync, writeSync } from "node:fs";
const { O_WRONLY, O_RDONLY, O_CREAT, O_NOFOLLOW, O_NONBLOCK, O_EXCL } = fsConstants;
import { homedir } from "node:os";
import { createHash, randomUUID } from "node:crypto";
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
import { MEMORY_ORIGIN_CAUSES } from "./types.js";
import { physicalPathOf, physicalPathOfExisting, priorCallerCasedPathOf } from "../physical-path.js";
import { thrownLabel, thrownText } from "../thrown-value.js";
const SCOPES_FILE = "scopes.json";
export const CURSORS_FILE = "cursors.json";
export const CONTROL_PLANE_DIR = ".engine";
export class ControlPlaneCorruptError extends Error {
    facet;
    constructor(message, opts) {
        super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);
        this.name = "ControlPlaneCorruptError";
        if (opts?.facet !== undefined)
            this.facet = opts.facet;
    }
}
function slugifyPath(absPath) {
    return absPath.normalize("NFC").replace(/[^A-Za-z0-9]/g, "-");
}
export function deriveRepoKey(repoRoot) {
    return slugifyPath(canonicalize(resolve(repoRoot)));
}
function deriveDirKey(dir) {
    return slugifyPath(resolve(dir));
}
function subtreeHasFiles(dir) {
    let entries;
    try {
        entries = readdirSync(dir, { withFileTypes: true });
    }
    catch {
        return true;
    }
    for (const e of entries) {
        if (!e.isDirectory())
            return true;
        if (subtreeHasFiles(join(dir, e.name)))
            return true;
    }
    return false;
}
function pruneFilelessDirTree(dir) {
    let entries;
    try {
        entries = readdirSync(dir, { withFileTypes: true });
    }
    catch {
        return !existsSync(dir);
    }
    for (const e of entries) {
        if (!e.isDirectory())
            return false;
        if (!pruneFilelessDirTree(join(dir, e.name)))
            return false;
    }
    try {
        rmdirSync(dir);
        return true;
    }
    catch {
        return false;
    }
}
function tryAdoptByRename(fromDir, toDir) {
    try {
        renameSync(fromDir, toDir);
        return { ok: true };
    }
    catch (err) {
        return { ok: false, code: err.code, error: err };
    }
}
function migrateKeyedPartition(m) {
    const disclose = (code, message) => {
        try {
            const e = new Error(message);
            e.code = code;
            m.onIncident?.(e);
        }
        catch {
        }
    };
    const splitText = `memory partition split detected for ${m.subject}: BOTH ${m.fromDir} and ${m.toDir} hold content. This session uses ${m.toDir}; the other dir is left untouched (never merged — CAS baselines would collide). If it holds the newer memory, move its rows by hand and remove it.`;
    try {
        if (m.fromDir === m.toDir || canonicalize(m.fromDir) === canonicalize(m.toDir))
            return;
        if (!existsSync(m.fromDir))
            return;
        if (existsSync(m.toDir) && subtreeHasFiles(m.toDir)) {
            disclose("memory.partition_split", splitText);
            return;
        }
        ensureDirExists(dirname(m.toDir));
        let outcome = tryAdoptByRename(m.fromDir, m.toDir);
        if (!outcome.ok && (outcome.code === "EEXIST" || outcome.code === "ENOTEMPTY") && !subtreeHasFiles(m.toDir) && pruneFilelessDirTree(m.toDir)) {
            outcome = tryAdoptByRename(m.fromDir, m.toDir);
        }
        if (outcome.ok)
            return;
        const { code, error } = outcome;
        if ((code === "ENOENT" || code === "EEXIST" || code === "ENOTEMPTY") && existsSync(m.toDir)) {
            if (!existsSync(m.fromDir))
                return;
            disclose("memory.partition_split", splitText);
            return;
        }
        disclose("memory.partition_adopt_failed", `could not adopt the memory partition ${m.fromDir} → ${m.toDir} (${code ?? thrownLabel(error)}): this session proceeds on ${m.toDir}; ` +
            `${m.fromDir} still holds the prior memory — move it by hand.`);
    }
    catch {
    }
}
export function adoptLegacyRepoDirs(configRoot, repoRoot, onIncident) {
    const toDir = join(configRoot, deriveRepoKey(repoRoot));
    for (const key of earlierRepoKeysOf(repoRoot)) {
        migrateKeyedPartition({ fromDir: join(configRoot, key), toDir, subject: repoRoot, onIncident });
    }
}
export function earlierRepoKeysOf(repoRoot) {
    const today = deriveRepoKey(repoRoot);
    const keys = [deriveDirKey(repoRoot)];
    try {
        keys.push(slugifyPath(priorCallerCasedPathOf(resolve(repoRoot))));
    }
    catch {
    }
    return [...new Set(keys)].filter((k) => k !== today);
}
export function adoptCanonicalKeyedControlDir(configRoot, dir, onIncident) {
    migrateKeyedPartition({
        fromDir: join(configRoot, deriveRepoKey(dir), CONTROL_PLANE_DIR),
        toDir: join(configRoot, deriveDirKey(dir), CONTROL_PLANE_DIR),
        subject: dir,
        onIncident,
    });
}
export function deriveRepoMemoryDir(configRoot, repoRoot) {
    return join(configRoot, deriveRepoKey(repoRoot), "memory");
}
export function deriveProjectMemoryDir(configRoot, projectId) {
    return join(configRoot, `proj-${projectId.toLowerCase()}`, "memory");
}
export function deriveProjectControlDir(configRoot, projectId) {
    return join(configRoot, `proj-${projectId.toLowerCase()}`, CONTROL_PLANE_DIR);
}
export const PROJECT_ID_HINTS_FILE = "project-id-hints.json";
export function recordProjectIdHint(configRoot, repoRoot, projectId) {
    try {
        const path = join(configRoot, PROJECT_ID_HINTS_FILE);
        let hints = {};
        try {
            const parsed = JSON.parse(readFileSync(path, "utf8"));
            if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
                hints = parsed;
        }
        catch {
        }
        const key = resolve(repoRoot);
        if (hints[key] === projectId.toLowerCase())
            return;
        hints[key] = projectId.toLowerCase();
        mkdirSync(configRoot, { recursive: true });
        atomicWriteFileSync(path, `${JSON.stringify(hints, null, 2)}\n`);
    }
    catch {
    }
}
export function lookupProjectIdHint(configRoot, repoRoot) {
    try {
        const parsed = JSON.parse(readFileSync(join(configRoot, PROJECT_ID_HINTS_FILE), "utf8"));
        if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
            return undefined;
        const v = parsed[resolve(repoRoot)];
        return typeof v === "string" ? v : undefined;
    }
    catch {
        return undefined;
    }
}
export function deriveControlPlaneDir(configRoot, key) {
    return join(configRoot, deriveDirKey(key), CONTROL_PLANE_DIR);
}
export function deriveRepoControlPlaneDir(configRoot, repoRoot) {
    return join(configRoot, deriveRepoKey(repoRoot), CONTROL_PLANE_DIR);
}
export function effectiveControlRootOf(dir, opts = {}) {
    const pinned = opts.pinnedBy?.controlPlaneRoot;
    if (typeof pinned === "string" && pinned !== "")
        return pinned;
    if (opts.controlDir !== undefined)
        return opts.controlDir;
    const configRoot = resolveMemoryEngineRoot();
    adoptCanonicalKeyedControlDir(configRoot, dir, opts.onIncident);
    return deriveControlPlaneDir(configRoot, dir);
}
export function resolveMemoryEngineRoot(explicit) {
    const raw = explicit ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
    return isAbsolute(raw) ? raw : resolve(raw);
}
export function scopeDirName(scope) {
    if (/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(scope))
        return scope;
    const cleaned = scope.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[.-]+/, "") || "scope";
    const hash = createHash("sha256").update(scope, "utf8").digest("hex").slice(0, 6);
    return `${cleaned}-${hash}`;
}
const caseFoldByDirIdentity = new Map();
let caseFoldProbeSeq = 0;
function lstatOrAbsent(p) {
    try {
        return lstatSync(p);
    }
    catch (err) {
        if (err.code === "ENOENT")
            return undefined;
        throw err;
    }
}
function probeDirCaseFolds(dir) {
    const name = `${CASEFOLD_PROBE_PREFIX}${process.pid}-${caseFoldProbeSeq++}`;
    const writeLeg = (() => {
        let fd;
        try {
            fd = openSync(join(dir, name), O_WRONLY | O_CREAT | O_EXCL, 0o600);
        }
        catch {
            return undefined;
        }
        let identity;
        try {
            const st = fstatSync(fd);
            identity = { dev: st.dev, ino: st.ino };
        }
        catch {
        }
        finally {
            closeSync(fd);
        }
        try {
            let swapped;
            let orig;
            try {
                swapped = lstatOrAbsent(join(dir, name.toUpperCase()));
                orig = swapped === undefined ? lstatOrAbsent(join(dir, name)) : undefined;
            }
            catch {
                return undefined;
            }
            if (swapped !== undefined) {
                return identity === undefined || (swapped.dev === identity.dev && swapped.ino === identity.ino) ? true : undefined;
            }
            if (orig === undefined || identity === undefined)
                return undefined;
            return orig.dev === identity.dev && orig.ino === identity.ino ? false : undefined;
        }
        finally {
            try {
                rmSync(join(dir, name), { force: true });
            }
            catch {
            }
        }
    })();
    if (writeLeg !== undefined)
        return writeLeg;
    try {
        for (const entry of readdirSync(dir)) {
            if (!/[A-Za-z]/.test(entry))
                continue;
            const swapped = entry.toLowerCase() !== entry ? entry.toLowerCase() : entry.toUpperCase();
            try {
                const orig = lstatOrAbsent(join(dir, entry));
                if (orig === undefined)
                    continue;
                const other = lstatOrAbsent(join(dir, swapped));
                const again = lstatOrAbsent(join(dir, entry));
                if (again === undefined || again.dev !== orig.dev || again.ino !== orig.ino)
                    continue;
                if (other === undefined)
                    return false;
                return other.dev === orig.dev && other.ino === orig.ino;
            }
            catch {
                continue;
            }
        }
    }
    catch {
    }
    return undefined;
}
export function dirCaseFolds(dir) {
    let key;
    try {
        ensureDirExists(dir);
        const s = statSync(dir);
        key = `${s.dev}:${s.ino}`;
    }
    catch {
    }
    if (key !== undefined) {
        const cached = caseFoldByDirIdentity.get(key);
        if (cached !== undefined)
            return cached;
    }
    const folds = probeDirCaseFolds(dir);
    if (folds === undefined)
        return undefined;
    try {
        const after = statSync(dir);
        if (key === undefined || key !== `${after.dev}:${after.ino}`)
            return undefined;
    }
    catch {
        return undefined;
    }
    caseFoldByDirIdentity.set(key, folds);
    return folds;
}
export const SCOPE_DIR_LABEL_RE = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
export const CASEFOLD_PROBE_PREFIX = ".casefold-probe-";
export const SCOPE_REGISTRY_SHAPES = Object.freeze(["absent", "v1", "v2", "migrating"]);
export const REGISTRY_SHAPE_DERIVES_PATHS = {
    absent: true,
    v1: false,
    v2: true,
    migrating: false,
};
function ownLabel(scopes, scope) {
    return scopes !== undefined && Object.prototype.hasOwnProperty.call(scopes, scope) ? scopes[scope] : undefined;
}
function readScopesRecord(controlDir) {
    const path = join(controlDir, SCOPES_FILE);
    const journalPath = `${path}.journal`;
    let raw;
    let sourcePath = journalPath;
    try {
        raw = readFileSync(journalPath, "utf8");
    }
    catch (err) {
        if (err.code !== "ENOENT") {
            throw new ControlPlaneCorruptError(`scope registry journal unreadable (${err.code ?? "io error"}) — a committed next state may exist that cannot be proven: ${journalPath}`, { cause: err });
        }
        raw = undefined;
    }
    if (raw === undefined) {
        sourcePath = path;
        try {
            raw = readFileSync(path, "utf8");
        }
        catch (err) {
            if (err.code === "ENOENT")
                return { shape: "absent", rec: { v: 2, scopes: {} } };
            throw new ControlPlaneCorruptError(`scope registry unreadable (${err.code ?? "io error"}): ${path}`, { cause: err });
        }
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        throw new ControlPlaneCorruptError(`scope registry is unparseable: ${sourcePath}`);
    }
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
        throw new ControlPlaneCorruptError(`scope registry has the wrong shape: ${path}`);
    }
    const rec = parsed;
    const scopes = coerceScopeLabels(rec.scopes, path);
    if (rec.v === undefined) {
        if (rec.rootScope !== undefined && typeof rec.rootScope !== "string") {
            throw new ControlPlaneCorruptError(`scope registry rootScope is not a string: ${path}`);
        }
        return { shape: "v1", ...(rec.rootScope !== undefined ? { rootScope: rec.rootScope } : {}), scopes };
    }
    if (rec.v !== 2) {
        throw new ControlPlaneCorruptError(`scope registry version ${JSON.stringify(rec.v)} is not one this engine reads (expected 2): ${path}`);
    }
    for (const [k, label] of Object.entries(scopes)) {
        if (!SCOPE_DIR_LABEL_RE.test(label)) {
            throw new ControlPlaneCorruptError(`scope registry maps ${JSON.stringify(k)} to the label ${JSON.stringify(label)}, which is not a single non-hidden path segment — refusing (fail-closed): ${path}`);
        }
    }
    if (rec.rootOwner !== undefined && typeof rec.rootOwner !== "string") {
        throw new ControlPlaneCorruptError(`scope registry rootOwner is not a string: ${path}`);
    }
    const v2 = { v: 2, scopes, ...(rec.rootOwner !== undefined ? { rootOwner: rec.rootOwner } : {}) };
    if (rec.migrating === undefined)
        return { shape: "v2", rec: v2 };
    const m = rec.migrating;
    if (!m || typeof m !== "object" || Array.isArray(m) || typeof m.root !== "string" || typeof m.owner !== "string" || typeof m.label !== "string" || !SCOPE_DIR_LABEL_RE.test(m.label)) {
        throw new ControlPlaneCorruptError(`scope registry carries a malformed migrating marker: ${path}`);
    }
    return { shape: "migrating", rec: { ...v2, migrating: { root: m.root, owner: m.owner, label: m.label } } };
}
function coerceScopeLabels(raw, path) {
    if (raw === undefined)
        return {};
    if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
        throw new ControlPlaneCorruptError(`scope registry scopes map has the wrong shape: ${path}`);
    }
    const out = Object.create(null);
    for (const [k, v] of Object.entries(raw)) {
        if (typeof v !== "string")
            throw new ControlPlaneCorruptError(`scope registry entry ${JSON.stringify(k)} is not a string: ${path}`);
        out[k] = v;
    }
    return out;
}
function requireV2Registry(read, controlDir) {
    if (REGISTRY_SHAPE_DERIVES_PATHS[read.shape])
        return read.rec;
    throw new ControlPlaneCorruptError(read.shape === "v1"
        ? `scope registry is in the v1 (pre-v2, root-owning) form at ${join(controlDir, SCOPES_FILE)} — scope homes cannot be derived until the layout adoption has run (a mount or a locked backend face runs it); refusing (fail-closed)`
        : `scope registry carries an unfinished layout move (migrating marker) at ${join(controlDir, SCOPES_FILE)} — scope homes cannot be derived until the move completes (the next mount or locked backend face finishes it); refusing (fail-closed)`);
}
function commitScopesRecord(controlDir, lock, token, next) {
    const file = join(controlDir, SCOPES_FILE);
    const journal = `${file}.journal`;
    const data = `${JSON.stringify(next, null, 2)}\n`;
    const fence = () => assertSidecarLockOwnership(lock, token, "scope registry", "strict");
    fence();
    let current;
    try {
        current = readFileSync(file, "utf8");
    }
    catch {
        current = undefined;
    }
    if (current === data)
        return;
    atomicWriteFileSync(journal, data);
    fence();
    atomicWriteFileSync(file, data);
    fence();
    rmSync(journal, { force: true });
}
function withScopesRegistry(controlDir, fn) {
    ensureDirExists(controlDir);
    const file = join(controlDir, SCOPES_FILE);
    const journal = `${file}.journal`;
    const lock = `${file}.lock`;
    const token = acquireSidecarLock(lock, { onDeadline: "throw" });
    try {
        rollForwardStrictSidecar(file, journal, () => assertSidecarLockOwnership(lock, token, "scope registry", "strict"));
        return fn({
            read: () => readScopesRecord(controlDir),
            commit: (next) => commitScopesRecord(controlDir, lock, token, next),
            renew: () => {
                const now = new Date();
                utimesSync(lock, now, now);
                assertSidecarLockOwnership(lock, token, "scope registry", "strict");
            },
        });
    }
    finally {
        releaseSidecarLock(lock, token);
    }
}
function assertNoLabelCollision(memoryDir, scopes, scope, label, caseFoldingFs) {
    for (const [other, otherLabel] of Object.entries(scopes)) {
        if (other === scope)
            continue;
        if (otherLabel === label) {
            throw new ControlPlaneCorruptError(`scope directory collision: ${JSON.stringify(scope)} and ${JSON.stringify(other)} both map to ${JSON.stringify(label)} — refusing (fail-closed)`);
        }
        if (otherLabel.toLowerCase() === label.toLowerCase()) {
            const folds = caseFoldingFs ?? dirCaseFolds(memoryDir);
            if (folds !== false) {
                throw new ControlPlaneCorruptError(`scope directory collision on a case-folding filesystem: ${JSON.stringify(scope)} → ${JSON.stringify(label)} and ${JSON.stringify(other)} → ${JSON.stringify(otherLabel)} are one physical directory under ${memoryDir}` +
                    `${folds === undefined ? " (volume case semantics could not be probed — refusing the fold-equal pair rather than risking a silent cross-scope merge)" : ""} — refusing (fail-closed)`);
            }
        }
    }
}
function assertHomePhysicalForm(home) {
    const st = lstatOrAbsent(home);
    if (st === undefined)
        return "absent";
    if (st.isDirectory())
        return "dir";
    throw scopeHomeRefusal(st.isSymbolicLink() ? "symlink" : st.isFile() ? "file" : "other", home);
}
function ensureScopeHome(home) {
    if (assertHomePhysicalForm(home) === "absent") {
        try {
            mkdirSync(home, { recursive: true });
        }
        catch (err) {
            assertHomePhysicalForm(home);
            throw err;
        }
    }
    assertHomePhysicalForm(home);
}
export function registerScope(memoryDir, controlDir, scope, opts) {
    return withScopesRegistry(controlDir, ({ read, commit }) => {
        const rec = requireV2Registry(read(), controlDir);
        const registered = ownLabel(rec.scopes, scope);
        const label = registered ?? scopeDirName(scope);
        assertNoLabelCollision(memoryDir, rec.scopes, scope, label, opts?.caseFoldingFs);
        const home = join(memoryDir, label);
        if (registered === undefined) {
            ensureDirExists(memoryDir);
            ensureScopeHome(home);
            commit({ ...rec, scopes: { ...rec.scopes, [scope]: label } });
        }
        else {
            assertHomePhysicalForm(home);
        }
        return home;
    });
}
export function registeredScopes(controlDir) {
    return requireV2Registry(readScopesRecord(controlDir), controlDir).scopes;
}
export function scopeDirFor(memoryDir, controlDir, scope) {
    const rec = requireV2Registry(readScopesRecord(controlDir), controlDir);
    return join(memoryDir, ownLabel(rec.scopes, scope) ?? scopeDirName(scope));
}
export function scopeRegistryShape(controlDir) {
    return readScopesRecord(controlDir).shape;
}
export function v1MemoryPathKeyResolver(controlDir) {
    const read = readScopesRecord(controlDir);
    const owner = read.shape === "v1" ? read.rootScope : read.rec.rootOwner;
    const scopes = read.shape === "v1" ? read.scopes : read.rec.scopes;
    const byLabel = new Map();
    for (const [scope, label] of Object.entries(scopes))
        if (label !== "" && scope !== owner)
            byLabel.set(label, scope);
    return (relPath) => {
        const norm = relPath.split(sep).join("/");
        const cut = norm.indexOf("/");
        if (cut > 0) {
            const scope = byLabel.get(norm.slice(0, cut));
            if (scope !== undefined)
                return { scope, rel: norm.slice(cut + 1) };
        }
        return owner === undefined ? undefined : { scope: owner, rel: norm };
    };
}
const UNOWNED_ROOT_CONTENT_NAMES_MAX = 8;
function discloseLayoutIncident(sink, code, message) {
    if (sink === undefined)
        return;
    try {
        const e = new Error(message);
        e.code = code;
        sink(e);
    }
    catch {
    }
}
function sameEntry(a, b) {
    const sa = lstatOrAbsent(a);
    const sb = lstatOrAbsent(b);
    return sa !== undefined && sb !== undefined && sa.dev === sb.dev && sa.ino === sb.ino;
}
function looseRootContent(root, labels, controlDir) {
    let names;
    try {
        names = readdirSync(root);
    }
    catch (err) {
        if (err.code === "ENOENT")
            return { names: [], probes: [] };
        throw new ControlPlaneCorruptError(`memory root ${root} could not be enumerated for the layout adoption (${err.code ?? "io error"}) — refusing (fail-closed)`, { cause: err });
    }
    const canonicalControl = canonicalize(controlDir);
    const out = [];
    const probes = [];
    const foldedLabels = new Map();
    for (const label of labels)
        foldedLabels.set(label.toLowerCase(), label);
    for (const name of names.sort()) {
        if (labels.has(name))
            continue;
        if (name.startsWith(CASEFOLD_PROBE_PREFIX)) {
            probes.push(name);
            continue;
        }
        if (canonicalize(join(root, name)) === canonicalControl)
            continue;
        const label = foldedLabels.get(name.toLowerCase());
        if (label !== undefined && sameEntry(join(root, name), join(root, label)))
            continue;
        out.push(name);
    }
    return { names: out, probes };
}
export function layoutNeedsAdoption(root, controlDir) {
    const read = readScopesRecord(controlDir);
    if (read.shape === "v1" || read.shape === "migrating")
        return true;
    const { names, probes } = looseRootContent(root, new Set(Object.values(read.rec.scopes)), controlDir);
    return names.length > 0 || probes.length > 0;
}
export function adoptRootScopeLayout(rootPath, controlDir, opts = {}) {
    const root = resolve(rootPath);
    withScopesRegistry(controlDir, (ctx) => {
        const renew = () => {
            ctx.renew();
            opts.renew?.();
        };
        const convertSidecars = () => {
            renew();
            opts.convertSidecars?.(controlDir);
        };
        const read = ctx.read();
        let rec;
        let converting = false;
        if (read.shape === "v1") {
            converting = true;
            const scopes = Object.create(null);
            for (const [scope, label] of Object.entries(read.scopes)) {
                if (label === "")
                    continue;
                if (!SCOPE_DIR_LABEL_RE.test(label)) {
                    throw new ControlPlaneCorruptError(`scope registry (v1 form) maps ${JSON.stringify(scope)} to the label ${JSON.stringify(label)}, which is not a single non-hidden path segment — refusing (fail-closed): ${join(controlDir, SCOPES_FILE)}`);
                }
                scopes[scope] = label;
            }
            rec = { v: 2, scopes, ...(read.rootScope !== undefined ? { rootOwner: read.rootScope } : {}) };
            if (read.rootScope !== undefined && ownLabel(scopes, read.rootScope) === undefined) {
                const label = scopeDirName(read.rootScope);
                assertNoLabelCollision(root, scopes, read.rootScope, label, undefined);
                const home = join(root, label);
                if (assertHomePhysicalForm(home) === "dir" && readdirSync(home).length > 0) {
                    throw new ControlPlaneCorruptError(`memory root ${root} holds a non-empty directory ${home} named like the home of its root-owning scope ${JSON.stringify(read.rootScope)} — the layout adoption cannot tell a topic directory from a home; resolve by hand (fail-closed)`);
                }
                rec.scopes = { ...scopes, [read.rootScope]: label };
            }
        }
        else if (read.shape === "migrating") {
            rec = read.rec;
            const m = read.rec.migrating;
            const { migrating: _m, ...rest } = rec;
            rec = { ...rest, scopes: { ...rec.scopes, [m.owner]: m.label }, rootOwner: m.owner };
            moveLooseContent(m.root, controlDir, rec, m.owner, m.label, renew, opts.onIncident, true);
            convertSidecars();
            ctx.commit(rec);
            if (canonicalize(m.root) === canonicalize(root))
                return;
        }
        else {
            rec = read.rec;
        }
        const labels = new Set(Object.values(rec.scopes));
        const { names, probes } = looseRootContent(root, labels, controlDir);
        if (names.length === 0) {
            if (converting) {
                convertSidecars();
                ctx.commit(rec);
            }
            for (const p of probes)
                rmSync(join(root, p), { force: true });
            return;
        }
        const candidate = opts.ownerCandidate ?? undefined;
        const owner = rec.rootOwner ?? candidate;
        if (owner === undefined) {
            if (converting) {
                convertSidecars();
                ctx.commit(rec);
            }
            discloseLayoutIncident(opts.onIncident, "memory.layout_unowned_root_content", `memory root ${root} holds ${names.length} loose entr${names.length === 1 ? "y" : "ies"} that belong to no recorded scope (${names.slice(0, UNOWNED_ROOT_CONTENT_NAMES_MAX).join(", ")}${names.length > UNOWNED_ROOT_CONTENT_NAMES_MAX ? ", …" : ""}) — left in place, not served; the next mount with a write scope adopts them into that scope's home`);
            return;
        }
        if (isContainedIn(root, controlDir)) {
            throw new ControlPlaneCorruptError(`memory control plane ${controlDir} lies inside the memory root ${root} — the layout adoption refuses to move engine metadata as scope content (fail-closed)`);
        }
        const label = ownLabel(rec.scopes, owner) ?? scopeDirName(owner);
        assertNoLabelCollision(root, rec.scopes, owner, label, undefined);
        const home = join(root, label);
        renew();
        const homeForm = assertHomePhysicalForm(home);
        if (homeForm === "dir") {
            const inHome = readdirSync(home);
            if (inHome.length > 0) {
                throw new ControlPlaneCorruptError(`memory root ${root} holds loose content for ${JSON.stringify(owner)} while its home ${home} is already a non-empty directory with no move in progress — the two cannot be merged automatically (a topic directory named like the home, a root re-occupied by an older engine, or a registry that lost this scope's row); resolve by hand (fail-closed)`);
            }
        }
        for (const name of names) {
            renew();
            if (lstatOrAbsent(join(home, name)) !== undefined) {
                throw new ControlPlaneCorruptError(`memory root ${root} and the home ${home} both hold ${JSON.stringify(name)} — the layout adoption refuses to overwrite either (fail-closed)`);
            }
        }
        const next = { v: 2, scopes: { ...rec.scopes, [owner]: label }, rootOwner: owner, migrating: { root, owner, label } };
        renew();
        ctx.commit(next);
        const { migrating: _marker, ...done } = next;
        moveLooseContent(root, controlDir, done, owner, label, renew, opts.onIncident, false);
        convertSidecars();
        renew();
        ctx.commit(done);
    });
}
function moveLooseContent(root, controlDir, rec, owner, label, renew, sink, resumed) {
    const labels = new Set(Object.values(rec.scopes));
    const { names, probes } = looseRootContent(root, labels, controlDir);
    const home = join(root, label);
    if (names.length > 0)
        ensureScopeHome(home);
    let moved = 0;
    for (const name of names) {
        renew();
        const src = join(root, name);
        const dst = join(home, name);
        if (sameEntry(src, home)) {
            throw new ControlPlaneCorruptError(`memory root ${root}: ${JSON.stringify(name)} is the home ${home} itself under another spelling — the layout adoption refuses to move a directory into itself (fail-closed)`);
        }
        if (lstatOrAbsent(dst) !== undefined) {
            throw new ControlPlaneCorruptError(`memory root ${root} and the home ${home} both hold ${JSON.stringify(name)} — the layout adoption refuses to overwrite either (fail-closed)`);
        }
        try {
            renameSync(src, dst);
            moved += 1;
        }
        catch (err) {
            if (err.code === "ENOENT" && lstatOrAbsent(src) === undefined)
                continue;
            throw err;
        }
    }
    for (const p of probes) {
        renew();
        rmSync(join(root, p), { force: true });
    }
    renew();
    writeIndexRevs(controlDir, {});
    discloseLayoutIncident(sink, "memory.layout_migrated", `memory layout adopted at ${root}: ${moved} loose entr${moved === 1 ? "y" : "ies"} moved into the home of ${JSON.stringify(owner)} (${label})${resumed ? " — an interrupted move was finished" : ""}`);
}
export const canonicalize = physicalPathOfExisting;
export function isContainedIn(root, child) {
    const r = canonicalize(root);
    const c = canonicalize(child);
    return c === r || c.startsWith(`${r}${sep}`);
}
export const STORE_FORM_REFUSAL_CODES = Object.freeze(["memory.target_outside_store", "memory.scope_home_not_directory"]);
export const TARGET_OUTSIDE_STORE = "memory.target_outside_store";
export const SCOPE_HOME_NOT_DIRECTORY = "memory.scope_home_not_directory";
export const SCOPE_HOME_FORMS = Object.freeze(["symlink", "file", "other"]);
const SCOPE_HOME_FORM_PHRASE = {
    symlink: "a symbolic link",
    file: "a regular file",
    other: "a special file",
};
function storeFormRefusal(code, sentence, detail) {
    const e = new Error(`${code}: ${sentence}`);
    e.code = code;
    e.detail = detail;
    return e;
}
export function storeActRefusal(act, path) {
    return storeFormRefusal(TARGET_OUTSIDE_STORE, `${act} was refused — its directory resolves outside the store root; not performed`, { path });
}
export function storeRootMovedRefusal(path, anchor) {
    return storeFormRefusal(TARGET_OUTSIDE_STORE, "the store root now resolves to a different directory than the one its control plane records — refused, nothing in the store was written; put the directory back, or, if the store was moved on purpose, call reanchorMemoryStoreRoot(detail.path, { controlDir: detail.controlDir }) and reopen", { path, controlDir: anchor.controlDir, recorded: anchor.recorded, now: anchor.now });
}
export function storeRootAbsentRefusal(path, anchor) {
    return storeFormRefusal(TARGET_OUTSIDE_STORE, "the store root no longer exists where its control plane records it — refused, nothing in the store was written; put the directory back, or, if the store was moved on purpose, call reanchorMemoryStoreRoot with its new directory and { controlDir: detail.controlDir }; to start over in place, create the directory, then call reanchorMemoryStoreRoot(detail.path, { controlDir: detail.controlDir }) and reopen", { path, controlDir: anchor.controlDir, recorded: anchor.recorded, now: anchor.now });
}
export function scopeHomeRefusal(form, path) {
    return storeFormRefusal(SCOPE_HOME_NOT_DIRECTORY, `a scope home must be a real directory, but this one is ${SCOPE_HOME_FORM_PHRASE[form]} — refused (fail-closed); put the real directory back`, { path, form });
}
export function landingPathOf(p) {
    return landingPathWithin(resolve(p), 0);
}
const MAX_LANDING_LINK_HOPS = 40;
function landingPathWithin(normalized, hops) {
    try {
        return physicalPathOf(normalized);
    }
    catch (err) {
        if (err.code !== "ENOENT")
            throw err;
    }
    const tail = [];
    for (let cur = normalized;;) {
        const st = lstatOrAbsent(cur);
        if (st?.isSymbolicLink() === true) {
            if (hops >= MAX_LANDING_LINK_HOPS) {
                const e = new Error(`ELOOP: too many symbolic links resolving ${normalized}`);
                e.code = "ELOOP";
                throw e;
            }
            return landingPathWithin(join(resolve(dirname(cur), readlinkSync(cur)), ...[...tail].reverse()), hops + 1);
        }
        if (st !== undefined)
            return canonicalize(normalized);
        const parent = dirname(cur);
        if (parent === cur)
            return normalized;
        tail.push(basename(cur));
        cur = parent;
    }
}
export function landsOutsideStore(physicalRoot, target) {
    const dir = landingPathOf(dirname(target));
    return dir !== physicalRoot && !dir.startsWith(`${physicalRoot}${sep}`);
}
export function ensureDirExists(dir) {
    if (!existsSync(dir))
        mkdirSync(dir, { recursive: true, mode: 0o700 });
}
export const LEDGER_FILE = "revs.json";
export const ROOT_ANCHOR_FILE = "root-anchor.json";
const ANCHOR_BACKFILL_ARM_OPEN = true;
export function absentAnchorReading(controlPlaneHoldsStore, backfillArmOpen = ANCHOR_BACKFILL_ARM_OPEN) {
    if (!controlPlaneHoldsStore)
        return "record";
    return backfillArmOpen ? "backfill" : "refuse";
}
function reanchorRemedy(dir, controlDir) {
    return `once the store root is where it belongs, call reanchorMemoryStoreRoot(${JSON.stringify(dir)}, { controlDir: ${JSON.stringify(controlDir)} }) and reopen`;
}
function damagedAnchorError(controlDir, dir, why, cause) {
    return new ControlPlaneCorruptError(`memory store root anchor ${join(controlDir, ROOT_ANCHOR_FILE)} is damaged (${why}) — the store root cannot be checked against it, so nothing in the store was opened; ${reanchorRemedy(dir, controlDir)}`, cause !== undefined ? { cause } : undefined);
}
function readStoreRootAnchor(controlDir, dir) {
    let raw;
    let fd;
    try {
        fd = openSync(join(controlDir, ROOT_ANCHOR_FILE), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
        if (!fstatSync(fd).isFile())
            throw damagedAnchorError(controlDir, dir, "not a regular file");
        raw = readFileSync(fd, "utf8");
    }
    catch (err) {
        if (err instanceof ControlPlaneCorruptError)
            throw err;
        const code = err.code;
        if (code === "ENOENT" && fd === undefined)
            return undefined;
        throw damagedAnchorError(controlDir, dir, code === "ELOOP" ? "a symbolic link, not the record" : `unreadable: ${code ?? thrownLabel(err)}`, err);
    }
    finally {
        if (fd !== undefined)
            closeSync(fd);
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch (err) {
        throw damagedAnchorError(controlDir, dir, "not JSON", err);
    }
    if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
        throw damagedAnchorError(controlDir, dir, "not an object");
    const a = parsed;
    if (a.v !== 1)
        throw damagedAnchorError(controlDir, dir, `unknown version ${JSON.stringify(a.v) ?? typeof a.v}`);
    if (typeof a.realpath !== "string" || !isAbsolute(a.realpath))
        throw damagedAnchorError(controlDir, dir, "realpath is not an absolute path");
    if (typeof a.spelling !== "string" || typeof a.recordedAt !== "number" || !Number.isFinite(a.recordedAt))
        throw damagedAnchorError(controlDir, dir, "spelling / recordedAt out of shape");
    return { v: 1, realpath: a.realpath, spelling: a.spelling, recordedAt: a.recordedAt };
}
function controlPlaneHoldsStore(controlDir) {
    return lstatOrAbsent(join(controlDir, SCOPES_FILE)) !== undefined || lstatOrAbsent(join(controlDir, LEDGER_FILE)) !== undefined;
}
function judgeAgainstAnchor(dir, controlDir, anchor) {
    const now = landingPathOf(dir);
    if (now !== anchor.realpath)
        throw storeRootMovedRefusal(dir, { controlDir, recorded: anchor.realpath, now });
    if (lstatOrAbsent(now) === undefined && controlPlaneHoldsStore(controlDir))
        throw storeRootAbsentRefusal(dir, { controlDir, recorded: anchor.realpath, now });
    return now;
}
function writeStoreRootAnchor(controlDir, lock, token, anchor) {
    assertSidecarLockOwnership(lock, token, "memory store root anchor", "strict");
    atomicWriteFileSync(join(controlDir, ROOT_ANCHOR_FILE), `${JSON.stringify(anchor, null, 2)}\n`);
}
export function anchorStoreRoot(dir, controlDir, opts = {}) {
    const anchor = readStoreRootAnchor(controlDir, dir);
    if (anchor !== undefined)
        return judgeAgainstAnchor(dir, controlDir, anchor);
    ensureDirExists(controlDir);
    const lock = join(controlDir, `${ROOT_ANCHOR_FILE}.lock`);
    const token = acquireSidecarLock(lock, { onDeadline: "throw" });
    try {
        const raced = readStoreRootAnchor(controlDir, dir);
        if (raced !== undefined)
            return judgeAgainstAnchor(dir, controlDir, raced);
        const reading = absentAnchorReading(controlPlaneHoldsStore(controlDir));
        if (reading === "refuse")
            throw damagedAnchorError(controlDir, dir, "absent, while the control plane holds a store");
        ensureDirExists(dir);
        const realpath = landingPathOf(dir);
        writeStoreRootAnchor(controlDir, lock, token, { v: 1, realpath, spelling: dir, recordedAt: (opts.now ?? Date.now)() });
        if (reading === "backfill") {
            discloseLayoutIncident(opts.onIncident, "memory.root_anchor_backfilled", `memory store root anchor recorded for a store that had none: ${dir} resolves to ${realpath} (control plane ${controlDir}) — from now on the store opens only there; where the root resolved before this open cannot be told`);
        }
        return realpath;
    }
    finally {
        releaseSidecarLock(lock, token);
    }
}
export const STORE_JOURNAL_FILE = "journal.json";
export const STORE_TXN_LOCK_DIR = "txn.lock";
export const TXN_LOCK_STALE_MS = 30_000;
const TXN_LOCK_WAIT_MS = 15_000;
const TXN_LOCK_STEAL_GRACE_MS = 250;
export function tryStoreTxnLock(controlDir, opts) {
    const lockDir = join(controlDir, STORE_TXN_LOCK_DIR);
    const ownerPath = join(lockDir, "owner");
    const staleMs = opts.staleMs ?? TXN_LOCK_STALE_MS;
    const faultText = (err) => err.code ?? "io error";
    const tryMkdir = () => {
        try {
            mkdirSync(controlDir, { recursive: true });
        }
        catch (err) {
            throw new ControlPlaneCorruptError(`memory control-plane root could not be created (${faultText(err)}) at ${controlDir} — a lock-acquisition fault is not contention (fail-closed)`, { cause: err });
        }
        try {
            mkdirSync(lockDir);
            return true;
        }
        catch (err) {
            if (err.code === "EEXIST")
                return false;
            throw new ControlPlaneCorruptError(`memory txn lock could not be created (${faultText(err)}) at ${lockDir} — a lock-acquisition fault is not contention (fail-closed)`, { cause: err });
        }
    };
    let acquired = tryMkdir();
    if (!acquired) {
        let mtimeMs;
        try {
            mtimeMs = statSync(lockDir).mtimeMs;
            opts.observed?.(mtimeMs);
        }
        catch (err) {
            if (err.code !== "ENOENT") {
                throw new ControlPlaneCorruptError(`memory txn lock state could not be probed (${faultText(err)}) at ${lockDir} — a probe failure is not contention (fail-closed)`, { cause: err });
            }
            let occupant;
            try {
                occupant = lstatSync(lockDir);
            }
            catch (lerr) {
                if (lerr.code !== "ENOENT") {
                    throw new ControlPlaneCorruptError(`memory txn lock re-probe failed (${faultText(lerr)}) at ${lockDir} — a probe failure is not contention (fail-closed)`, { cause: lerr });
                }
            }
            if (occupant !== undefined && !occupant.isDirectory()) {
                throw new ControlPlaneCorruptError(`memory txn lock path is occupied by an invalid node (a dangling symlink?) at ${lockDir} — not contention and not a release race; remove the occupant (fail-closed)`, { cause: err });
            }
            mtimeMs = undefined;
        }
        if (mtimeMs !== undefined && opts.now() - mtimeMs > staleMs) {
            let tokenPresent = false;
            try {
                statSync(ownerPath);
                tokenPresent = true;
            }
            catch (err) {
                if (err.code !== "ENOENT") {
                    throw new ControlPlaneCorruptError(`memory txn lock owner token could not be probed (${faultText(err)}) at ${ownerPath} — a probe failure is not a mid-acquisition (fail-closed)`, { cause: err });
                }
            }
            if (tokenPresent) {
                const tomb = join(controlDir, `${STORE_TXN_LOCK_DIR}.stale-${randomUUID()}`);
                let stolen = false;
                try {
                    renameSync(lockDir, tomb);
                    stolen = true;
                }
                catch (err) {
                    if (err.code !== "ENOENT") {
                        throw new ControlPlaneCorruptError(`memory txn lock stale-steal rename failed (${faultText(err)}) at ${lockDir} — a rename fault is not a lost race (fail-closed)`, { cause: err });
                    }
                }
                if (stolen) {
                    rmSync(tomb, { recursive: true, force: true });
                    acquired = tryMkdir();
                }
            }
        }
        if (!acquired)
            return undefined;
    }
    const token = randomUUID();
    try {
        const fd = openSync(ownerPath, "w", 0o600);
        try {
            writeAllSync(fd, token);
            fsyncSync(fd);
        }
        finally {
            closeSync(fd);
        }
    }
    catch (err) {
        try {
            rmSync(lockDir, { recursive: true, force: true });
        }
        catch {
        }
        throw new Error(`memory txn lock owner token could not be written at ${ownerPath}: ${thrownText(err)}`);
    }
    return {
        token,
        release: () => {
            try {
                if (readFileSync(ownerPath, "utf8") !== token)
                    return;
            }
            catch {
                return;
            }
            try {
                rmSync(lockDir, { recursive: true, force: true });
            }
            catch {
            }
        },
    };
}
export function assertStoreTxnLockOwnership(controlDir, token) {
    let held;
    try {
        held = readFileSync(join(controlDir, STORE_TXN_LOCK_DIR, "owner"), "utf8");
    }
    catch {
        held = undefined;
    }
    if (held !== token) {
        throw new ControlPlaneCorruptError("txn lock ownership lost before commit (holder stalled past the stale deadline and was stolen from?) — transaction aborted, nothing was committed", { facet: "lock-ownership" });
    }
}
export async function acquireStoreTxnLock(controlDir, opts) {
    const staleMs = opts.timings?.staleMs ?? TXN_LOCK_STALE_MS;
    const waitMs = opts.timings?.waitMs ?? TXN_LOCK_WAIT_MS;
    const stealGraceMs = opts.timings?.stealGraceMs ?? TXN_LOCK_STEAL_GRACE_MS;
    let deadline = Date.now() + waitMs;
    for (;;) {
        const lock = tryStoreTxnLock(controlDir, { now: opts.now, staleMs, observed: (mtimeMs) => (deadline = Math.max(deadline, mtimeMs + staleMs + stealGraceMs)) });
        if (lock !== undefined)
            return lock;
        if (Date.now() > deadline) {
            throw new Error(`memory txn lock timeout (${waitMs}ms, extended to the lock's stale line) at ${join(controlDir, STORE_TXN_LOCK_DIR)} — another writer is wedged?`);
        }
        await new Promise((r) => setTimeout(r, 20 + Math.floor(Math.random() * 40)));
    }
}
function reanchorDestination(dir) {
    let to;
    try {
        to = physicalPathOf(resolve(dir));
    }
    catch (err) {
        throw reanchorRefusal(`the store root ${JSON.stringify(dir)} does not resolve (${err.code ?? thrownLabel(err)}) — put the directory in place first`);
    }
    if (lstatOrAbsent(to)?.isDirectory() !== true)
        throw reanchorRefusal(`the store root ${JSON.stringify(dir)} resolves to ${to}, which is not a directory`);
    return to;
}
function reanchorRefusal(sentence) {
    const e = new Error(`reanchorMemoryStoreRoot: ${sentence}`);
    e.code = "config.memory_reanchor";
    return e;
}
export async function reanchorMemoryStoreRoot(dir, opts = {}) {
    const controlDir = effectiveControlRootOf(dir, { controlDir: opts.controlDir, onIncident: opts.onIncident });
    const to = reanchorDestination(dir);
    const lock = join(controlDir, `${ROOT_ANCHOR_FILE}.lock`);
    if (lstatOrAbsent(join(controlDir, ROOT_ANCHOR_FILE)) === undefined && !controlPlaneHoldsStore(controlDir)) {
        throw reanchorRefusal(`the control plane ${controlDir} holds no store and no root anchor — nothing to re-anchor; pass the controlDir this store's backend / engine is opened with`);
    }
    const txn = await acquireStoreTxnLock(controlDir, { now: Date.now });
    let from;
    try {
        const token = acquireSidecarLock(lock, { onDeadline: "throw" });
        try {
            try {
                from = readStoreRootAnchor(controlDir, dir)?.realpath;
            }
            catch {
                from = undefined;
            }
            try {
                assertStoreTxnLockOwnership(controlDir, txn.token);
            }
            catch {
                throw reanchorRefusal(`the store's transaction lock was lost while re-anchoring (held past its stale line and taken by another writer) — nothing changed; retry (control plane ${controlDir})`);
            }
            if (lstatOrAbsent(join(controlDir, STORE_JOURNAL_FILE)) !== undefined) {
                throw reanchorRefusal(`a committed transaction is still pending recovery at the recorded root — put the store back at ${from ?? "the root it was last opened at"}, open it once so the journal replays, then re-anchor (control plane ${controlDir})`);
            }
            let still;
            try {
                still = reanchorDestination(dir);
            }
            catch {
                still = undefined;
            }
            if (still !== to)
                throw reanchorRefusal(`the store root ${JSON.stringify(dir)} moved while re-anchoring (it resolved to ${to} when the call began) — nothing changed; retry`);
            writeStoreRootAnchor(controlDir, lock, token, { v: 1, realpath: to, spelling: dir, recordedAt: Date.now() });
        }
        finally {
            releaseSidecarLock(lock, token);
        }
    }
    finally {
        txn.release();
    }
    discloseLayoutIncident(opts.onIncident, "memory.root_reanchored", `memory store root re-anchored: ${dir} now opens at ${to}${from !== undefined ? ` (was ${from})` : ""} — control plane ${controlDir}`);
    return from !== undefined ? { from, to, controlDir } : { to, controlDir };
}
export function permissionRemedyOf(err, memoryDir) {
    const e = typeof err === "object" && err !== null ? err : undefined;
    if (e === undefined || (e.code !== "EACCES" && e.code !== "EPERM") || typeof e.path !== "string" || !isContainedIn(memoryDir, e.path))
        return undefined;
    return `file permissions refused this memory write (the engine never changes permission bits) — if an engine from 7.30.x or earlier left read-only bits on this store, run \`chmod -R u+w ${shellQuoted(memoryDir)}\` once no such engine still runs on it (an older engine sets the bits again whenever it mounts a layer read-only)`;
}
function shellQuoted(s) {
    return `'${s.replace(/'/g, "'\\''")}'`;
}
const remedied = new WeakSet();
export function withPermissionRemedy(err, memoryDir) {
    const remedy = permissionRemedyOf(err, memoryDir);
    if (remedy === undefined || !(err instanceof Error) || remedied.has(err))
        return err;
    err.message = `${err.message} — ${remedy}`;
    remedied.add(err);
    return err;
}
export const QUARANTINE_DIR = "quarantine";
const DELETED_TOMBSTONE = "---\ndeleted: true\n---\n";
export function writeFileNoFollow(path, content) {
    const fd = openSync(path, O_WRONLY | O_CREAT | O_NOFOLLOW, 0o644);
    try {
        const links = fstatSync(fd).nlink;
        if (links > 1)
            throw Object.assign(new Error(`${path} has ${links} names (a hard link) — a write through one name changes the file under the others; not written`), { path });
        ftruncateSync(fd, 0);
        writeFileSync(fd, content, "utf8");
    }
    finally {
        closeSync(fd);
    }
}
export function writeFileNoReplace(path, content) {
    let fd;
    try {
        fd = openSync(path, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0o644);
    }
    catch (err) {
        if (err.code === "EEXIST")
            return "kept";
        throw err;
    }
    try {
        writeFileSync(fd, content, "utf8");
    }
    finally {
        closeSync(fd);
    }
    return "written";
}
function readForVerification(path, maxBytes) {
    let fd;
    try {
        fd = openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
    }
    catch {
        return undefined;
    }
    try {
        if (!fstatSync(fd).isFile())
            return undefined;
        const buf = Buffer.allocUnsafe(maxBytes);
        let got = 0;
        for (;;) {
            const n = readSync(fd, buf, got, maxBytes - got, got);
            if (n <= 0 || got + n >= maxBytes)
                return buf.subarray(0, got + Math.max(0, n)).toString("utf8");
            got += n;
        }
    }
    catch {
        return undefined;
    }
    finally {
        closeSync(fd);
    }
}
export function restoreContainedBaseline(path, committed, leftOnPlane) {
    try {
        if (leftOnPlane === undefined || readForVerification(path, Buffer.byteLength(leftOnPlane) + 1) !== leftOnPlane) {
            return writeFileNoReplace(path, committed);
        }
        writeFileNoFollow(path, committed);
        return "written";
    }
    catch {
        return "failed";
    }
}
let quarantineSeq = 0;
export function quarantineAndTombstone(path, content, quarantineDir, now) {
    let captured;
    let detail;
    try {
        ensureDirExists(quarantineDir);
        const base = `${now()}-${basename(path)}`;
        for (let attempt = 0; captured === undefined && attempt < 10; attempt++) {
            const candidate = join(quarantineDir, attempt === 0 ? base : `${base}.${attempt}`);
            try {
                writeFileSync(candidate, content, { encoding: "utf8", flag: "wx" });
                captured = candidate;
            }
            catch (err) {
                if (!(err instanceof Error && "code" in err && err.code === "EEXIST"))
                    throw err;
            }
        }
        if (captured === undefined) {
            const last = join(quarantineDir, `${base}.${process.pid}.${quarantineSeq++}`);
            writeFileSync(last, content, { encoding: "utf8", flag: "wx" });
            captured = last;
        }
    }
    catch (err) {
        detail = `quarantine copy failed: ${thrownText(err)}`;
    }
    let left = content;
    let removed = false;
    try {
        rmSync(path, { force: true });
        left = undefined;
        removed = true;
    }
    catch (rmErr) {
        try {
            writeFileNoFollow(path, DELETED_TOMBSTONE);
            left = DELETED_TOMBSTONE;
            removed = true;
            detail = `${detail !== undefined ? `${detail}; ` : ""}delete failed (${thrownText(rmErr)}) — tombstoned in place`;
        }
        catch (clearErr) {
            detail = `${detail !== undefined ? `${detail}; ` : ""}suspect file could not be deleted or cleared: ${thrownText(clearErr)}`;
        }
    }
    return {
        ...(captured !== undefined ? { dest: captured } : {}),
        removed,
        ...(left !== undefined ? { leftOnPlane: left } : {}),
        ...(detail !== undefined ? { detail } : {}),
    };
}
export const ANNOUNCEMENTS_FILE = "announcements.json";
export const MEMORY_ANNOUNCEMENTS_MAX = 20;
export const SCAN_FUSE_FILE = "scan-fuse.json";
export const SCAN_FUSE_THRESHOLD = 3;
function sleepSync(ms) {
    Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
const LOCK_STALE_MS = 1_500;
const LOCK_WAIT_CAP_MS = 3_000;
const LOCK_SPIN_PAUSE_MS = 5;
const MAX_FAIL_OPEN_DEADLINE_ROUNDS = 3;
export function acquireSidecarLock(lockDir, opts = {}) {
    const waitCapMs = opts.waitCapMs ?? LOCK_WAIT_CAP_MS;
    const deadline = Date.now() + waitCapMs;
    const mintToken = () => `${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
    let deadlineRounds = 0;
    const stealOnce = () => {
        const tomb = `${lockDir}.stale-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
        try {
            renameSync(lockDir, tomb);
        }
        catch {
            return false;
        }
        rmSync(tomb, { recursive: true, force: true });
        return true;
    };
    for (;;) {
        try {
            mkdirSync(lockDir);
            const token = mintToken();
            let fd;
            try {
                fd = openSync(join(lockDir, "owner"), "w");
                writeAllSync(fd, token);
            }
            catch {
            }
            finally {
                if (fd !== undefined) {
                    try {
                        closeSync(fd);
                    }
                    catch {
                    }
                }
            }
            return token;
        }
        catch (err) {
            if (err.code !== "EEXIST")
                throw err;
        }
        try {
            const age = Date.now() - statSync(lockDir).mtimeMs;
            if (age > LOCK_STALE_MS)
                stealOnce();
        }
        catch {
        }
        if (Date.now() > deadline) {
            if (opts.onDeadline === "throw") {
                throw new Error(`sidecar lock wait timed out (${waitCapMs}ms) at ${lockDir} — refusing to proceed without the lock (fail-closed sidecar)`);
            }
            deadlineRounds += 1;
            if (deadlineRounds > MAX_FAIL_OPEN_DEADLINE_ROUNDS)
                return mintToken();
            stealOnce();
        }
        sleepSync(LOCK_SPIN_PAUSE_MS);
    }
}
function rollForwardSidecar(file, journal) {
    let raw;
    try {
        raw = readFileSync(journal, "utf8");
    }
    catch {
        return;
    }
    try {
        JSON.parse(raw);
        atomicWriteFileSync(file, raw);
    }
    catch {
    }
    rmSync(journal, { force: true });
}
function lockedJournaledUpdate(controlDir, fileName, mutate) {
    ensureDirExists(controlDir);
    const file = join(controlDir, fileName);
    const journal = `${file}.journal`;
    const lock = `${file}.lock`;
    const token = acquireSidecarLock(lock);
    try {
        rollForwardSidecar(file, journal);
        let current;
        try {
            current = JSON.parse(readFileSync(file, "utf8"));
        }
        catch {
            current = undefined;
        }
        const next = mutate(current);
        if (next === undefined)
            return;
        const data = `${JSON.stringify(next, null, 2)}\n`;
        atomicWriteFileSync(journal, data);
        atomicWriteFileSync(file, data);
        rmSync(journal, { force: true });
    }
    finally {
        releaseSidecarLock(lock, token);
    }
}
export function assertSidecarLockOwnership(lockDir, token, what, mode = "lenient") {
    let held;
    try {
        held = readFileSync(join(lockDir, "owner"), "utf8");
    }
    catch (err) {
        if (mode === "strict" && err.code !== undefined) {
            throw new ControlPlaneCorruptError(`${what}: sidecar lock ownership UNPROVABLE at commit (owner file ${err.code === "ENOENT" ? "absent" : `unreadable: ${err.code}`}) — a fail-closed ledger must not commit on a fence it cannot prove; update aborted`, { cause: err, facet: "lock-ownership" });
        }
        return;
    }
    if (held !== token) {
        throw new ControlPlaneCorruptError(`${what}: sidecar lock ownership lost before commit (holder stalled past ${LOCK_STALE_MS}ms and was stolen from) — update aborted`, { facet: "lock-ownership" });
    }
}
function releaseSidecarLock(lockDir, token) {
    try {
        const held = readFileSync(join(lockDir, "owner"), "utf8");
        if (held !== token)
            return;
    }
    catch {
    }
    rmSync(lockDir, { recursive: true, force: true });
}
function readSidecarJson(controlDir, fileName) {
    const file = join(controlDir, fileName);
    for (const p of [`${file}.journal`, file]) {
        try {
            return JSON.parse(readFileSync(p, "utf8"));
        }
        catch {
        }
    }
    return undefined;
}
function coerceAnnouncements(raw) {
    const rec = { folded: 0, queue: [] };
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return rec;
    const r = raw;
    if (typeof r.folded === "number" && Number.isFinite(r.folded) && r.folded > 0)
        rec.folded = Math.floor(r.folded);
    if (Array.isArray(r.queue)) {
        for (const e of r.queue) {
            const a = e;
            if (a &&
                typeof a === "object" &&
                (a.kind === "gate" || a.kind === "external") &&
                typeof a.at === "number" &&
                Array.isArray(a.items) &&
                a.items.every((i) => typeof i === "string")) {
                rec.queue.push({
                    kind: a.kind,
                    at: a.at,
                    ...(typeof a.sessionId === "string" ? { sessionId: a.sessionId } : {}),
                    ...(typeof a.reportId === "string" ? { reportId: a.reportId } : {}),
                    items: a.items,
                });
            }
        }
    }
    return rec;
}
export function enqueueMemoryAnnouncement(controlDir, ann, opts) {
    const family = opts?.supersedesPrefix;
    if (ann.items.length === 0 && family === undefined)
        return;
    lockedJournaledUpdate(controlDir, ANNOUNCEMENTS_FILE, (current) => {
        const rec = coerceAnnouncements(current);
        if (family !== undefined) {
            const queued = rec.queue.flatMap((a) => a.items.filter((i) => i.startsWith(family)));
            if (queued.length === ann.items.length && ann.items.every((i) => queued.includes(i)))
                return undefined;
            for (const a of rec.queue)
                a.items = a.items.filter((i) => !i.startsWith(family));
            rec.queue = rec.queue.filter((a) => a.items.length > 0);
            if (ann.items.length === 0)
                return rec;
        }
        rec.queue.push(ann);
        if (rec.queue.length > MEMORY_ANNOUNCEMENTS_MAX) {
            rec.folded += rec.queue.length - MEMORY_ANNOUNCEMENTS_MAX;
            rec.queue = rec.queue.slice(-MEMORY_ANNOUNCEMENTS_MAX);
        }
        return rec;
    });
}
export function drainMemoryAnnouncements(controlDir) {
    const file = join(controlDir, ANNOUNCEMENTS_FILE);
    if (!existsSync(file) && !existsSync(`${file}.journal`))
        return { folded: 0, queue: [] };
    let drained = { folded: 0, queue: [] };
    lockedJournaledUpdate(controlDir, ANNOUNCEMENTS_FILE, (current) => {
        drained = coerceAnnouncements(current);
        if (drained.queue.length === 0 && drained.folded === 0)
            return undefined;
        return { folded: 0, queue: [] };
    });
    return drained;
}
export function peekMemoryAnnouncements(controlDir) {
    return coerceAnnouncements(readSidecarJson(controlDir, ANNOUNCEMENTS_FILE));
}
export function scanFuseKey(scope, rel) {
    return JSON.stringify([scope, rel.split(sep).join("/")]);
}
function coerceFuse(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return {};
    const rec = raw;
    if (rec.v !== 2 || !rec.counts || typeof rec.counts !== "object" || Array.isArray(rec.counts))
        return {};
    const out = {};
    for (const [k, v] of Object.entries(rec.counts)) {
        if (typeof v === "number" && Number.isFinite(v) && v > 0)
            out[k] = Math.floor(v);
    }
    return out;
}
export function bumpScanFuse(controlDir, key) {
    let prior = 0;
    lockedJournaledUpdate(controlDir, SCAN_FUSE_FILE, (current) => {
        const counts = coerceFuse(current);
        prior = counts[key] ?? 0;
        counts[key] = prior + 1;
        return { v: 2, counts };
    });
    return prior;
}
const INDEX_REVS_FILE = "index-revs.json";
export function readIndexRevs(controlDir) {
    const parsed = readSidecarJson(controlDir, INDEX_REVS_FILE);
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
        return {};
    const pairs = [];
    for (const [k, v] of Object.entries(parsed)) {
        if (typeof v === "string")
            pairs.push([k, { rev: v }]);
        else if (v !== null && typeof v === "object" && typeof v.rev === "string") {
            const { rev, line } = v;
            pairs.push([k, typeof line === "string" ? { rev, line } : { rev }]);
        }
    }
    return Object.fromEntries(pairs);
}
export function normalizeIndexLine(line) {
    return line.trim().replace(/\s+/g, " ");
}
export function indexLineIsStale(snapshot, entryRev, line) {
    return snapshot !== undefined && snapshot.rev !== entryRev && snapshot.line !== undefined && normalizeIndexLine(snapshot.line) === normalizeIndexLine(line);
}
export function writeIndexRevs(controlDir, revs) {
    try {
        ensureDirExists(controlDir);
        const recorded = Object.fromEntries(Object.entries(revs).map(([target, v]) => [target, { rev: v.rev, line: normalizeIndexLine(v.line) }]));
        writeIfChanged(join(controlDir, INDEX_REVS_FILE), `${JSON.stringify(recorded, null, 2)}\n`);
    }
    catch {
    }
}
export function scanFuseCount(controlDir, key) {
    return coerceFuse(readSidecarJson(controlDir, SCAN_FUSE_FILE))[key] ?? 0;
}
export function sessionRecordBindsIncarnation(recorded, asking) {
    return recorded === undefined || asking === undefined || recorded === asking;
}
function readIncarnationSeat(parsed) {
    return isIncarnationSeat(parsed.incarnation) && parsed.incarnation !== undefined ? parsed.incarnation : undefined;
}
function isIncarnationSeat(raw) {
    if (raw === undefined)
        return true;
    if (typeof raw !== "string" || raw === "")
        return false;
    try {
        encodeURIComponent(raw);
        return true;
    }
    catch {
        return false;
    }
}
export function usableSessionIncarnation(raw) {
    return isIncarnationSeat(raw) ? raw : undefined;
}
function sessionRecordPath(controlDir, dir, sessionId, incarnation) {
    const name = incarnation === undefined ? `${encodeURIComponent(sessionId)}.json` : `${encodeURIComponent(sessionId)}@${encodeURIComponent(incarnation)}.json`;
    return join(controlDir, dir, name);
}
function standingOwnerAt(path) {
    let raw;
    try {
        raw = readFileSync(path, "utf8");
    }
    catch (err) {
        if (err.code !== "ENOENT")
            return "unattributable";
        try {
            lstatSync(path);
        }
        catch {
            return "unattributable";
        }
        return { owner: undefined };
    }
    try {
        const parsed = JSON.parse(raw);
        if (parsed !== null && typeof parsed === "object")
            return { owner: readIncarnationSeat(parsed) };
    }
    catch {
    }
    return "unattributable";
}
function mintSessionRecordSlots(controlDir, dir, sessionId, incarnation, text) {
    const idSlot = sessionRecordPath(controlDir, dir, sessionId);
    try {
        ensureDirExists(dirname(idSlot));
        writeFileSync(idSlot, text, { encoding: "utf8", flag: "wx" });
        return "created";
    }
    catch (err) {
        if (!(err instanceof Error && "code" in err && err.code === "EEXIST"))
            return "unpersisted";
    }
    const own = usableSessionIncarnation(incarnation);
    const standing = standingOwnerAt(idSlot);
    const vouches = standing !== "unattributable" && (standing.owner === undefined || standing.owner === own);
    const unnamed = vouches || own === undefined ? undefined : standingOwnerAt(sessionRecordPath(controlDir, dir, sessionId, ""));
    if (vouches || (unnamed !== undefined && unnamed !== "unattributable" && unnamed.owner === undefined))
        return "existed";
    try {
        writeFileSync(sessionRecordPath(controlDir, dir, sessionId, own ?? ""), text, { encoding: "utf8", flag: "wx" });
        return "created";
    }
    catch (err) {
        if (err instanceof Error && "code" in err && err.code === "EEXIST")
            return "existed";
        return "unpersisted";
    }
}
function bindOrSeparate(record, identity, asking, unknownSlot) {
    if (sessionRecordBindsIncarnation(record.incarnation, asking))
        return record;
    const unnamed = unknownSlot();
    if (unnamed !== undefined && sessionRecordBindsIncarnation(unnamed.incarnation, asking))
        return unnamed;
    if (record.incarnation !== undefined)
        identity?.onSeparated?.(record.incarnation);
    return undefined;
}
function sessionIdOfRecordName(name) {
    if (!name.endsWith(".json"))
        return undefined;
    const stem = name.slice(0, -".json".length);
    const at = stem.indexOf("@");
    try {
        return decodeURIComponent(at < 0 ? stem : stem.slice(0, at));
    }
    catch {
        return undefined;
    }
}
export const SESSION_POLLUTION_DIR = "session-pollution";
function pollutionPath(controlDir, sessionId, incarnation) {
    return sessionRecordPath(controlDir, SESSION_POLLUTION_DIR, sessionId, incarnation);
}
export function markSessionPolluted(controlDir, sessionId, reason, now, cause, incarnation) {
    const record = { at: now(), reason, ...(cause !== undefined ? { cause } : {}), ...(incarnation !== undefined ? { incarnation } : {}) };
    return mintSessionRecordSlots(controlDir, SESSION_POLLUTION_DIR, sessionId, incarnation, `${JSON.stringify(record, null, 2)}\n`);
}
export function readSessionPollution(controlDir, sessionId, identity) {
    const asking = usableSessionIncarnation(identity?.incarnation);
    if (asking !== undefined) {
        const own = readPollutionAt(pollutionPath(controlDir, sessionId, asking));
        if (own !== undefined)
            return own;
    }
    const unnamedSlot = () => readPollutionAt(pollutionPath(controlDir, sessionId, ""));
    const standing = readPollutionAt(pollutionPath(controlDir, sessionId));
    return standing === undefined ? unnamedSlot() : bindOrSeparate(standing, identity, asking, unnamedSlot);
}
function readPollutionAt(path) {
    let raw;
    try {
        raw = readFileSync(path, "utf8");
    }
    catch {
        try {
            lstatSync(path);
        }
        catch {
            return undefined;
        }
        return { at: 0, reason: "pollution marker present but unreadable (kept fail-closed)" };
    }
    try {
        const parsed = JSON.parse(raw);
        if (parsed !== null && typeof parsed === "object") {
            const at = parsed.at;
            const reason = parsed.reason;
            const cause = parsed.cause;
            const knownCause = typeof cause === "string" && MEMORY_ORIGIN_CAUSES.includes(cause) ? cause : undefined;
            const incarnation = readIncarnationSeat(parsed);
            if (typeof at === "number" && typeof reason === "string")
                return { at, reason, ...(knownCause !== undefined ? { cause: knownCause } : {}), ...(incarnation !== undefined ? { incarnation } : {}) };
        }
    }
    catch {
    }
    return { at: 0, reason: "pollution marker present but unreadable (kept fail-closed)" };
}
export function listSessionPollution(controlDir) {
    const dir = join(controlDir, SESSION_POLLUTION_DIR);
    let names;
    try {
        names = readdirSync(dir);
    }
    catch (err) {
        if (err.code === "ENOENT")
            return {};
        throw new ControlPlaneCorruptError(`session pollution markers could not be enumerated (${err.code ?? "io error"}) at ${dir} — an enumeration failure is not an empty set (fail-closed)`, { cause: err });
    }
    const out = Object.create(null);
    for (const name of names.sort()) {
        const sessionId = sessionIdOfRecordName(name);
        if (sessionId === undefined || Object.prototype.hasOwnProperty.call(out, sessionId))
            continue;
        const rec = readPollutionAt(join(dir, name));
        if (rec !== undefined)
            out[sessionId] = rec;
    }
    return out;
}
export function importSessionPollution(controlDir, sessionId, record) {
    const path = pollutionPath(controlDir, sessionId);
    ensureDirExists(dirname(path));
    try {
        writeFileSync(path, `${JSON.stringify({ at: record.at, reason: record.reason, ...(record.cause !== undefined ? { cause: record.cause } : {}) }, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
        return "written";
    }
    catch (err) {
        if (!(err instanceof Error && "code" in err && err.code === "EEXIST"))
            throw err;
    }
    const standing = readSessionPollution(controlDir, sessionId);
    return standing !== undefined && standing.at === record.at && standing.reason === record.reason ? "already" : "divergent";
}
export const SESSION_CAPTURE_OPTOUT_DIR = "session-capture-optout";
function captureOptOutPath(controlDir, sessionId, incarnation) {
    return sessionRecordPath(controlDir, SESSION_CAPTURE_OPTOUT_DIR, sessionId, incarnation);
}
export function markSessionCaptureOptOut(controlDir, sessionId, reason, now, incarnation) {
    const record = { at: now(), reason, ...(incarnation !== undefined ? { incarnation } : {}) };
    return mintSessionRecordSlots(controlDir, SESSION_CAPTURE_OPTOUT_DIR, sessionId, incarnation, `${JSON.stringify(record, null, 2)}\n`);
}
export function readSessionCaptureOptOut(controlDir, sessionId, identity) {
    const asking = usableSessionIncarnation(identity?.incarnation);
    if (asking !== undefined) {
        const own = readCaptureOptOutAt(captureOptOutPath(controlDir, sessionId, asking));
        if (own !== undefined)
            return own;
    }
    const unnamedSlot = () => readCaptureOptOutAt(captureOptOutPath(controlDir, sessionId, ""));
    const standing = readCaptureOptOutAt(captureOptOutPath(controlDir, sessionId));
    return standing === undefined ? unnamedSlot() : bindOrSeparate(standing, identity, asking, unnamedSlot);
}
function readCaptureOptOutAt(path) {
    let raw;
    try {
        raw = readFileSync(path, "utf8");
    }
    catch (err) {
        if (err.code === "ENOENT") {
            try {
                lstatSync(path);
            }
            catch (lerr) {
                if (lerr.code === "ENOENT")
                    return undefined;
                throw new ControlPlaneCorruptError(`session capture opt-out record state is INDETERMINATE (${lerr.code ?? "io error"}) at ${path} — the store cannot establish whether a record exists; consumers must suppress on the fault axis (an outage is never reported as a user request)`, { cause: lerr });
            }
            return { at: 0, reason: "capture opt-out record present but unreadable (kept fail-closed)" };
        }
        try {
            statSync(path);
        }
        catch {
            throw new ControlPlaneCorruptError(`session capture opt-out record state is INDETERMINATE (${err.code ?? "io error"}) at ${path} — the store cannot establish whether a record exists; consumers must suppress on the fault axis (an outage is never reported as a user request)`, { cause: err });
        }
        return { at: 0, reason: "capture opt-out record present but unreadable (kept fail-closed)" };
    }
    try {
        const parsed = JSON.parse(raw);
        if (parsed !== null && typeof parsed === "object") {
            const at = parsed.at;
            const reason = parsed.reason;
            const incarnation = readIncarnationSeat(parsed);
            if (typeof at === "number" && typeof reason === "string")
                return { at, reason, ...(incarnation !== undefined ? { incarnation } : {}) };
        }
    }
    catch {
    }
    return { at: 0, reason: "capture opt-out record present but unreadable (kept fail-closed)" };
}
export function listSessionCaptureOptOut(controlDir) {
    const dir = join(controlDir, SESSION_CAPTURE_OPTOUT_DIR);
    let names;
    try {
        names = readdirSync(dir);
    }
    catch (err) {
        if (err.code === "ENOENT")
            return {};
        throw new ControlPlaneCorruptError(`session capture opt-out records could not be enumerated (${err.code ?? "io error"}) at ${dir} — an enumeration failure is not an empty set (fail-closed)`, { cause: err });
    }
    const out = Object.create(null);
    for (const name of names.sort()) {
        const sessionId = sessionIdOfRecordName(name);
        if (sessionId === undefined || Object.prototype.hasOwnProperty.call(out, sessionId))
            continue;
        const rec = readCaptureOptOutAt(join(dir, name));
        if (rec !== undefined)
            out[sessionId] = rec;
    }
    return out;
}
export function fileSessionCaptureRecordStore(controlDir) {
    return {
        mark: (sessionId, record) => markSessionCaptureOptOut(controlDir, sessionId, record.reason, () => record.at, record.incarnation),
        read: (sessionId, identity) => readSessionCaptureOptOut(controlDir, sessionId, identity),
        list: () => listSessionCaptureOptOut(controlDir),
    };
}
export const USAGE_RETRIEVED_FILE = "usage-retrieved.json";
export const USAGE_RETRIEVED_MAX_IDS = 4096;
function coerceRetrievedAccount(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return Object.create(null);
    const out = Object.create(null);
    for (const [k, v] of Object.entries(raw)) {
        const row = v;
        if (row && typeof row === "object" && typeof row.count === "number" && Number.isFinite(row.count) && row.count > 0 && typeof row.lastAt === "number" && Number.isFinite(row.lastAt)) {
            out[k] = { count: Math.floor(row.count), lastAt: row.lastAt };
        }
    }
    return out;
}
export function recordRetrievedAccount(controlDir, ids, now) {
    if (ids.length === 0)
        return;
    const at = now();
    lockedJournaledUpdate(controlDir, USAGE_RETRIEVED_FILE, (current) => {
        const rows = coerceRetrievedAccount(current);
        for (const id of ids) {
            const prior = rows[id];
            rows[id] = { count: (prior?.count ?? 0) + 1, lastAt: at };
        }
        const keys = Object.keys(rows);
        if (keys.length > USAGE_RETRIEVED_MAX_IDS) {
            keys.sort((a, b) => rows[a].lastAt - rows[b].lastAt || (a < b ? -1 : 1));
            for (const cold of keys.slice(0, keys.length - USAGE_RETRIEVED_MAX_IDS))
                delete rows[cold];
        }
        return rows;
    });
}
export function readRetrievedAccount(controlDir) {
    return coerceRetrievedAccount(readSidecarJson(controlDir, USAGE_RETRIEVED_FILE));
}
export function clearScanFuse(controlDir, keys) {
    const wanted = [...keys];
    if (wanted.length === 0)
        return;
    const file = join(controlDir, SCAN_FUSE_FILE);
    if (!existsSync(file) && !existsSync(`${file}.journal`))
        return;
    lockedJournaledUpdate(controlDir, SCAN_FUSE_FILE, (current) => {
        const counts = coerceFuse(current);
        let changed = false;
        for (const k of wanted) {
            if (counts[k] !== undefined) {
                delete counts[k];
                changed = true;
            }
        }
        return changed ? { v: 2, counts } : undefined;
    });
}
export const LINEAGE_FILE = "lineage.json";
export const CHALLENGES_FILE = "challenges.json";
export const LINEAGE_AUDIT_FILE = "lineage-audit.json";
export const LINEAGE_AUDIT_MAX_ROWS = 2048;
export const CHALLENGED_HISTORY_FILE = "usage-challenged-history.json";
function rollForwardStrictSidecar(file, journal, fence) {
    let raw;
    try {
        raw = readFileSync(journal, "utf8");
    }
    catch {
        return;
    }
    try {
        JSON.parse(raw);
    }
    catch {
        throw new ControlPlaneCorruptError(`control-plane journal is unparseable (fail-closed sidecar): ${journal}`);
    }
    fence?.();
    atomicWriteFileSync(file, raw);
    fence?.();
    rmSync(journal, { force: true });
}
export function lockedStrictUpdate(controlDir, fileName, what, coerce, fn) {
    ensureDirExists(controlDir);
    const file = join(controlDir, fileName);
    const journal = `${file}.journal`;
    const lock = `${file}.lock`;
    const token = acquireSidecarLock(lock, { onDeadline: "throw" });
    try {
        rollForwardStrictSidecar(file, journal, () => assertSidecarLockOwnership(lock, token, what, "strict"));
        const current = coerce(readStrictSidecarRaw(file, what));
        const { next, result } = fn(current);
        if (next !== undefined) {
            const data = `${JSON.stringify(next, null, 2)}\n`;
            const fence = () => assertSidecarLockOwnership(lock, token, what, "strict");
            fence();
            atomicWriteFileSync(journal, data);
            fence();
            atomicWriteFileSync(file, data);
            fence();
            rmSync(journal, { force: true });
        }
        return result;
    }
    finally {
        releaseSidecarLock(lock, token);
    }
}
function readStrictSidecarRaw(file, what) {
    let raw;
    try {
        raw = readFileSync(file, "utf8");
    }
    catch (err) {
        if (err.code === "ENOENT")
            return undefined;
        throw new ControlPlaneCorruptError(`${what} unreadable (${err.code ?? "io error"}): ${file}`, { cause: err });
    }
    try {
        return JSON.parse(raw);
    }
    catch {
        throw new ControlPlaneCorruptError(`${what} is unparseable: ${file}`);
    }
}
export function readStrictSidecar(controlDir, fileName, what) {
    const file = join(controlDir, fileName);
    const journal = `${file}.journal`;
    let journalRaw;
    try {
        journalRaw = readFileSync(journal, "utf8");
    }
    catch (err) {
        if (err.code !== "ENOENT") {
            throw new ControlPlaneCorruptError(`control-plane journal unreadable ((${err.code ?? "io error"})) — a committed next state may exist that cannot be proven; repair the read fault rather than serving the pre-transaction state: ${journal}`, { cause: err });
        }
        journalRaw = undefined;
    }
    if (journalRaw !== undefined) {
        try {
            return JSON.parse(journalRaw);
        }
        catch {
            throw new ControlPlaneCorruptError(`control-plane journal is unparseable (fail-closed sidecar): ${journal}`);
        }
    }
    return readStrictSidecarRaw(file, what);
}
function coerceLineage(raw) {
    if (raw === undefined) {
        return { version: 1, committed: Object.create(null), pending: Object.create(null) };
    }
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        throw new ControlPlaneCorruptError("memory lineage ledger has the wrong shape");
    const r = raw;
    if (r.version !== 1)
        throw new ControlPlaneCorruptError(`memory lineage ledger has an unrecognized version (${String(r.version)}) — refusing (fail-closed)`);
    const badShape = (part) => new ControlPlaneCorruptError(`memory lineage ledger ${part} has the wrong shape`);
    if (!r.committed || typeof r.committed !== "object" || Array.isArray(r.committed))
        throw badShape("committed set");
    if (!r.pending || typeof r.pending !== "object" || Array.isArray(r.pending))
        throw badShape("pending set");
    for (const [entryId, sessions] of Object.entries(r.committed)) {
        if (!sessions || typeof sessions !== "object" || Array.isArray(sessions))
            throw badShape(`committed[${JSON.stringify(entryId)}]`);
        for (const [sid, c] of Object.entries(sessions)) {
            const row = c;
            if (!row || typeof row !== "object" || typeof row.lastRev !== "string" || typeof row.lastAt !== "number") {
                throw badShape(`committed[${JSON.stringify(entryId)}][${JSON.stringify(sid)}]`);
            }
            if (row.marked !== undefined && row.marked !== true)
                throw badShape(`committed[${JSON.stringify(entryId)}][${JSON.stringify(sid)}] marked`);
            if (!isIncarnationSeat(row.incarnation)) {
                throw badShape(`committed[${JSON.stringify(entryId)}][${JSON.stringify(sid)}] incarnation`);
            }
        }
    }
    for (const [txnId, t] of Object.entries(r.pending)) {
        const txn = t;
        if (!txn || typeof txn !== "object" || typeof txn.sessionId !== "string" || typeof txn.at !== "number" || !Array.isArray(txn.rows)) {
            throw badShape(`pending[${JSON.stringify(txnId)}]`);
        }
        if (txn.kind !== undefined && txn.kind !== "latch-only")
            throw badShape(`pending[${JSON.stringify(txnId)}] kind`);
        if (!isIncarnationSeat(txn.incarnation))
            throw badShape(`pending[${JSON.stringify(txnId)}] incarnation`);
        for (const row of txn.rows) {
            const p = row;
            if (!p || typeof p !== "object" || typeof p.entryId !== "string" || typeof p.rev !== "string")
                throw badShape(`pending[${JSON.stringify(txnId)}] row`);
            if (p.kind !== undefined && p.kind !== "latch-only")
                throw badShape(`pending[${JSON.stringify(txnId)}] row kind`);
            if (p.marked !== undefined && p.marked !== true)
                throw badShape(`pending[${JSON.stringify(txnId)}] row marked`);
        }
        if (txn.credential !== undefined) {
            const c = txn.credential;
            if (!c || typeof c !== "object" || !Array.isArray(c.appliedIds) || c.appliedIds.some((x) => typeof x !== "string") || typeof c.at !== "number") {
                throw badShape(`pending[${JSON.stringify(txnId)}] credential`);
            }
        }
    }
    const committed = Object.create(null);
    for (const [entryId, sessions] of Object.entries(r.committed)) {
        const s = Object.create(null);
        for (const [sid, c] of Object.entries(sessions))
            s[sid] = c;
        committed[entryId] = s;
    }
    const pending = Object.create(null);
    for (const [txnId, t] of Object.entries(r.pending))
        pending[txnId] = t;
    return { version: 1, committed, pending };
}
export function stageLineagePending(controlDir, txnId, sessionId, rows, now, incarnation) {
    lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        rec.pending[txnId] = { sessionId, at: now(), rows: [...rows], ...(incarnation !== undefined ? { incarnation } : {}) };
        return { next: rec, result: undefined };
    });
}
export function discardLineagePending(controlDir, txnId) {
    lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        if (rec.pending[txnId] === undefined)
            return { result: undefined };
        delete rec.pending[txnId];
        return { next: rec, result: undefined };
    });
}
export function recordLineageCredential(controlDir, txnId, appliedIds, now) {
    lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const txn = rec.pending[txnId];
        if (txn === undefined)
            throw new ControlPlaneCorruptError(`memory lineage ledger: credential for unknown pending transaction ${txnId}`);
        txn.credential = { appliedIds: [...appliedIds], at: now() };
        return { next: rec, result: undefined };
    });
}
export function promoteLineagePending(controlDir, txnId) {
    return lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const txn = rec.pending[txnId];
        if (txn === undefined)
            return { result: [] };
        if (txn.credential === undefined) {
            throw new ControlPlaneCorruptError(`memory lineage ledger: refusing to promote uncredentialed pending transaction ${txnId} (the commit outcome is not durably known)`);
        }
        const applied = new Set(txn.credential.appliedIds);
        const at = txn.credential.at;
        const promoted = [];
        for (const row of txn.rows) {
            if (!applied.has(row.entryId))
                continue;
            const sessions = (rec.committed[row.entryId] ??= Object.create(null));
            sessions[txn.sessionId] = { lastRev: row.rev, lastAt: at, ...(row.marked === true ? { marked: true } : {}), ...(txn.incarnation !== undefined ? { incarnation: txn.incarnation } : {}) };
            promoted.push({ entryId: row.entryId, sessionId: txn.sessionId, rev: row.rev, ...(row.marked === true ? { marked: true } : {}) });
        }
        delete rec.pending[txnId];
        return { next: rec, result: promoted };
    });
}
export function adjudicateLineagePending(controlDir, txnId, action, now) {
    return lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const txn = rec.pending[txnId];
        if (txn === undefined)
            return { result: [] };
        if (isLatchOnlyTxn(txn)) {
            throw new ControlPlaneCorruptError(`memory lineage ledger: pending transaction ${txnId} is a bundle-import latch — it cannot be ${action === "promote" ? "promoted (its rows are not contributions)" : "discarded (its withhold guards not-yet-governed imported entries)"}; re-run the same bundle import to converge it (releaseImportLatch is receipt-gated)`);
        }
        if (action === "discard") {
            delete rec.pending[txnId];
            return { next: rec, result: [] };
        }
        const applied = txn.credential !== undefined ? new Set(txn.credential.appliedIds) : undefined;
        const at = txn.credential?.at ?? now();
        const promoted = [];
        for (const row of txn.rows) {
            if (applied !== undefined && !applied.has(row.entryId))
                continue;
            const sessions = (rec.committed[row.entryId] ??= Object.create(null));
            sessions[txn.sessionId] = { lastRev: row.rev, lastAt: at, ...(row.marked === true ? { marked: true } : {}), ...(txn.incarnation !== undefined ? { incarnation: txn.incarnation } : {}) };
            promoted.push({ entryId: row.entryId, sessionId: txn.sessionId, rev: row.rev, ...(row.marked === true ? { marked: true } : {}) });
        }
        delete rec.pending[txnId];
        return { next: rec, result: promoted };
    });
}
export function reconcileLineage(controlDir) {
    return lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const promoted = [];
        const undecidable = [];
        let changed = false;
        for (const [txnId, txn] of Object.entries(rec.pending)) {
            if (txn.credential === undefined) {
                undecidable.push({ txnId, sessionId: txn.sessionId, entryIds: txn.rows.map((r) => r.entryId), ...(isLatchOnlyTxn(txn) ? { kind: "latch-only" } : {}) });
                continue;
            }
            const applied = new Set(txn.credential.appliedIds);
            for (const row of txn.rows) {
                if (!applied.has(row.entryId))
                    continue;
                const sessions = (rec.committed[row.entryId] ??= Object.create(null));
                sessions[txn.sessionId] = { lastRev: row.rev, lastAt: txn.credential.at, ...(row.marked === true ? { marked: true } : {}), ...(txn.incarnation !== undefined ? { incarnation: txn.incarnation } : {}) };
                promoted.push({ entryId: row.entryId, sessionId: txn.sessionId, rev: row.rev, ...(row.marked === true ? { marked: true } : {}) });
            }
            delete rec.pending[txnId];
            changed = true;
        }
        return { ...(changed ? { next: rec } : {}), result: { promoted, undecidable } };
    });
}
function isLatchOnlyTxn(txn) {
    if (txn.kind === "latch-only")
        return true;
    return txn.rows.some((row) => row.kind === "latch-only");
}
export const IMPORT_RECEIPTS_DIR = "import-receipts";
export function importLatchTxnId(bundleHash) {
    return `import:${bundleHash}`;
}
function importReceiptPath(controlDir, bundleHash) {
    return join(controlDir, IMPORT_RECEIPTS_DIR, `${bundleHash}.json`);
}
export function stageImportLatch(controlDir, bundleHash, rows, now) {
    const txnId = importLatchTxnId(bundleHash);
    lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const existing = rec.pending[txnId];
        if (existing !== undefined && !isLatchOnlyTxn(existing)) {
            throw new ControlPlaneCorruptError(`memory lineage ledger: pending transaction ${txnId} exists and is NOT an import latch — refusing to overwrite a real staged transaction`);
        }
        rec.pending[txnId] = {
            sessionId: txnId,
            at: now(),
            kind: "latch-only",
            rows: rows.map((r) => ({ entryId: r.entryId, rev: r.rev, kind: "latch-only" })),
        };
        return { next: rec, result: undefined };
    });
    return txnId;
}
export function releaseImportLatch(controlDir, txnId) {
    const prefix = "import:";
    if (!txnId.startsWith(prefix) || !/^[0-9a-f]{64}$/.test(txnId.slice(prefix.length))) {
        throw new ControlPlaneCorruptError(`releaseImportLatch: ${JSON.stringify(txnId)} is not an import-latch transaction id (import:<64-hex bundleHash>)`);
    }
    const receipt = importReceiptPath(controlDir, txnId.slice(prefix.length));
    let receiptPresent;
    try {
        readFileSync(receipt);
        receiptPresent = true;
    }
    catch (err) {
        if (err.code !== "ENOENT") {
            throw new ControlPlaneCorruptError(`releaseImportLatch: the completion receipt at ${receipt} could not be read (${err.code ?? "io error"}) — a probe failure is not absence (fail-closed), and absence would refuse the release`, { cause: err });
        }
        receiptPresent = false;
    }
    if (!receiptPresent) {
        throw new ControlPlaneCorruptError(`releaseImportLatch: no completion receipt at ${receipt} — the import's governance legs are not proven landed, the latch stays; re-run the same bundle import to converge`);
    }
    lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const txn = rec.pending[txnId];
        if (txn === undefined)
            return { result: undefined };
        if (!isLatchOnlyTxn(txn)) {
            throw new ControlPlaneCorruptError(`releaseImportLatch: pending transaction ${txnId} is NOT an import latch — refusing (this channel never discards a real staged transaction)`);
        }
        delete rec.pending[txnId];
        return { next: rec, result: undefined };
    });
}
export function lineageLatchedIds(controlDir) {
    const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
    const out = new Set();
    for (const txn of Object.values(rec.pending))
        for (const row of txn.rows)
            out.add(row.entryId);
    return out;
}
export function lineageContributionsOfSession(controlDir, sessionId, asking) {
    const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
    const out = [];
    for (const [entryId, sessions] of Object.entries(rec.committed)) {
        const c = Object.prototype.hasOwnProperty.call(sessions, sessionId) ? sessions[sessionId] : undefined;
        if (c === undefined || !sessionRecordBindsIncarnation(c.incarnation, asking))
            continue;
        out.push({ entryId, lastRev: c.lastRev, lastAt: c.lastAt, ...(c.marked === true ? { marked: true } : {}) });
    }
    return out;
}
export function lineageAccountOfEntry(controlDir, entryId) {
    const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
    const sessions = Object.prototype.hasOwnProperty.call(rec.committed, entryId) ? rec.committed[entryId] : undefined;
    const contributors = sessions === undefined ? [] : Object.entries(sessions).map(([sessionId, c]) => ({ sessionId, lastRev: c.lastRev, lastAt: c.lastAt }));
    let pendingLatched = false;
    for (const txn of Object.values(rec.pending)) {
        if (txn.rows.some((r) => r.entryId === entryId)) {
            pendingLatched = true;
            break;
        }
    }
    return { contributors, pendingLatched };
}
export function clearLineageForEntries(controlDir, entryIds) {
    if (entryIds.length === 0)
        return;
    lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        let changed = false;
        for (const id of entryIds) {
            if (rec.committed[id] !== undefined) {
                delete rec.committed[id];
                changed = true;
            }
        }
        return { ...(changed ? { next: rec } : {}), result: undefined };
    });
}
export function captureAndClearLineageForEntries(controlDir, entryIds) {
    if (entryIds.length === 0)
        return new Map();
    return lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const want = new Set(entryIds);
        const captured = new Map();
        const add = (id, sessionId) => {
            const set = captured.get(id) ?? new Set();
            set.add(sessionId);
            captured.set(id, set);
        };
        let changed = false;
        for (const id of entryIds) {
            if (!Object.prototype.hasOwnProperty.call(rec.committed, id))
                continue;
            for (const sessionId of Object.keys(rec.committed[id] ?? {}))
                add(id, sessionId);
            delete rec.committed[id];
            changed = true;
        }
        for (const txn of Object.values(rec.pending)) {
            const keep = [];
            for (const row of txn.rows) {
                if (!want.has(row.entryId)) {
                    keep.push(row);
                    continue;
                }
                changed = true;
                if (row.kind !== "latch-only")
                    add(row.entryId, txn.sessionId);
            }
            txn.rows = keep;
        }
        const result = new Map();
        for (const [id, sessions] of captured)
            result.set(id, [...sessions].sort());
        return { ...(changed ? { next: rec } : {}), result };
    });
}
export function importLineageCommitted(controlDir, rows) {
    if (rows.length === 0)
        return [];
    return lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
        const divergence = [];
        let changed = false;
        for (const row of rows) {
            const sessions = (rec.committed[row.entryId] ??= Object.create(null));
            const standing = Object.prototype.hasOwnProperty.call(sessions, row.sessionId) ? sessions[row.sessionId] : undefined;
            if (standing === undefined || row.lastAt > standing.lastAt) {
                sessions[row.sessionId] = { lastRev: row.lastRev, lastAt: row.lastAt, ...(row.marked === true ? { marked: true } : {}) };
                changed = true;
                continue;
            }
            if (row.lastAt === standing.lastAt && row.lastRev !== standing.lastRev) {
                divergence.push({ entryId: row.entryId, sessionId: row.sessionId, kept: "destination" });
                continue;
            }
            if (row.lastAt === standing.lastAt && row.lastRev === standing.lastRev && row.marked === true && standing.marked !== true) {
                sessions[row.sessionId] = { ...standing, marked: true };
                changed = true;
                continue;
            }
        }
        return { ...(changed ? { next: rec } : {}), result: divergence };
    });
}
export function readLineageRecord(controlDir) {
    const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
    return { committed: rec.committed, pending: rec.pending };
}
export function appendLineageAudit(controlDir, rows) {
    if (rows.length === 0)
        return;
    lockedJournaledUpdate(controlDir, LINEAGE_AUDIT_FILE, (current) => {
        const list = Array.isArray(current) ? current.filter((r) => r && typeof r === "object") : [];
        list.push(...rows);
        return list.length > LINEAGE_AUDIT_MAX_ROWS ? list.slice(-LINEAGE_AUDIT_MAX_ROWS) : list;
    });
}
function coerceChallenges(raw) {
    if (raw === undefined)
        return { version: 1, events: [] };
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        throw new ControlPlaneCorruptError("memory challenge ledger has the wrong shape");
    const r = raw;
    if (r.version !== 1)
        throw new ControlPlaneCorruptError(`memory challenge ledger has an unrecognized version (${String(r.version)}) — refusing (fail-closed)`);
    if (!Array.isArray(r.events))
        throw new ControlPlaneCorruptError("memory challenge ledger events list has the wrong shape");
    for (const e of r.events) {
        const ev = e;
        if (!ev ||
            typeof ev !== "object" ||
            typeof ev.eventId !== "string" ||
            typeof ev.entryId !== "string" ||
            (ev.kind !== "challenge" && ev.kind !== "resolve") ||
            typeof ev.generation !== "number" ||
            typeof ev.at !== "number" ||
            typeof ev.reason !== "string") {
            throw new ControlPlaneCorruptError("memory challenge ledger event has the wrong shape");
        }
    }
    return raw;
}
export const CHALLENGE_LEDGER_MAX_EVENTS = 10_000;
export function appendChallengeEvents(controlDir, events, now) {
    if (events.length === 0)
        return { assignments: [], eventCount: undefined };
    return lockedStrictUpdate(controlDir, CHALLENGES_FILE, "memory challenge ledger", coerceChallenges, (rec) => {
        const byEventId = new Map(rec.events.map((e) => [e.eventId, e]));
        const maxGen = new Map();
        for (const e of rec.events) {
            if (e.kind !== "challenge")
                continue;
            maxGen.set(e.entryId, Math.max(maxGen.get(e.entryId) ?? 0, e.generation));
        }
        const out = [];
        let changed = false;
        for (const req of events) {
            const existing = byEventId.get(req.eventId);
            if (existing !== undefined) {
                out.push({ entryId: existing.entryId, eventId: existing.eventId, generation: existing.generation, at: existing.at, replayed: true });
                continue;
            }
            const generation = (maxGen.get(req.entryId) ?? 0) + 1;
            maxGen.set(req.entryId, generation);
            const ev = {
                eventId: req.eventId,
                entryId: req.entryId,
                kind: "challenge",
                generation,
                at: now(),
                reason: req.reason,
                grade: "S",
                ...(req.challengedRev !== undefined ? { challengedRev: req.challengedRev } : {}),
            };
            rec.events.push(ev);
            byEventId.set(ev.eventId, ev);
            out.push({ entryId: ev.entryId, eventId: ev.eventId, generation, at: ev.at, replayed: false });
            changed = true;
        }
        return { ...(changed ? { next: rec } : {}), result: { assignments: out, eventCount: rec.events.length } };
    });
}
export function resolveChallengeEvent(controlDir, entryId, generation, reason, now, requestId) {
    return lockedStrictUpdate(controlDir, CHALLENGES_FILE, "memory challenge ledger", coerceChallenges, (rec) => {
        const opened = rec.events.some((e) => e.kind === "challenge" && e.entryId === entryId && e.generation === generation);
        if (!opened)
            return { result: { resolved: false, eventCount: undefined } };
        const alreadyResolved = rec.events.some((e) => e.kind === "resolve" && e.entryId === entryId && e.generation === generation);
        if (alreadyResolved)
            return { result: { resolved: true, eventCount: undefined } };
        rec.events.push({
            eventId: requestId !== undefined && requestId !== "" ? `${requestId}:${entryId}` : `resolve:${entryId}:${generation}`,
            entryId,
            kind: "resolve",
            generation,
            at: now(),
            reason,
        });
        return { next: rec, result: { resolved: true, eventCount: rec.events.length } };
    });
}
export function challengedEntryIds(controlDir) {
    const rec = coerceChallenges(readStrictSidecar(controlDir, CHALLENGES_FILE, "memory challenge ledger"));
    const resolved = new Set();
    for (const e of rec.events)
        if (e.kind === "resolve")
            resolved.add(`${e.entryId}#${e.generation}`);
    const out = new Map();
    for (const e of rec.events) {
        if (e.kind !== "challenge" || resolved.has(`${e.entryId}#${e.generation}`))
            continue;
        const prior = out.get(e.entryId);
        if (prior === undefined || e.generation > prior.generation)
            out.set(e.entryId, { generation: e.generation, at: e.at });
    }
    return out;
}
export function readChallengeEvents(controlDir) {
    return coerceChallenges(readStrictSidecar(controlDir, CHALLENGES_FILE, "memory challenge ledger")).events;
}
export function isStrictControlPlaneLedgerCorrupt(controlDir, ledger) {
    const fileName = ledger === "lineage" ? LINEAGE_FILE : CHALLENGES_FILE;
    const what = ledger === "lineage" ? "memory lineage ledger" : "memory challenge ledger";
    const coerce = ledger === "lineage" ? coerceLineage : coerceChallenges;
    try {
        coerce(readStrictSidecar(controlDir, fileName, what));
        return false;
    }
    catch (err) {
        if (err instanceof ControlPlaneCorruptError)
            return true;
        throw err;
    }
}
export function rebuildStrictControlPlaneLedger(controlDir, ledger, now) {
    const fileName = ledger === "lineage" ? LINEAGE_FILE : CHALLENGES_FILE;
    const what = ledger === "lineage" ? "memory lineage ledger" : "memory challenge ledger";
    const coerce = ledger === "lineage" ? coerceLineage : coerceChallenges;
    const empty = ledger === "lineage" ? { version: 1, committed: {}, pending: {} } : { version: 1, events: [] };
    ensureDirExists(controlDir);
    const file = join(controlDir, fileName);
    const journal = `${file}.journal`;
    const lock = `${file}.lock`;
    const token = acquireSidecarLock(lock, { onDeadline: "throw" });
    try {
        let healthy = false;
        try {
            coerce(readStrictSidecar(controlDir, fileName, what));
            healthy = true;
        }
        catch (err) {
            if (!(err instanceof ControlPlaneCorruptError))
                throw err;
        }
        if (healthy) {
            const e = new Error(`${what} reads cleanly — refusing to rebuild it. This call exists to recover a ledger whose corruption already refuses every read; resetting a healthy one would drop taint evidence that is still doing its job.`);
            e.code = "memory.control_plane_not_corrupt";
            throw e;
        }
        const at = now();
        const quarantinedTo = [];
        assertSidecarLockOwnership(lock, token, what, "strict");
        for (const path of [file, journal]) {
            if (!existsSync(path))
                continue;
            let dest = `${path}.corrupt-${at}`;
            for (let n = 2;; n++) {
                try {
                    copyFileSync(path, dest, fsConstants.COPYFILE_EXCL);
                    break;
                }
                catch (err) {
                    if (err.code !== "EEXIST")
                        throw err;
                    dest = `${path}.corrupt-${at}-${n}`;
                }
            }
            quarantinedTo.push(dest);
        }
        assertSidecarLockOwnership(lock, token, what, "strict");
        atomicWriteFileSync(file, `${JSON.stringify(empty, null, 2)}\n`);
        rmSync(journal, { force: true });
        return { ledger, quarantinedTo, at };
    }
    finally {
        releaseSidecarLock(lock, token);
    }
}
function coerceChallengedHistory(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return {};
    const out = {};
    for (const [k, v] of Object.entries(raw)) {
        const row = v;
        if (row &&
            typeof row === "object" &&
            typeof row.count === "number" &&
            Number.isFinite(row.count) &&
            row.count > 0 &&
            typeof row.lastAt === "number" &&
            (row.lastOp === "update" || row.lastOp === "tombstone")) {
            out[k] = { count: Math.floor(row.count), lastAt: row.lastAt, lastOp: row.lastOp };
        }
    }
    return out;
}
export function recordChallengedHistory(controlDir, rows, now) {
    if (rows.length === 0)
        return;
    const at = now();
    lockedJournaledUpdate(controlDir, CHALLENGED_HISTORY_FILE, (current) => {
        const acc = coerceChallengedHistory(current);
        for (const r of rows) {
            const prior = acc[r.entryId];
            acc[r.entryId] = { count: (prior?.count ?? 0) + 1, lastAt: at, lastOp: r.op };
        }
        return acc;
    });
}
export function readChallengedHistory(controlDir) {
    return coerceChallengedHistory(readSidecarJson(controlDir, CHALLENGED_HISTORY_FILE));
}
export const PROJECTION_DEBTS_FILE = "projection-debts.json";
function safeLedgerRel(rel) {
    return typeof rel === "string" && rel.length > 0 && !rel.startsWith("/") && !rel.split(/[\\/]/).includes("..");
}
function malformedDebtRow(row) {
    return (typeof row !== "object" ||
        row === null ||
        typeof row.entryId !== "string" ||
        row.entryId.length === 0 ||
        typeof row.rev !== "string" ||
        row.rev.length === 0 ||
        typeof row.at !== "number" ||
        !Number.isFinite(row.at));
}
function coerceProjectionDebts(raw) {
    if (raw === undefined)
        return { version: 2, rows: [] };
    const rec = raw;
    if (typeof rec !== "object" || rec === null || (rec.version !== 1 && rec.version !== 2) || !Array.isArray(rec.rows)) {
        throw new ControlPlaneCorruptError("memory projection-debt ledger has the wrong shape — refusing (fail-closed; an unreadable debt account must not read as 'no debts')");
    }
    const bad = () => {
        throw new ControlPlaneCorruptError("memory projection-debt ledger: a row is malformed — refusing (fail-closed)");
    };
    const rows = rec.rows;
    if (rec.version === 1) {
        for (const r of rows) {
            const row = r;
            if (malformedDebtRow(row) || !safeLedgerRel(row.relPath))
                bad();
        }
        return { version: 1, rows: rows };
    }
    for (const r of rows) {
        const row = r;
        if (malformedDebtRow(row) || typeof row.scope !== "string" || row.scope.length === 0 || !safeLedgerRel(row.rel))
            bad();
    }
    const unresolved = rec.unresolved === undefined ? undefined : Array.isArray(rec.unresolved) ? rec.unresolved : bad();
    for (const r of unresolved ?? []) {
        const row = r;
        if (malformedDebtRow(row) || !safeLedgerRel(row.relPath))
            bad();
    }
    return { version: 2, rows: rows, ...(unresolved !== undefined ? { unresolved: unresolved } : {}) };
}
function convertProjectionDebts(read, controlDir) {
    if (read.version === 2)
        return { rec: read, converted: false };
    const resolve = v1MemoryPathKeyResolver(controlDir);
    const rows = [];
    const unresolved = [];
    for (const r of read.rows) {
        const key = resolve(r.relPath);
        if (key === undefined)
            unresolved.push(r);
        else
            rows.push({ scope: key.scope, rel: key.rel, entryId: r.entryId, rev: r.rev, at: r.at });
    }
    return { rec: { version: 2, rows, ...(unresolved.length > 0 ? { unresolved } : {}) }, converted: true };
}
function debtSeatKey(scope, rel) {
    return JSON.stringify([scope, rel]);
}
export function stageProjectionDebts(controlDir, rows, now) {
    if (rows.length === 0)
        return { refused: [] };
    const at = now();
    return lockedStrictUpdate(controlDir, PROJECTION_DEBTS_FILE, "memory projection-debt ledger", coerceProjectionDebts, (read) => {
        const { rec } = convertProjectionDebts(read, controlDir);
        const bySeat = new Map(rec.rows.map((r) => [debtSeatKey(r.scope, r.rel), r]));
        const refused = [];
        for (const r of rows) {
            const seat = debtSeatKey(r.scope, r.rel);
            const existing = bySeat.get(seat);
            if (existing !== undefined && existing.entryId !== r.entryId && existing.entryId !== r.replaces) {
                refused.push({ scope: r.scope, rel: r.rel, entryId: r.entryId });
                continue;
            }
            bySeat.set(seat, { scope: r.scope, rel: r.rel, entryId: r.entryId, rev: r.rev, at });
        }
        return { next: { ...rec, version: 2, rows: [...bySeat.values()] }, result: { refused } };
    });
}
export function settleProjectionDebts(controlDir, rows) {
    if (rows.length === 0)
        return;
    const drop = new Set(rows.map((r) => JSON.stringify([r.entryId, r.scope, r.rel])));
    lockedStrictUpdate(controlDir, PROJECTION_DEBTS_FILE, "memory projection-debt ledger", coerceProjectionDebts, (read) => {
        const { rec, converted } = convertProjectionDebts(read, controlDir);
        const kept = rec.rows.filter((r) => !drop.has(JSON.stringify([r.entryId, r.scope, r.rel])));
        if (kept.length === rec.rows.length && !converted)
            return { result: undefined };
        return { next: { ...rec, version: 2, rows: kept }, result: undefined };
    });
}
export function readProjectionDebts(controlDir) {
    const read = coerceProjectionDebts(readStrictSidecar(controlDir, PROJECTION_DEBTS_FILE, "memory projection-debt ledger"));
    if (read.version === 2)
        return read.rows;
    return lockedStrictUpdate(controlDir, PROJECTION_DEBTS_FILE, "memory projection-debt ledger", coerceProjectionDebts, (again) => {
        const { rec, converted } = convertProjectionDebts(again, controlDir);
        return converted ? { next: rec, result: rec.rows } : { result: rec.rows };
    });
}
export function unresolvedProjectionDebts(controlDir) {
    const read = coerceProjectionDebts(readStrictSidecar(controlDir, PROJECTION_DEBTS_FILE, "memory projection-debt ledger"));
    if (read.version === 1)
        return read.rows.map((r) => ({ relPath: r.relPath, entryId: r.entryId }));
    return (read.unresolved ?? []).map((r) => ({ relPath: r.relPath, entryId: r.entryId }));
}
export function writeAllSync(fd, data) {
    const buf = Buffer.from(data, "utf8");
    let written = 0;
    while (written < buf.length) {
        const n = writeSync(fd, buf, written, buf.length - written);
        if (n <= 0)
            throw new Error(`writeAllSync: write made no progress (${written}/${buf.length} bytes) — refusing to leave a truncated file`);
        written += n;
    }
}
export function atomicWriteFileSync(path, data) {
    ensureDirExists(dirname(path));
    const tmp = join(dirname(path), `.tmp-${process.pid}-${Math.random().toString(36).slice(2, 10)}`);
    const fd = openSync(tmp, "w", 0o600);
    try {
        writeAllSync(fd, data);
        fsyncSync(fd);
    }
    finally {
        closeSync(fd);
    }
    try {
        renameSync(tmp, path);
    }
    catch (err) {
        try {
            unlinkSync(tmp);
        }
        catch {
        }
        throw err;
    }
}
export function writeIfChanged(path, data) {
    let current;
    try {
        current = readFileSync(path, "utf8");
    }
    catch {
        current = undefined;
    }
    if (current === data)
        return;
    atomicWriteFileSync(path, data);
}