UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

1,245 lines • 63.7 kB
import { createHash, randomBytes } from "node:crypto";
import { appendFileSync, closeSync, existsSync, fsyncSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { thrownText } from "../../core/thrown-value.js";
import { applyFileRestoreViaEnv, boundaryPublishVerdict, worldStillMatchesMint, captureFileBackupViaEnv, modeProvenDifferent, previewFileDelta, describeTrackKey, fileHistoryExportDigest, resolveTrackKey, trackKeyOf, validateFileHistoryExport, } from "../../core/file-history-store.js";
import { fileHistoryBoundariesToKeep, resolveFileHistoryRetention } from "../../core/file-history-retention.js";
import { atomicWriteFile, ensureDir, sanitizePathComponent, sanitizeScope, writeThenLink } from "./fs-atomic.js";
import { guardCorruptSink } from "./corrupt-read-sink.js";
import { isAbsoluteForFamily } from "../../tools/fs/safety.js";
import { assertAdoptionBootGate } from "./adoption/marker.js";
function trackedDigest(rows) {
    const canonical = [...rows]
        .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
        .map(([key, rec]) => [
        key,
        {
            baseAvailable: rec.baseAvailable,
            mintId: rec.mintId ?? null,
            keyKind: rec.keyKind ?? null,
            keyFamily: rec.keyFamily ?? null,
            observedByLaterTouch: rec.observedByLaterTouch === true,
            versions: [...rec.versions]
                .sort((x, y) => x.version - y.version)
                .map((v) => ({ version: v.version, blobHash: v.blobHash, mode: v.mode ?? null, parentIdent: v.parentIdent ?? null, backupTime: v.backupTime })),
        },
    ]);
    return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
}
function boundaryDigest(files) {
    const canonical = [...files].sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
}
const EMPTY_LEDGER = () => ({
    filesChanged: [],
    identicalSkipped: [],
    refused: [],
    failed: [],
});
const WINDOWS_DEVICE_NAMES = new Set(["con", "prn", "aux", "nul", ...Array.from({ length: 10 }, (_, i) => `com${i}`), ...Array.from({ length: 10 }, (_, i) => `lpt${i}`)]);
function historyPathComponent(raw) {
    const safe = sanitizePathComponent(raw);
    if (safe.length > 200)
        throw new Error(`file history: path component is too long (${safe.length} > 200): ${JSON.stringify(raw.slice(0, 32))}…`);
    if (WINDOWS_DEVICE_NAMES.has(safe.split(".")[0].toLowerCase()))
        throw new Error(`file history: ${JSON.stringify(safe)} is a reserved device name — it would address a device, not a record`);
    if (!/^[A-Za-z0-9_]/.test(safe))
        throw new Error(`file history: path component must start with a letter, digit or underscore: ${JSON.stringify(safe)}`);
    return safe;
}
function descriptorOf(rec) {
    return rec.keyKind !== undefined && rec.keyFamily !== undefined ? { kind: rec.keyKind, family: rec.keyFamily } : undefined;
}
const STALE_JOURNAL_MS = 60 * 60 * 1000;
function pidIsAlive(pid) {
    if (!Number.isInteger(pid) || pid <= 0)
        return false;
    try {
        process.kill(pid, 0);
        return true;
    }
    catch (err) {
        return err.code === "EPERM";
    }
}
function parseJsonRecord(raw) {
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        return null;
    }
    return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
}
function leaseJournalBlob(line, live) {
    const rec = parseJsonRecord(line);
    if (rec !== null && typeof rec.blob === "string")
        live.add(rec.blob);
}
function isTrackedPair(el) {
    if (!Array.isArray(el) || el.length !== 2 || typeof el[0] !== "string")
        return false;
    const rec = el[1];
    if (typeof rec !== "object" || rec === null || Array.isArray(rec))
        return false;
    const versions = rec.versions;
    return Array.isArray(versions) && versions.every((v) => typeof v === "object" && v !== null && !Array.isArray(v));
}
function trackedRowsOf(parsed) {
    if (parsed === null || !Array.isArray(parsed.tracked))
        return null;
    const rows = [];
    for (const el of parsed.tracked) {
        if (!isTrackedPair(el))
            return null;
        rows.push(el);
    }
    return rows;
}
function nestedSetAdd(map, scope, value) {
    let inner = map.get(scope);
    if (inner === undefined)
        map.set(scope, (inner = new Set()));
    inner.add(value);
}
function nestedMapSet(map, scope, key, value) {
    let inner = map.get(scope);
    if (inner === undefined)
        map.set(scope, (inner = new Map()));
    inner.set(key, value);
}
function safeRm(path) {
    try {
        rmSync(path, { force: true, recursive: true });
        return true;
    }
    catch {
        return false;
    }
}
export class FileFileHistoryStore {
    base;
    blobsDir;
    scopesDir;
    onCorruptRead;
    spentAttempts = new Map();
    activeCaptures = new Map();
    activeImports = new Map();
    reapedCache = new Map();
    boundaryKeep;
    lastCommittedAt = 0;
    seededScopes = new Set();
    nextCommittedAt(scope) {
        if (!this.seededScopes.has(scope)) {
            this.seededScopes.add(scope);
            const dir = this.boundaryDir(scope);
            try {
                for (const f of readdirSync(dir)) {
                    if (!f.endsWith(".json"))
                        continue;
                    const order = this.boundaryOrderOf(join(dir, f));
                    if (order !== undefined && order > this.lastCommittedAt)
                        this.lastCommittedAt = order;
                }
            }
            catch {
            }
        }
        this.lastCommittedAt = Math.max(Date.now(), this.lastCommittedAt + 1);
        return this.lastCommittedAt;
    }
    constructor(root, opts) {
        assertAdoptionBootGate(root, "FileFileHistoryStore");
        this.boundaryKeep = resolveFileHistoryRetention("FileFileHistoryStore", opts?.retention);
        this.base = join(root, "file-history");
        this.blobsDir = join(this.base, "blobs");
        this.scopesDir = join(this.base, "scopes");
        ensureDir(this.blobsDir);
        ensureDir(this.scopesDir);
        this.onCorruptRead = guardCorruptSink(opts?.onCorruptRead);
    }
    applyBoundaryRetention(scope, justCommitted) {
        if (this.boundaryKeep === null)
            return;
        try {
            const dir = this.boundaryDir(scope);
            let names;
            try {
                names = readdirSync(dir).filter((f) => f.endsWith(".json"));
            }
            catch (err) {
                if (err.code !== "ENOENT") {
                    this.disclose(dir, `boundary directory unreadable (${err.code ?? "unknown"}) — the retention pass was skipped, so this scope is NOT bounded until it can be read`);
                }
                return;
            }
            if (names.length <= this.boundaryKeep)
                return;
            const readable = [];
            for (const f of names) {
                const path = join(dir, f);
                if (this.readBoundary(path) === null) {
                    this.disclose(join(dir, f), "a boundary in this scope cannot be judged — the retention pass is disabled for the WHOLE scope until it is repaired or removed, so this scope is NOT bounded (trimming around a record whose references cannot be read destroys the versions only it points at)");
                    return;
                }
                readable.push({ id: f.slice(0, -".json".length), order: this.boundaryOrderOf(path) ?? 0 });
            }
            const ordered = readable.sort((a, b) => a.order - b.order || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).map((e) => e.id);
            const keepIds = fileHistoryBoundariesToKeep(ordered, justCommitted, this.boundaryKeep);
            if (keepIds === null)
                return;
            this.reap(scope, keepIds).catch(() => { });
        }
        catch {
        }
    }
    disclose(path, reason) {
        this.onCorruptRead?.({ kind: "file", path, reason });
    }
    scopeDir(scope) {
        return join(this.scopesDir, sanitizeScope(scope));
    }
    trackedPath(scope) {
        return join(this.scopeDir(scope), "tracked.json");
    }
    boundaryDir(scope) {
        return join(this.scopeDir(scope), "boundaries");
    }
    tryBoundaryPath(scope, entryId) {
        try {
            return join(this.boundaryDir(scope), `${historyPathComponent(entryId)}.json`);
        }
        catch {
            return null;
        }
    }
    blobPath(hash) {
        return join(this.blobsDir, sanitizePathComponent(hash));
    }
    readTrackedState(scope) {
        const path = this.trackedPath(scope);
        let raw;
        try {
            raw = readFileSync(path, "utf8");
        }
        catch (err) {
            if (err.code === "ENOENT")
                return { corrupt: false, tracked: new Map() };
            this.disclose(path, `tracked set unreadable (${err.code ?? "unknown"}) — this scope's history is UNUSABLE until it is repaired`);
            return { corrupt: true, tracked: new Map() };
        }
        const parsed = parseJsonRecord(raw);
        if (parsed === null) {
            this.disclose(path, "unparseable tracked set (torn/zero-byte write?) — this scope's history is UNUSABLE until it is repaired");
            return { corrupt: true, tracked: new Map() };
        }
        const rows = trackedRowsOf(parsed);
        if (rows === null) {
            this.disclose(path, "tracked set is not a pair array of [key, record] rows — this scope's history is UNUSABLE until it is repaired");
            return { corrupt: true, tracked: new Map() };
        }
        if (parsed.digest !== undefined && (typeof parsed.digest !== "string" || parsed.digest !== trackedDigest(rows))) {
            this.disclose(path, "tracked set fails its own integrity digest (edited in place, or written by something that is not this store) — this scope's history is UNUSABLE until it is repaired");
            return { corrupt: true, tracked: new Map() };
        }
        return { corrupt: false, tracked: new Map(rows) };
    }
    readTracked(scope) {
        return this.readTrackedState(scope).tracked;
    }
    writeTracked(scope, tracked) {
        const dir = this.scopeDir(scope);
        ensureDir(dir);
        const rows = [...tracked];
        const doc = { formatVersion: "file-history/1", scope, tracked: rows, digest: trackedDigest(rows) };
        atomicWriteFile(join(this.base, "tmp"), this.trackedPath(scope), JSON.stringify(doc));
    }
    readBoundary(path) {
        let raw;
        try {
            raw = readFileSync(path, "utf8");
        }
        catch (err) {
            if (err.code === "ENOENT")
                return null;
            this.disclose(path, `boundary unreadable (${err.code ?? "unknown"}) — read as no boundary`);
            return null;
        }
        {
            const doc = parseJsonRecord(raw);
            if (doc === null) {
                this.disclose(path, "unparseable boundary (torn/zero-byte write?) — read as no boundary");
                return null;
            }
            if (doc.formatVersion !== "file-history/1" || !Array.isArray(doc.files)) {
                this.disclose(path, "boundary is not a file-history/1 pair array — read as no boundary");
                return null;
            }
            const parsed = { formatVersion: doc.formatVersion, files: doc.files, digest: doc.digest };
            for (const pair of parsed.files) {
                if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || !Number.isInteger(pair[1]) || pair[1] < 1) {
                    this.disclose(path, "boundary carries a malformed mapping row — read as no boundary");
                    return null;
                }
            }
            if (new Set(parsed.files.map(([k]) => k)).size !== parsed.files.length) {
                this.disclose(path, "boundary maps the same path twice — read as no boundary");
                return null;
            }
            if (typeof parsed.digest !== "string" || parsed.digest !== boundaryDigest(parsed.files)) {
                this.disclose(path, "boundary fails its own integrity digest (rows added or dropped?) — read as no boundary");
                return null;
            }
            return new Map(parsed.files);
        }
    }
    boundaryCommittedAt(path) {
        try {
            const doc = parseJsonRecord(readFileSync(path, "utf8"));
            const at = doc?.committedAt;
            return typeof at === "number" && Number.isFinite(at) ? at : undefined;
        }
        catch {
            return undefined;
        }
    }
    unjudgeableBoundaryIdsOf(scope) {
        const dir = this.boundaryDir(scope);
        let names;
        try {
            names = readdirSync(dir);
        }
        catch (err) {
            return err.code === "ENOENT" ? null : [];
        }
        const bad = names.filter((f) => f.endsWith(".json") && this.readBoundary(join(dir, f)) === null).map((f) => f.slice(0, -".json".length));
        return bad.length > 0 ? bad : null;
    }
    carryConsumedIds(fromScope, toScope) {
        const spent = this.readReapedIds(fromScope);
        if (spent === null) {
            this.disclose(this.reapedLogPath(fromScope), "source consumed-id ledger unreadable — the adoption is refused rather than published without its lineage's tombstones");
            return false;
        }
        if (spent.size === 0)
            return true;
        return this.markReaped(toScope, [...spent]);
    }
    reapedLogPath(scope) {
        return `${this.scopeDir(scope)}.reaped.log`;
    }
    readReapedIds(scope) {
        const path = this.reapedLogPath(scope);
        let size;
        try {
            size = statSync(path).size;
        }
        catch (err) {
            if (err.code === "ENOENT")
                return new Set();
            this.disclose(path, `consumed-id ledger unreadable (${err.code ?? "unknown"})`);
            return null;
        }
        const cached = this.reapedCache.get(scope);
        if (cached !== undefined && cached.size === size)
            return cached.ids;
        try {
            const ids = new Set(readFileSync(path, "utf8")
                .split("\n")
                .map((l) => l.trim())
                .filter((l) => l !== ""));
            this.reapedCache.set(scope, { size, ids });
            return ids;
        }
        catch (err) {
            this.disclose(path, `consumed-id ledger unreadable (${err.code ?? "unknown"})`);
            return null;
        }
    }
    markReaped(scope, ids) {
        if (ids.length === 0)
            return true;
        const path = this.reapedLogPath(scope);
        try {
            ensureDir(this.scopesDir);
            const payload = ids.map((id) => `${id}\n`).join("");
            const fd = openSync(path, "a");
            try {
                writeFileSync(fd, payload);
                fsyncSync(fd);
            }
            finally {
                closeSync(fd);
            }
            const cached = this.reapedCache.get(scope);
            if (cached === undefined)
                this.reapedCache.delete(scope);
            else
                this.reapedCache.set(scope, { size: cached.size + Buffer.byteLength(payload, "utf8"), ids: new Set([...cached.ids, ...ids]) });
            return true;
        }
        catch (err) {
            this.disclose(path, `consumed-id ledger could not be appended (${err.code ?? "unknown"}) — the boundaries it names are NOT being reaped, so this scope is not bounded until it can be written`);
            return false;
        }
    }
    boundaryOrderOf(path) {
        const stamped = this.boundaryCommittedAt(path);
        if (stamped !== undefined)
            return stamped;
        try {
            return statSync(path).mtimeMs;
        }
        catch {
            return undefined;
        }
    }
    putBlob(hash, bytes) {
        try {
            writeThenLink(this.blobPath(hash), bytes);
        }
        catch (err) {
            if (err.code !== "EEXIST")
                throw err;
        }
    }
    async trackEdit(scope, absPath, env, root, signal) {
        try {
            const { key, kind, family } = describeTrackKey(root, absPath);
            const before = this.readTrackedState(scope);
            if (before.corrupt)
                return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" is unreadable — refusing to mint a first-touch record over an unusable graph` } };
            const existing = before.tracked.get(key);
            if (existing !== undefined) {
                if (existing.observedByLaterTouch !== true) {
                    existing.observedByLaterTouch = true;
                    this.writeTracked(scope, before.tracked);
                }
                return { ok: true, minted: false };
            }
            const cap = await captureFileBackupViaEnv(env, absPath, root, signal);
            const after = this.readTrackedState(scope);
            if (after.corrupt)
                return { ok: false, error: { code: "track_failed", message: `file history for scope "${scope}" became unreadable during the first-touch capture — nothing was recorded` } };
            const tracked = after.tracked;
            const raced = tracked.get(key);
            if (raced !== undefined) {
                if (raced.observedByLaterTouch !== true) {
                    raced.observedByLaterTouch = true;
                    this.writeTracked(scope, tracked);
                }
                return { ok: true, minted: false };
            }
            if (cap.state === "aborted") {
                return { ok: false, error: { code: "aborted", message: `first-touch backup of ${absPath} aborted` } };
            }
            const now = new Date().toISOString();
            const mintId = randomBytes(8).toString("hex");
            if (cap.state === "unreadable") {
                tracked.set(key, { baseAvailable: false, mintId, keyKind: kind, keyFamily: family, versions: [] });
                this.writeTracked(scope, tracked);
                return { ok: true, minted: true };
            }
            if (cap.state === "file") {
                this.putBlob(cap.hash, cap.bytes);
                tracked.set(key, { baseAvailable: true, mintId, keyKind: kind, keyFamily: family, versions: [{ version: 1, blobHash: cap.hash, backupTime: now, ...(cap.mode !== undefined ? { mode: cap.mode } : {}), ...(cap.parentIdent !== undefined ? { parentIdent: cap.parentIdent } : {}) }] });
                this.writeTracked(scope, tracked);
                return { ok: true, minted: true };
            }
            tracked.set(key, { baseAvailable: true, mintId, keyKind: kind, keyFamily: family, versions: [{ version: 1, blobHash: null, backupTime: now, ...(cap.parentIdent !== undefined ? { parentIdent: cap.parentIdent } : {}) }] });
            this.writeTracked(scope, tracked);
            return { ok: true, minted: true };
        }
        catch (err) {
            return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${thrownText(err)}` } };
        }
    }
    async annulTrack(scope, absPath, root, verify) {
        try {
            const key = trackKeyOf(root, absPath);
            const state = this.readTrackedState(scope);
            if (state.corrupt) {
                return { ok: false, error: { code: "conflict", message: `file history for scope "${scope}" is unreadable — the first-touch record (if any) was left in place; repair the graph before retraction can be judged` } };
            }
            const tracked = state.tracked;
            const rec = tracked.get(key);
            if (rec === undefined)
                return { ok: true };
            for (const entryId of this.listBoundaryIds(scope)) {
                const bp = this.tryBoundaryPath(scope, entryId);
                const mapping = bp === null ? null : this.readBoundary(bp);
                if (mapping?.has(key) === true) {
                    return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} is referenced by a committed boundary — it is history now, not a retractable mint` } };
                }
            }
            if (rec.versions.length > 1) {
                return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} has grown past its first version — only the mint itself is retractable` } };
            }
            if (rec.observedByLaterTouch === true) {
                return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} has since been served to another edit of the same path — it is that edit's baseline now, not a retractable mint` } };
            }
            const proof = verify === undefined ? undefined : await worldStillMatchesMint(verify.env, absPath, root, rec.baseAvailable, rec.versions[0]?.blobHash ?? null, verify.signal);
            if (proof !== undefined && !proof.unchanged) {
                return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} is NOT retractable: ${proof.reason} — the write this retraction compensates for may have changed the file` } };
            }
            const fresh = this.readTrackedState(scope);
            if (fresh.corrupt)
                return { ok: false, error: { code: "conflict", message: `file history for scope "${scope}" became unreadable — the record was left in place` } };
            const still = fresh.tracked.get(key);
            if (still === undefined)
                return { ok: true };
            if (still.observedByLaterTouch === true || still.versions.length > 1) {
                return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} stopped being a retractable mint while this retraction was being proven` } };
            }
            if (still.mintId !== rec.mintId || still.baseAvailable !== rec.baseAvailable || still.versions.length !== rec.versions.length || still.versions[0]?.blobHash !== rec.versions[0]?.blobHash || still.versions[0]?.backupTime !== rec.versions[0]?.backupTime) {
                return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was replaced while this retraction was being proven — the record now there belongs to a newer edit` } };
            }
            for (const entryId of this.listBoundaryIds(scope)) {
                const bp2 = this.tryBoundaryPath(scope, entryId);
                const mapping2 = bp2 === null ? null : this.readBoundary(bp2);
                if (mapping2?.has(key) === true) {
                    return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was frozen by a boundary committed while this retraction was being proven — it is history now` } };
                }
            }
            fresh.tracked.delete(key);
            this.writeTracked(scope, fresh.tracked);
            return { ok: true };
        }
        catch (err) {
            return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${thrownText(err)}` } };
        }
    }
    scopeIsEmptySync(scope) {
        const state = this.readTrackedState(scope);
        if (state.corrupt)
            return false;
        return state.tracked.size === 0 && this.listBoundaryIds(scope).length === 0;
    }
    publishStagedScopeSync(scope, staging) {
        if (!this.scopeIsEmptySync(scope))
            return "not_empty";
        const target = this.scopeDir(scope);
        const destroyed = [];
        for (const f of (() => {
            try {
                return readdirSync(this.boundaryDir(scope));
            }
            catch {
                return [];
            }
        })()) {
            if (f.endsWith(".json"))
                destroyed.push(f.slice(0, -".json".length));
        }
        if (!this.markReaped(scope, destroyed))
            return "ledger_failed";
        safeRm(target);
        renameSync(staging, target);
        return "published";
    }
    listBoundaryIds(scope) {
        const dir = this.boundaryDir(scope);
        let entries;
        try {
            entries = readdirSync(dir);
        }
        catch (err) {
            if (err.code !== "ENOENT") {
                this.disclose(dir, `boundary directory unreadable (${err.code ?? "unknown"}) — listed as no boundaries`);
            }
            return [];
        }
        return entries
            .filter((f) => f.endsWith(".json"))
            .filter((f) => this.readBoundary(join(dir, f)) !== null)
            .map((f) => f.slice(0, -".json".length));
    }
    async snapshot(scope, entryId, env, root, signal) {
        const bpath = this.tryBoundaryPath(scope, entryId);
        if (bpath === null)
            return { ok: false, error: { code: "snapshot_failed", message: `unsafe boundary key ${JSON.stringify(entryId)}` } };
        if (this.readBoundary(bpath) !== null)
            return { ok: true };
        const consumed = this.readReapedIds(scope);
        if (consumed === null) {
            return { ok: false, error: { code: "snapshot_failed", message: `the consumed-entry ledger for scope "${scope}" cannot be read — refusing to mint a boundary while it is impossible to tell a fresh entry id from one retention already reaped` } };
        }
        if (consumed.has(entryId)) {
            return { ok: false, error: { code: "snapshot_failed", message: `entry "${entryId}" already had a boundary and it was reaped by retention — a fresh capture now would record post-turn bytes under a pre-turn key, so this entry has no boundary and cannot get one back` } };
        }
        if (this.spentAttempts.get(scope)?.has(entryId) === true) {
            return { ok: false, error: { code: "snapshot_failed", message: `capture lease for entry "${entryId}" is closed (a prior attempt failed); no complete pre-close staging exists, so this turn has no boundary` } };
        }
        if (this.activeCaptures.get(scope)?.has(entryId) === true) {
            return { ok: false, error: { code: "snapshot_failed", message: `a capture attempt for entry "${entryId}" is already in flight — concurrent same-key capture is refused (the in-flight attempt owns the lease)` } };
        }
        const journal = join(this.scopeDir(scope), "staging", `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}.jsonl`);
        nestedMapSet(this.activeCaptures, scope, entryId, journal);
        try {
            ensureDir(join(this.scopeDir(scope), "staging"));
            appendFileSync(journal, `${JSON.stringify({ entryId, pid: process.pid, startedAt: new Date().toISOString() })}\n`);
            const state = this.readTrackedState(scope);
            if (state.corrupt) {
                nestedSetAdd(this.spentAttempts, scope, entryId);
                return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" is unreadable — a boundary minted over an unusable graph would claim to restore state that is not there` } };
            }
            const tracked = state.tracked;
            const staged = new Map();
            for (const [key, rec] of tracked) {
                if (signal?.aborted) {
                    nestedSetAdd(this.spentAttempts, scope, entryId);
                    return { ok: false, error: { code: "aborted", message: `boundary capture for entry "${entryId}" aborted` } };
                }
                const resolved = resolveTrackKey(root, key, descriptorOf(rec));
                if (!resolved.ok) {
                    nestedSetAdd(this.spentAttempts, scope, entryId);
                    return { ok: false, error: { code: "snapshot_failed", message: resolved.reason } };
                }
                const cap = await captureFileBackupViaEnv(env, resolved.abs, root, signal);
                if (cap.state === "aborted") {
                    nestedSetAdd(this.spentAttempts, scope, entryId);
                    return { ok: false, error: { code: "aborted", message: `boundary capture for entry "${entryId}" aborted` } };
                }
                if (cap.state === "unreadable") {
                    nestedSetAdd(this.spentAttempts, scope, entryId);
                    return { ok: false, error: { code: "snapshot_failed", message: cap.cause } };
                }
                const last = rec.versions[rec.versions.length - 1];
                if (cap.state === "absent") {
                    if (last !== undefined && last.blobHash === null)
                        staged.set(key, { kind: "unchanged", version: last.version });
                    else
                        staged.set(key, { kind: "changed", blobHash: null, ...(cap.parentIdent !== undefined ? { parentIdent: cap.parentIdent } : {}) });
                    continue;
                }
                if (last !== undefined && last.blobHash === cap.hash && !modeProvenDifferent(last.mode, cap.mode)) {
                    staged.set(key, { kind: "unchanged", version: last.version });
                    continue;
                }
                appendFileSync(journal, `${JSON.stringify({ blob: cap.hash })}\n`);
                this.putBlob(cap.hash, cap.bytes);
                staged.set(key, { kind: "changed", blobHash: cap.hash, ...(cap.mode !== undefined ? { mode: cap.mode } : {}), ...(cap.parentIdent !== undefined ? { parentIdent: cap.parentIdent } : {}) });
            }
            if (!boundaryPublishVerdict({ aborted: signal?.aborted === true }).publish) {
                nestedSetAdd(this.spentAttempts, scope, entryId);
                return { ok: false, error: { code: "aborted", message: `boundary capture for entry "${entryId}" aborted (fenced — never committed)` } };
            }
            const currentState = this.readTrackedState(scope);
            if (currentState.corrupt) {
                nestedSetAdd(this.spentAttempts, scope, entryId);
                return { ok: false, error: { code: "snapshot_failed", message: `file history for scope "${scope}" became unreadable during the capture — no boundary was published` } };
            }
            const current = currentState.tracked;
            for (const [key, cap] of staged) {
                if (cap.kind !== "unchanged")
                    continue;
                if (current.get(key)?.versions.some((v) => v.version === cap.version) !== true) {
                    nestedSetAdd(this.spentAttempts, scope, entryId);
                    return { ok: false, error: { code: "snapshot_failed", message: `staged reference ${key}@v${cap.version} no longer exists (reaped mid-capture)` } };
                }
            }
            const mapping = [];
            const now = new Date().toISOString();
            for (const [key, cap] of staged) {
                if (cap.kind === "unchanged") {
                    mapping.push([key, cap.version]);
                    continue;
                }
                const rec = current.get(key);
                if (rec === undefined)
                    continue;
                const last = rec.versions[rec.versions.length - 1];
                const version = last !== undefined ? last.version + 1 : rec.baseAvailable ? 1 : 2;
                rec.versions.push({ version, blobHash: cap.blobHash, backupTime: now, ...(cap.mode !== undefined ? { mode: cap.mode } : {}), ...(cap.parentIdent !== undefined ? { parentIdent: cap.parentIdent } : {}) });
                mapping.push([key, version]);
            }
            this.writeTracked(scope, current);
            ensureDir(this.boundaryDir(scope));
            const doc = { formatVersion: "file-history/1", files: mapping, digest: boundaryDigest(mapping), committedAt: this.nextCommittedAt(scope) };
            try {
                writeThenLink(bpath, JSON.stringify(doc));
            }
            catch (err) {
                if (err.code !== "EEXIST")
                    throw err;
                if (this.readBoundary(bpath) === null) {
                    nestedSetAdd(this.spentAttempts, scope, entryId);
                    return { ok: false, error: { code: "snapshot_failed", message: `a boundary file for entry "${entryId}" already exists but cannot be read — this turn has no usable boundary (the unreadable file is left in place for an operator rather than silently replaced)` } };
                }
            }
            this.applyBoundaryRetention(scope, entryId);
            return { ok: true };
        }
        catch (err) {
            nestedSetAdd(this.spentAttempts, scope, entryId);
            return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${thrownText(err)}` } };
        }
        finally {
            this.activeCaptures.get(scope)?.delete(entryId);
            safeRm(journal);
        }
    }
    async restore(scope, entryId, env, root, signal) {
        const ledger = EMPTY_LEDGER();
        let modeNotPreserved = 0;
        try {
            const bpath = this.tryBoundaryPath(scope, entryId);
            const boundary = bpath === null ? null : this.readBoundary(bpath);
            if (boundary === null) {
                return { outcome: "failed", ...ledger, error: { code: "not_found", message: `no history boundary for entry "${entryId}"` } };
            }
            const state = this.readTrackedState(scope);
            if (state.corrupt) {
                return { outcome: "failed", ...ledger, error: { code: "restore_failed", message: `file history for scope "${scope}" is unreadable — the boundary exists but its version records cannot be read, so NOTHING was restored` } };
            }
            const tracked = state.tracked;
            for (const key of [...tracked.keys()].sort()) {
                const rec = tracked.get(key);
                if (signal?.aborted) {
                    ledger.failed.push({ path: key, reason: "aborted before attempt" });
                    continue;
                }
                let targetVersion = boundary.get(key);
                if (targetVersion === undefined) {
                    if (!rec.baseAvailable) {
                        ledger.refused.push({ path: key, reason: "base_unavailable" });
                        continue;
                    }
                    targetVersion = 1;
                }
                const version = rec.versions.find((v) => v.version === targetVersion);
                if (version === undefined) {
                    ledger.failed.push({ path: key, reason: `version ${targetVersion} record missing (store integrity)` });
                    continue;
                }
                const resolved = resolveTrackKey(root, key, descriptorOf(rec));
                if (!resolved.ok) {
                    ledger.refused.push({ path: key, reason: resolved.reason });
                    continue;
                }
                let target;
                if (version.blobHash === null)
                    target = { absent: true };
                else {
                    let bytes;
                    try {
                        bytes = readFileSync(this.blobPath(version.blobHash));
                    }
                    catch {
                        bytes = undefined;
                    }
                    if (bytes === undefined) {
                        ledger.failed.push({ path: key, reason: `blob ${version.blobHash} missing (store integrity)` });
                        continue;
                    }
                    if (createHash("sha256").update(bytes).digest("hex") !== version.blobHash) {
                        ledger.failed.push({ path: key, reason: `blob ${version.blobHash} fails content-address integrity (store corruption) — the file was left untouched` });
                        this.disclose(this.blobPath(version.blobHash), "blob bytes do not hash to their own name — the restore refused to apply them");
                        continue;
                    }
                    target = { bytes, hash: version.blobHash };
                }
                let applied;
                try {
                    applied = await applyFileRestoreViaEnv(env, resolved.abs, target, {
                        root,
                        ...(version.mode !== undefined ? { mode: version.mode } : {}),
                        ...(version.parentIdent !== undefined ? { parentIdent: version.parentIdent } : {}),
                        ...(signal !== undefined ? { signal } : {}),
                    });
                }
                catch (err) {
                    applied = { outcome: "failed", reason: thrownText(err) };
                }
                if (applied.outcome === "applied")
                    ledger.filesChanged.push(key);
                else if (applied.outcome === "identical")
                    ledger.identicalSkipped.push(key);
                else if (applied.outcome === "refused")
                    ledger.refused.push({ path: key, reason: applied.reason });
                else
                    ledger.failed.push({ path: key, reason: applied.reason });
                if ((applied.outcome === "applied" || applied.outcome === "identical") && applied.modeNotPreserved === true)
                    modeNotPreserved++;
            }
            return { outcome: ledger.refused.length > 0 || ledger.failed.length > 0 ? "partial" : "complete", ...ledger, ...(modeNotPreserved > 0 ? { modeNotPreserved } : {}) };
        }
        catch (err) {
            const touched = ledger.filesChanged.length > 0 || ledger.identicalSkipped.length > 0 || ledger.refused.length > 0 || ledger.failed.length > 0;
            return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: thrownText(err) } };
        }
    }
    async canRestore(scope, entryId) {
        const bpath = this.tryBoundaryPath(scope, entryId);
        return bpath !== null && this.readBoundary(bpath) !== null;
    }
    async diffStats(scope, entryId, env, root) {
        try {
            const bpath = this.tryBoundaryPath(scope, entryId);
            const boundary = bpath === null ? null : this.readBoundary(bpath);
            if (boundary === null)
                return null;
            const state = this.readTrackedState(scope);
            if (state.corrupt)
                return null;
            const filesChanged = [];
            let insertions = 0;
            let deletions = 0;
            for (const key of [...state.tracked.keys()].sort()) {
                const rec = state.tracked.get(key);
                let targetVersion = boundary.get(key);
                if (targetVersion === undefined) {
                    if (!rec.baseAvailable)
                        continue;
                    targetVersion = 1;
                }
                const version = rec.versions.find((v) => v.version === targetVersion);
                const resolved = resolveTrackKey(root, key, descriptorOf(rec));
                if (version === undefined || !resolved.ok)
                    continue;
                let targetBytes;
                if (version.blobHash !== null) {
                    try {
                        targetBytes = readFileSync(this.blobPath(version.blobHash));
                    }
                    catch {
                        continue;
                    }
                    if (createHash("sha256").update(targetBytes).digest("hex") !== version.blobHash)
                        continue;
                }
                const delta = await previewFileDelta(env, resolved.abs, version.blobHash, targetBytes, version.mode);
                if (delta === undefined)
                    continue;
                filesChanged.push(key);
                insertions += delta.insertions;
                deletions += delta.deletions;
            }
            return { filesChanged, insertions, deletions };
        }
        catch {
            return null;
        }
    }
    async reap(scope, keepEntryIds) {
        const keep = new Set(keepEntryIds);
        let removed = 0;
        const dir = this.boundaryDir(scope);
        let entries = [];
        try {
            entries = readdirSync(dir);
        }
        catch (err) {
            if (err.code !== "ENOENT") {
                this.disclose(dir, `boundary directory unreadable (${err.code ?? "unknown"}) — the WHOLE reap is refused (0 dropped, no version or blob collection) because an unlistable directory cannot distinguish "no boundaries" from "boundaries we cannot see"`);
                return 0;
            }
        }
        const mappings = new Map();
        for (const file of entries) {
            if (!file.endsWith(".json"))
                continue;
            const mapping = this.readBoundary(join(dir, file));
            if (mapping === null) {
                this.disclose(join(dir, file), "a boundary in this scope cannot be judged — the WHOLE reap is refused (0 dropped, nothing collected) until it is repaired or removed, because trimming around a record whose references cannot be read destroys the versions only it points at");
                return 0;
            }
            mappings.set(file.slice(0, -".json".length), mapping);
        }
        const survivors = new Set(keep);
        for (const file of entries) {
            if (!file.endsWith(".json"))
                continue;
            const id = file.slice(0, -".json".length);
            if (keep.has(id))
                continue;
            if (!this.markReaped(scope, [id])) {
                survivors.add(id);
                continue;
            }
            if (safeRm(join(dir, file))) {
                removed++;
            }
            else {
                survivors.add(id);
            }
        }
        const referenced = new Map();
        for (const [id, mapping] of mappings) {
            if (!survivors.has(id))
                continue;
            for (const [key, v] of mapping) {
                let s = referenced.get(key);
                if (s === undefined)
                    referenced.set(key, (s = new Set()));
                s.add(v);
            }
        }
        const tracked = this.readTracked(scope);
        if (tracked.size > 0) {
            for (const [key, rec] of tracked) {
                rec.versions = rec.versions.filter((v) => v.version === 1 || (referenced.get(key)?.has(v.version) ?? false));
            }
            try {
                this.writeTracked(scope, tracked);
            }
            catch (err) {
                this.disclose(this.trackedPath(scope), `version-chain trim could not be committed (${thrownText(err)}) — trim skipped this pass; untrimmed versions keep their blobs live`);
            }
        }
        this.collectBlobs();
        return removed;
    }
    collectBlobs() {
        const live = new Set();
        let complete = true;
        let scopeDirs;
        try {
            scopeDirs = readdirSync(this.scopesDir);
        }
        catch (err) {
            if (err.code === "ENOENT")
                return;
            this.disclose(this.scopesDir, `scope root unreadable (${err.code ?? "unknown"}) — blob GC aborted, every blob kept`);
            return;
        }
        const liveJournals = new Set([...[...this.activeCaptures.values()].flatMap((m) => [...m.values()]), ...[...this.activeImports.values()].flatMap((set) => [...set])]);
        for (const name of scopeDirs) {
            const sdir = join(this.scopesDir, name);
            try {
                if (!statSync(sdir).isDirectory())
                    continue;
            }
            catch {
                continue;
            }
            const tpath = join(sdir, "tracked.json");
            if (existsSync(tpath)) {
                let doc;
                try {
                    doc = parseJsonRecord(readFileSync(tpath, "utf8"));
                }
                catch {
                    doc = null;
                }
                const rows = trackedRowsOf(doc);
                if (rows === null) {
                    complete = false;
                    this.disclose(tpath, "tracked set unreadable during the live-set scan — blob GC aborted, every blob kept");
                    break;
                }
                if (doc?.digest !== undefined && (typeof doc.digest !== "string" || doc.digest !== trackedDigest(rows))) {
                    complete = false;
                    this.disclose(tpath, "tracked set fails its own integrity digest during the live-set scan — blob GC aborted, every blob kept (a live set built from damaged rows would delete the bytes a repair needs)");
                    break;
                }
                for (const [, rec] of rows)
                    for (const v of rec.versions)
                        if (v.blobHash !== null)
                            live.add(v.blobHash);
            }
            const stagingDir = join(sdir, "staging");
            let journals = [];
            try {
                journals = readdirSync(stagingDir);
            }
            catch (err) {
                if (err.code !== "ENOENT") {
                    complete = false;
                    this.disclose(stagingDir, `staging directory unreadable (${err.code ?? "unknown"}) — blob GC aborted, every blob kept`);
                    break;
                }
            }
            for (const j of journals) {
                const jpath = join(stagingDir, j);
                let raw;
                try {
                    raw = readFileSync(jpath, "utf8");
                }
                catch (err) {
                    if (err.code === "ENOENT")
                        continue;
                    complete = false;
                    this.disclose(jpath, "staging journal unreadable — blob GC aborted, every blob kept");
                    break;
                }
                const lines = raw.split("\n").filter((l) => l !== "");
                const header = parseJsonRecord(lines[0] ?? "");
                if (header === null || typeof header.pid !== "number" || typeof header.startedAt !== "string") {
                    this.disclose(jpath, "staging journal header is unreadable — the journal and its leased blobs are KEPT (an unknown owner is never proof of death)");
                    for (const line of lines)
                        leaseJournalBlob(line, live);
                    continue;
                }
                const ownedHere = liveJournals.has(jpath);
                const ownerAlive = !ownedHere && typeof header.pid === "number" && pidIsAlive(header.pid);
                const startedMs = Date.parse(header.startedAt ?? "");
                const young = Number.isFinite(startedMs) && Date.now() - startedMs < STALE_JOURNAL_MS;
                if (!ownedHere && !ownerAlive && !young) {
                    safeRm(jpath);
                    continue;
                }
                for (const line of lines)
                    leaseJournalBlob(line, live);
            }
            if (!complete)
                break;
        }
        if (!complete)
            return;
        let blobs;
        try {
            blobs = readdirSync(this.blobsDir);
        }
        catch (err) {
            if (err.code !== "ENOENT") {
                this.disclose(this.blobsDir, `blob directory unreadable (${err.code ?? "unknown"}) — blob GC skipped, every blob kept`);
            }
            return;
        }
        for (const blob of blobs)
            if (!live.has(blob))
                safeRm(join(this.blobsDir, blob));
    }
    async adoptScope(fromScope, toScope) {
        try {
            if (fromScope === toScope)
                return { ok: true };
            const targetState = this.readTrackedState(toScope);
            if (targetState.corrupt) {
                return { ok: false, error: { code: "adopt_failed", message: `adoptScope target "${toScope}" has an unreadable tracked set — a scope whose bytes cannot be judged is not an empty target` } };
            }
            const targetTracked = targetState.tracked;
            const targetBoundaries = this.listBoundaryIds(toScope);
            if (targetTracked.size > 0 || targetBoundaries.length > 0) {
                return { ok: false, error: { code: "conflict", message: `adoptScope target "${toScope}" already has history of its own — adoption is legal only onto an EMPTY scope (the fork/resume boundary before the new session's first turn)` } };
            }
            const sourceState = this.readTrackedState(fromScope);
            if (sourceState.corrupt) {
                return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has an unreadable tracked set — adopting it would publish boundaries with no version records behind them` } };
            }
            const source = sourceState.tracked;
            const unjudgeable = this.unjudgeableBoundaryIdsOf(fromScope);
            if (unjudgeable !== null) {
                return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" has ${unjudgeable.length > 0 ? `boundaries that cannot be judged (${unjudgeable.slice(0, 3).map((i) => JSON.stringify(i)).join(", ")}${unjudgeable.length > 3 ? ", …" : ""})` : "a boundary directory that cannot be listed"} — adopting it would publish a graph that silently drops those turns and hands their entry ids back to the fork; nothing was published` } };
            }
            const sourceBoundaries = this.listBoundaryIds(fromScope);
            if (source.size === 0 && sourceBoundaries.length === 0) {
                if (!this.carryConsumedIds(fromScope, toScope)) {
                    return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — refusing rather than reporting a carry that would let an already-consumed entry id be minted again in the fork` } };
                }
                return { ok: true };
            }
            for (const [key, rec] of source) {
                for (const v of rec.versions) {
                    if (v.blobHash === null)
                        continue;
                    let bytes;
                    try {
                        bytes = readFileSync(this.blobPath(v.blobHash));
                    }
                    catch {
                        bytes = undefined;
                    }
                    if (bytes === undefined || createHash("sha256").update(bytes).digest("hex") !== v.blobHash) {
                        return { ok: false, error: { code: "adopt_failed", message: `adoptScope source "${fromScope}" references blob ${v.blobHash} for ${key}@v${v.version}, which is missing or fails content-address integrity — nothing was published` } };
                    }
                }
            }
            const staging = `${this.scopeDir(toScope)}.adopting-${process.pid}-${randomBytes(6).toString("hex")}`;
            safeRm(staging);
            try {
                ensureDir(join(staging, "boundaries"));
                const adoptedRows = [...source];
                const doc = { formatVersion: "file-history/1", scope: toScope, tracked: adoptedRows, digest: trackedDigest(adoptedRows) };
                atomicWriteFile(join(this.base, "tmp"), join(staging, "tracked.json"), JSON.stringify(doc));
                for (const id of sourceBoundaries) {
                    const bp = this.tryBoundaryPath(fromScope, id);
                    const mapping = bp === null ? null : this.readBoundary(bp);
                    if (mapping === null)
                        continue;
                    for (const [key, version] of mapping) {
                        if (source.get(key)?.versions.some((v) => v.version === version) !== true) {
                            throw new Error(`source boundary ${id} references ${key}@v${version}, which the source's version chains do not carry`);
                        }
                    }
                    const sourceCommittedAt = bp === null ? undefined : this.boundaryOrderOf(bp);
                    const bdoc = { formatVersion: "file-history/1", files: [...mapping], digest: boundaryDigest([...mapping]), ...(sourceCommittedAt !== undefined ? { committedAt: sourceCommittedAt } : {}) };
                    atomicWriteFile(join(this.base, "tmp"), join(staging, "boundaries", `${historyPathComponent(id)}.json`), JSON.stringify(bdoc));
                }
                if (!this.carryConsumedIds(fromScope, toScope)) {
                    safeRm(staging);
                    return { ok: false, error: { code: "adopt_failed", message: `adoptScope could not carry the consumed-entry ledger from "${fromScope}" to "${toScope}" — nothing was published, because a fork missing its lineage's tombstones can mint an already-consumed entry id` } };
                }
                const published = this.publishStagedScopeSync(toScope, staging);
                if (published !== "published") {
                    safeRm(staging);
                    return published === "not_empty"
                        ? { ok: false, error: { code: "conflict", message: `adoptScope target "${toScope}" gained history of its own while this adoption was being staged — nothing was published` } }
                        : { ok: false, error: { code: "adopt_failed", message: `adoptScope could not record the consumed-entry tombstones for target "${toScope}"'s unreadable boundary residue — nothing was published, because replacing records nothing remembers would hand their already-consumed entry ids back to the fork` } };
                }
            }
            catch (err) {
                safeRm(staging);
                throw err;
            }
            return { ok: true };
        }
        catch (err) {
            return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${thrownText(err)}` } };
        }
    }
    async exportHistory(scope) {
        const state = this.readTrackedState(scope);
        if (state.corrupt) {
            return null;
        }
        const tracked = state.tracked;
        const boundaryIds = this.listBoundaryIds(scope);
        if (tracked.size === 0 && boundaryIds.length === 0)
            return null;
        const boundaries = [];
        for (const entryId of [...boundaryIds].sort()) {
            const bp = this.tryBoundaryPath(scope, entryId);
            const mapping = bp === null ? null : this.readBoundary(bp);
            if (mapping === null)
                continue;
            boundaries.push({ entryId, files: [...mapping.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([relOrAbsPath, version]) => ({ relOrAbsPath, version })) });
        }
        return {
            formatVersion: "file-history/1",
            tracked: [...tracked.entries()]
                .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
                .map(([key, rec]) => ({
                relOrAbsPath: key,
                keyKind: rec.keyKind ?? (isAbsoluteForFamily(rec.keyFamily ?? "posix", key) ? "absolute" : "root-relative"),
                keyFamily: rec.keyFamily ?? "posix",
                baseAvailable: rec.baseAvailable,
                versions: rec.versions.map((v) => ({ version: v.version, blobHash: v.blobHash, ...(v.mode !== undefined ? { mode: v.mode } : {}), ...(v.parentIdent !== undefined ? { parentIdent: v.parentIdent } : {}), backupTime: v.backupTime })),
            })),
            boundaries,
        };
    }
    async importHistory(scope, data, srcGetBlob) {
        try {
            const shaped = validateFileHistoryExport(data);
            if (!shaped.ok)
                return shaped;
            const existing = await this.exportHistory(scope);
            let identicalRepublish = false;
            if (existing !== null) {
                if (fileHistoryExportDigest(existing) === fileHistoryExportDigest(data))
                    identicalRepublish = true;
                else
                    return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" already holds a DIFFERENT history — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)` } };
            }
            const importJournal = join(this.scopeDir(scope), "staging", `import-${Date.now().toString(36)}-${randomBytes(6).toString("hex")}.jsonl`);
            ensureDir(join(this.scopeDir(scope), "staging"));
            appendFileSync(importJournal, `${JSON.stringify({ entryId: `import:${scope}`, pid: process.pid, startedAt: new Date().toISOString() })}\n`);
            nestedSetAdd(this.activeImports, scope, importJournal);
            for (const t of data.tracked) {
                for (const v of t.versions) {
                    if (v.blobHash !== null)
                        appendFileSync(importJournal, `${JSON.stringify({ blob: v.blobHash })}\n`);
                }
            }
            return await this.runImport(scope, data, srcGetBlob, importJournal, identicalRepublish);
        }
        catch (err) {
            return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}` } };
        }
    }
    async runImport(scope, data, srcGetBlob, importJournal, identicalRepublish) {
        try {
            for (const t of data.tracked) {
                for (const v of t.versions) {
                    if (v.blobHash === null)
                        continue;
                    if (existsSync(this.blobPath(v.blobHash))) {
                        let local;
                        try {
                            local = readFileSync(this.blobPath(v.blobHash));
                        }
                        catch {
                            local = undefined;
                        }
                        if (local !== undefined && createHash("sha256").update(local).digest("hex") === v.blobHash)
                            continue;
                        this.disclose(this.blobPath(v.blobHash), "locally stored blob does not hash to its own name — the import re-fetched it from the source");
                        safeRm(this.blobPath(v.blobHash));
                    }
                    let bytes;
                    try {
                        bytes = await srcGetBlob(v.blobHash);
                    }
                    catch (err) {
                        return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}` } };
                    }
                    if (bytes === undefined)
                        return { ok: false, error: { code: "not_found", message: `source blob ${v.blobHash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
                    if (createHash("sha256").update(bytes).digest("hex") !== v.blobHash) {
                        return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fails content-address integrity — refused, nothing published` } };
                    }
                    this.putBlob(v.blobHash, bytes);
                }
            }
            if (identicalRepublish)
                return { ok: true };
            const now = await this.exportHistory(scope);
            if (now !== null) {
                if (fileHistoryExportDigest(now) === fileHistoryExportDigest(data))
                    return { ok: true };
                return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained a DIFFERENT history while this import was fetching blobs — nothing was published` } };
            }
            const staging = `${this.scopeDir(scope)}.importing-${process.pid}-${randomBytes(6).toString("hex")}`;
            safeRm(staging);
            try {
                ensureDir(join(staging, "boundaries"));
                const tracked = data.tracked.map((t) => [
                    t.relOrAbsPath,
                    {
                        baseAvailable: t.baseAvailable,
                        keyKind: t.keyKind,
                        keyFamily: t.keyFamily,
                        versions: t.versions.map((v) => ({ version: v.version, blobHash: v.blobHash, ...(v.mode !== undefined ? { mode: v.mode } : {}), ...(v.parentIdent !== undefined ? { parentIdent: v.parentIdent } : {}), backupTime: v.backupTime })),
                    },
                ]);
                const doc = { formatVersion: "file-history/1", scope, tracked, digest: trackedDigest(tracked) };
                atomicWriteFile(join(this.base, "tmp"), join(staging, "tracked.json"), JSON.stringify(doc));
                for (const b of data.boundaries) {
                    const bfiles = b.files.map((f) => [f.relOrAbsPath, f.version]);
                    const bdoc = { formatVersion: "file-history/1", files: bfiles, digest: boundaryDigest(bfiles) };
                    atomicWriteFile(join(this.base, "tmp"), join(staging, "boundaries", `${historyPathComponent(b.entryId)}.json`), JSON.stringify(bdoc));
                }
                let published;
                try {
                    published = this.publishStagedScopeSync(scope, staging);
                }
                catch (err) {
                    safeRm(staging);
                    return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${thrownText(err)} — nothing was published (a local storage fault, not a malformed envelope)` } };
                }
                if (published !== "published") {
                    safeRm(staging);
                    return published === "not_empty"
                        ? { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained history of its own while this import was being staged — nothing was published` } }
                        : { ok: false, error: { code: "adopt_failed", message: `importHistory could not record the consumed-entry tombstones for target "${scope}"'s unreadable boundary residue — nothing was published, because replacing records nothing remembers would hand their already-consumed entry ids back to this scope` } };
                }
            }
            catch (err) {
                safeRm(staging);
                throw err;
            }
            return { ok: true };
        }
        catch (err) {
            return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}` } };
        }
        finally {
            safeRm(importJournal);
            this.activeImports.get(scope)?.delete(importJournal);
        }
    }
}