"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RepositoryWriteLockOwnershipError = exports.RepositoryWriteLockConflictError = exports.DEFAULT_REPOSITORY_WRITE_LOCK_LEASE_MS = void 0;
exports.repositoryWriteLockPath = repositoryWriteLockPath;
exports.repositoryTransactionDirectory = repositoryTransactionDirectory;
exports.acquireRepositoryWriteLockSync = acquireRepositoryWriteLockSync;
exports.renewRepositoryWriteLockSync = renewRepositoryWriteLockSync;
exports.releaseRepositoryWriteLockSync = releaseRepositoryWriteLockSync;
exports.executeRepositoryTransactionSync = executeRepositoryTransactionSync;
exports.publishRepositoryDirectoryAtomicallySync = publishRepositoryDirectoryAtomicallySync;
exports.projectRepositoryTransactionExpectedPostconditions = projectRepositoryTransactionExpectedPostconditions;
const node_fs_1 = __importDefault(require("node:fs"));
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = __importDefault(require("node:path"));
const node_crypto_1 = require("node:crypto");
const atomic_write_js_1 = require("./atomic-write.js");
const mutation_preconditions_js_1 = require("./mutation-preconditions.js");
const canonical_write_target_js_1 = require("../paths/canonical-write-target.js");
exports.DEFAULT_REPOSITORY_WRITE_LOCK_LEASE_MS = 30_000;
class RepositoryWriteLockConflictError extends Error {
    reason;
    constructor(reason, message) {
        super(message);
        this.name = "RepositoryWriteLockConflictError";
        this.reason = reason;
    }
}
exports.RepositoryWriteLockConflictError = RepositoryWriteLockConflictError;
class RepositoryWriteLockOwnershipError extends Error {
    constructor(message) {
        super(message);
        this.name = "RepositoryWriteLockOwnershipError";
    }
}
exports.RepositoryWriteLockOwnershipError = RepositoryWriteLockOwnershipError;
class PreconditionConflictSignal extends Error {
}
function repositoryWriteLockPath(repositoryRoot) {
    return node_path_1.default.join(resolveRepositoryRoot(repositoryRoot), ".day-shift", "state", "write.lock");
}
function repositoryTransactionDirectory(repositoryRoot, transactionId) {
    validateTransactionId(transactionId);
    return node_path_1.default.join(resolveRepositoryRoot(repositoryRoot), ".day-shift", "state", "transactions", transactionId);
}
function acquireRepositoryWriteLockSync(repositoryRoot, transactionId, options = {}) {
    const resolvedRoot = resolveRepositoryRoot(repositoryRoot);
    validateTransactionId(transactionId);
    const lockPath = repositoryWriteLockPath(resolvedRoot);
    const now = options.now ?? Date.now;
    const leaseMs = positiveDuration(options.leaseMs, exports.DEFAULT_REPOSITORY_WRITE_LOCK_LEASE_MS, "repository write-lock lease");
    const ownerToken = options.ownerToken ?? (0, node_crypto_1.randomBytes)(18).toString("hex");
    if (ownerToken === "") {
        throw new TypeError("Repository write-lock owner token must be non-empty.");
    }
    const hostname = options.hostname ?? node_os_1.default.hostname();
    const pid = options.pid ?? process.pid;
    if (!Number.isInteger(pid) || pid <= 0) {
        throw new TypeError("Repository write-lock PID must be a positive integer.");
    }
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(lockPath), { recursive: true, mode: 0o700 });
    for (let attempt = 0; attempt < 2; attempt += 1) {
        const timestamp = now();
        const record = {
            schemaVersion: 1,
            transactionId,
            ownerToken,
            hostname,
            pid,
            acquiredAtMs: timestamp,
            heartbeatAtMs: timestamp,
            expiresAtMs: timestamp + leaseMs
        };
        try {
            writeExclusiveJson(lockPath, record);
            return { lockPath, record };
        }
        catch (error) {
            if (!isNodeError(error) || error.code !== "EEXIST") {
                throw error;
            }
        }
        const current = readLockRecord(lockPath);
        if (current === null) {
            throw new RepositoryWriteLockConflictError("invalid-lock-record", "The repository write lock exists but its owner record is invalid; no write was attempted.");
        }
        if (current.expiresAtMs > timestamp) {
            throw new RepositoryWriteLockConflictError("active", `Repository transaction '${current.transactionId}' holds the active write lock.`);
        }
        const ownerState = (options.isProcessAlive ?? defaultProcessAlive)(current);
        if (ownerState !== false) {
            throw new RepositoryWriteLockConflictError(ownerState === true ? "active" : "unverifiable-owner", "The expired repository write lock cannot be cleared because owner liveness is not safely disproven.");
        }
        if (!hasMatchingTransactionEvidence(resolvedRoot, current.transactionId)) {
            throw new RepositoryWriteLockConflictError("ambiguous-expired-lock", "The expired repository write lock has no matching transaction evidence and cannot be cleared from age alone.");
        }
        removeOwnedLock(lockPath, current);
    }
    throw new RepositoryWriteLockConflictError("active", "Repository write-lock ownership changed during stale-lock takeover.");
}
function renewRepositoryWriteLockSync(lock, options = {}) {
    const current = requireOwnedLock(lock);
    const timestamp = (options.now ?? Date.now)();
    const renewed = {
        ...current,
        heartbeatAtMs: timestamp,
        expiresAtMs: timestamp + positiveDuration(options.leaseMs, exports.DEFAULT_REPOSITORY_WRITE_LOCK_LEASE_MS, "repository write-lock lease")
    };
    (0, atomic_write_js_1.atomicWriteFileSync)(lock.lockPath, serializeJson(renewed), { mode: 0o600 });
    const verified = requireOwnedLock({ lockPath: lock.lockPath, record: renewed });
    return { lockPath: lock.lockPath, record: verified };
}
function releaseRepositoryWriteLockSync(lock) {
    requireOwnedLock(lock);
    removeOwnedLock(lock.lockPath, lock.record);
}
function executeRepositoryTransactionSync(repositoryRoot, request, options = {}) {
    const prepared = prepareTransaction(repositoryRoot, request);
    const emptyResult = () => ({
        outcome: "no-write",
        transactionId: prepared.transactionId,
        lockPath: ".day-shift/state/write.lock",
        transactionPath: prepared.transactionRelativePath,
        conflicts: [],
        entries: [],
        canonicalWrites: 0,
        failure: null,
        cleanupErrors: []
    });
    let lock;
    try {
        lock = acquireRepositoryWriteLockSync(prepared.repositoryRoot, prepared.transactionId, options);
    }
    catch (error) {
        if (!(error instanceof RepositoryWriteLockConflictError)) {
            throw error;
        }
        return {
            ...emptyResult(),
            outcome: "conflict",
            failure: error.message
        };
    }
    let manifest = null;
    let status = null;
    let canonicalWrites = 0;
    let transactionCreated = false;
    let failure = null;
    let outcome = "no-write";
    let conflicts = [];
    const cleanupErrors = [];
    try {
        invokeHook(options, "after-lock", prepared.transactionId, null, null);
        const validation = (0, mutation_preconditions_js_1.revalidateMutationPreconditionsSync)(prepared.preconditions);
        if (!validation.ok) {
            conflicts = validation.conflicts;
            outcome = "conflict";
            throw new PreconditionConflictSignal();
        }
        invokeHook(options, "after-preconditions", prepared.transactionId, null, null);
        const transactionsDirectory = node_path_1.default.dirname(prepared.transactionDirectory);
        node_fs_1.default.mkdirSync(transactionsDirectory, { recursive: true, mode: 0o700 });
        node_fs_1.default.chmodSync(transactionsDirectory, 0o700);
        node_fs_1.default.mkdirSync(prepared.transactionDirectory, { mode: 0o700 });
        transactionCreated = true;
        const stagedDirectory = node_path_1.default.join(prepared.transactionDirectory, "staged");
        const retiredDirectory = node_path_1.default.join(prepared.transactionDirectory, "retired");
        node_fs_1.default.mkdirSync(stagedDirectory, { mode: 0o700 });
        node_fs_1.default.mkdirSync(retiredDirectory, { mode: 0o700 });
        node_fs_1.default.chmodSync(prepared.transactionDirectory, 0o700);
        node_fs_1.default.chmodSync(stagedDirectory, 0o700);
        node_fs_1.default.chmodSync(retiredDirectory, 0o700);
        const timestamp = (options.now ?? Date.now)();
        manifest = {
            schemaVersion: 1,
            transactionId: prepared.transactionId,
            transactionKind: prepared.transactionKind,
            durableRecordPath: prepared.durableRecordPath,
            createdAtMs: timestamp,
            entries: prepared.operations.map((operation, index) => {
                invokeHook(options, "before-staging-entry", prepared.transactionId, index, operation.target.repositoryRelativePath);
                const stagedName = index.toString().padStart(6, "0");
                const stagedRelativePath = operation.operation === "write"
                    ? `staged/${stagedName}`
                    : `retired/${stagedName}`;
                if (operation.operation === "write") {
                    const stagedPath = node_path_1.default.join(stagedDirectory, stagedName);
                    node_fs_1.default.writeFileSync(stagedPath, operation.contents, { mode: 0o600, flag: "wx" });
                    node_fs_1.default.chmodSync(stagedPath, 0o600);
                }
                else if (!operation.target.exists || !node_fs_1.default.statSync(operation.target.runtimePath).isFile()) {
                    throw new Error(`Repository transaction retirement source '${operation.target.repositoryRelativePath}' is not an existing file.`);
                }
                const entry = {
                    operation: operation.operation,
                    path: operation.target.repositoryRelativePath,
                    stagedPath: stagedRelativePath,
                    beforeRevision: observeRevision(operation.target.runtimePath),
                    intendedRevision: operation.intendedRevision,
                    observedAfterRevision: null,
                    mode: operation.mode
                };
                invokeHook(options, "after-staging-entry", prepared.transactionId, index, operation.target.repositoryRelativePath);
                return entry;
            })
        };
        writeTransactionManifest(prepared.transactionDirectory, manifest);
        status = createStatus(prepared.transactionId, "staged", timestamp, [], null);
        writeTransactionStatus(prepared.transactionDirectory, status);
        invokeHook(options, "after-staging", prepared.transactionId, null, null);
        for (let index = 0; index < prepared.operations.length; index += 1) {
            const operation = prepared.operations[index];
            const entry = manifest.entries[index];
            invokeHook(options, "before-replacement", prepared.transactionId, index, operation.target.repositoryRelativePath);
            if (operation.operation === "write") {
                node_fs_1.default.mkdirSync(node_path_1.default.dirname(operation.target.runtimePath), { recursive: true });
                node_fs_1.default.renameSync(node_path_1.default.join(prepared.transactionDirectory, entry.stagedPath), operation.target.runtimePath);
                node_fs_1.default.chmodSync(operation.target.runtimePath, operation.mode);
            }
            else {
                node_fs_1.default.renameSync(operation.target.runtimePath, node_path_1.default.join(prepared.transactionDirectory, entry.stagedPath));
                node_fs_1.default.chmodSync(node_path_1.default.join(prepared.transactionDirectory, entry.stagedPath), 0o600);
            }
            canonicalWrites += 1;
            status = createStatus(prepared.transactionId, "applying", (options.now ?? Date.now)(), manifest.entries.slice(0, canonicalWrites).map((item) => item.path), null);
            writeTransactionStatus(prepared.transactionDirectory, status);
            invokeHook(options, "after-replacement", prepared.transactionId, index, operation.target.repositoryRelativePath);
            invokeHook(options, "before-postcondition", prepared.transactionId, index, operation.target.repositoryRelativePath);
            const afterRevision = observeRevision(operation.target.runtimePath);
            entry.observedAfterRevision = afterRevision;
            writeTransactionManifest(prepared.transactionDirectory, manifest);
            if (afterRevision !== entry.intendedRevision) {
                throw new Error(`Repository transaction postcondition failed for '${operation.target.repositoryRelativePath}'.`);
            }
            lock = renewRepositoryWriteLockSync(lock, options);
        }
        status = createStatus(prepared.transactionId, "committed", (options.now ?? Date.now)(), manifest.entries.map((entry) => entry.path), null);
        writeTransactionStatus(prepared.transactionDirectory, status);
        outcome = "committed";
    }
    catch (error) {
        if (error instanceof PreconditionConflictSignal) {
            outcome = "conflict";
        }
        else {
            failure = error;
            outcome = canonicalWrites === 0 ? "no-write" : "incomplete";
        }
        if (transactionCreated && outcome !== "conflict") {
            status = createStatus(prepared.transactionId, outcome, (options.now ?? Date.now)(), manifest?.entries.slice(0, canonicalWrites).map((entry) => entry.path) ?? [], sanitizeFailure(error, prepared));
            try {
                writeTransactionStatus(prepared.transactionDirectory, status);
            }
            catch (statusError) {
                failure = new Error(`${sanitizeFailure(error, prepared)} Transaction status persistence also failed: ${sanitizeFailure(statusError, prepared)}`);
            }
        }
    }
    finally {
        if (outcome === "committed" || outcome === "no-write" || outcome === "conflict") {
            try {
                invokeHook(options, "before-cleanup", prepared.transactionId, null, null);
                if (transactionCreated) {
                    node_fs_1.default.rmSync(node_path_1.default.join(prepared.transactionDirectory, "staged"), {
                        recursive: true,
                        force: true
                    });
                    node_fs_1.default.rmSync(node_path_1.default.join(prepared.transactionDirectory, "retired"), {
                        recursive: true,
                        force: true
                    });
                }
            }
            catch (cleanupError) {
                cleanupErrors.push(sanitizeFailure(cleanupError, prepared));
            }
        }
        try {
            releaseRepositoryWriteLockSync(lock);
        }
        catch (cleanupError) {
            cleanupErrors.push(sanitizeFailure(cleanupError, prepared));
        }
        if (transactionCreated &&
            outcome !== "incomplete" &&
            cleanupErrors.length === 0) {
            try {
                node_fs_1.default.rmSync(prepared.transactionDirectory, { recursive: true, force: true });
            }
            catch (cleanupError) {
                cleanupErrors.push(sanitizeFailure(cleanupError, prepared));
            }
        }
    }
    return finishResult();
    function finishResult() {
        return {
            outcome,
            transactionId: prepared.transactionId,
            lockPath: ".day-shift/state/write.lock",
            transactionPath: prepared.transactionRelativePath,
            conflicts,
            entries: manifest?.entries.map((entry) => ({ ...entry })) ?? [],
            canonicalWrites,
            failure: failure === null ? null : sanitizeFailure(failure, prepared),
            cleanupErrors: [...cleanupErrors]
        };
    }
}
/**
 * Publishes one new repository-local directory with a single rename. The destination
 * is never replaced, and failed staging is removed without exposing a partial pack.
 */
function publishRepositoryDirectoryAtomicallySync(repositoryRoot, request, options = {}) {
    const resolvedRoot = resolveRepositoryRoot(repositoryRoot);
    const destination = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(resolvedRoot, request.destination);
    if (destination.exists) {
        throw new TypeError(`Atomic directory destination '${destination.repositoryRelativePath}' already exists.`);
    }
    const parent = node_path_1.default.dirname(destination.runtimePath);
    const parentStat = node_fs_1.default.lstatSync(parent, { throwIfNoEntry: false });
    if (parentStat === undefined || parentStat.isSymbolicLink() || !parentStat.isDirectory()) {
        throw new TypeError("Atomic directory destination parent must be an existing non-symbolic directory.");
    }
    if (!Array.isArray(request.files) || request.files.length === 0) {
        throw new TypeError("Atomic directory publication requires at least one file.");
    }
    if (!Array.isArray(request.preconditions)) {
        throw new TypeError("Atomic directory publication requires explicit mutation preconditions.");
    }
    const files = normalizeAtomicDirectoryFiles(request.files);
    const transactionId = request.transactionId
        ?? `directory-publish-${(0, node_crypto_1.createHash)("sha256")
            .update(destination.repositoryRelativePath)
            .update("\u0000")
            .update(files.map((file) => file.path).join("\u0000"))
            .digest("hex")
            .slice(0, 24)}`;
    validateTransactionId(transactionId);
    const empty = (outcome, failure, conflicts = []) => ({
        outcome,
        transactionId,
        destination: destination.repositoryRelativePath,
        changedPaths: [],
        conflicts,
        failure
    });
    let lock;
    try {
        lock = acquireRepositoryWriteLockSync(resolvedRoot, transactionId, options);
    }
    catch (error) {
        if (error instanceof RepositoryWriteLockConflictError)
            return empty("conflict", error.message);
        throw error;
    }
    const staging = node_path_1.default.join(parent, `.${node_path_1.default.basename(destination.runtimePath)}.tmp-${process.pid}-${(0, node_crypto_1.randomBytes)(8).toString("hex")}`);
    let published = false;
    try {
        invokeHook(options, "after-lock", transactionId, null, null);
        const validation = (0, mutation_preconditions_js_1.revalidateMutationPreconditionsSync)(request.preconditions);
        if (!validation.ok)
            return empty("conflict", null, validation.conflicts);
        request.revalidate?.();
        invokeHook(options, "after-preconditions", transactionId, null, null);
        if (node_fs_1.default.existsSync(destination.runtimePath)) {
            return empty("conflict", "Atomic directory destination appeared after precondition validation.");
        }
        node_fs_1.default.mkdirSync(staging, { mode: 0o700 });
        for (let index = 0; index < files.length; index += 1) {
            const file = files[index];
            invokeHook(options, "before-staging-entry", transactionId, index, file.path);
            const target = node_path_1.default.join(staging, ...file.path.split("/"));
            node_fs_1.default.mkdirSync(node_path_1.default.dirname(target), { recursive: true, mode: 0o700 });
            node_fs_1.default.writeFileSync(target, file.contents, { flag: "wx", mode: file.mode });
            invokeHook(options, "after-staging-entry", transactionId, index, file.path);
        }
        invokeHook(options, "after-staging", transactionId, null, null);
        const refreshed = (0, mutation_preconditions_js_1.revalidateMutationPreconditionsSync)(request.preconditions);
        if (!refreshed.ok || node_fs_1.default.existsSync(destination.runtimePath)) {
            return empty("conflict", refreshed.ok ? "Atomic directory destination appeared before publication." : null, refreshed.conflicts);
        }
        request.revalidate?.();
        invokeHook(options, "before-replacement", transactionId, 0, destination.repositoryRelativePath);
        node_fs_1.default.renameSync(staging, destination.runtimePath);
        published = true;
        invokeHook(options, "after-replacement", transactionId, 0, destination.repositoryRelativePath);
        return {
            outcome: "committed",
            transactionId,
            destination: destination.repositoryRelativePath,
            changedPaths: files.map((file) => node_path_1.default.posix.join(destination.repositoryRelativePath, file.path)),
            conflicts: [],
            failure: null
        };
    }
    catch (error) {
        if (published) {
            return {
                outcome: "committed",
                transactionId,
                destination: destination.repositoryRelativePath,
                changedPaths: files.map((file) => node_path_1.default.posix.join(destination.repositoryRelativePath, file.path)),
                conflicts: [],
                failure: error instanceof Error
                    ? `Directory committed; post-publication hook reported: ${error.message}`
                    : "Directory committed; a post-publication hook reported an unknown failure."
            };
        }
        return empty("no-write", error instanceof Error ? error.message : "Atomic directory publication failed.");
    }
    finally {
        if (!published)
            node_fs_1.default.rmSync(staging, { recursive: true, force: true });
        try {
            releaseRepositoryWriteLockSync(lock);
        }
        catch {
            // The publication outcome remains authoritative; the normal lock recovery path
            // retains ownership evidence for an independently inspectable cleanup.
        }
    }
}
function normalizeAtomicDirectoryFiles(files) {
    const seen = new Set();
    return files.map((file) => {
        if (typeof file.path !== "string"
            || file.path === ""
            || file.path.startsWith("/")
            || file.path.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
            throw new TypeError(`Atomic directory file '${String(file.path)}' must be a canonical relative path.`);
        }
        if (seen.has(file.path)) {
            throw new TypeError(`Atomic directory publication duplicates file '${file.path}'.`);
        }
        seen.add(file.path);
        const contents = toWriteBuffer(file.contents, undefined);
        if (contents.byteLength === 0) {
            throw new TypeError(`Atomic directory file '${file.path}' must not be empty.`);
        }
        return { path: file.path, contents, mode: file.mode ?? 0o600 };
    }).sort((left, right) => left.path.localeCompare(right.path));
}
function projectRepositoryTransactionExpectedPostconditions(request) {
    if (!Array.isArray(request.writes)) {
        throw new TypeError("Repository transaction writes must be an array.");
    }
    if (request.retirements !== undefined && !Array.isArray(request.retirements)) {
        throw new TypeError("Repository transaction retirements must be an array.");
    }
    return [
        ...request.writes.map((write) => ({
            path: write.path,
            presence: "present",
            contentRevision: (0, node_crypto_1.createHash)("sha256")
                .update(toWriteBuffer(write.contents, write.encoding))
                .digest("hex")
        })),
        ...(request.retirements ?? []).map((retirement) => ({
            path: retirement.path,
            presence: "absent",
            contentRevision: null
        }))
    ];
}
function prepareTransaction(repositoryRoot, request) {
    const resolvedRoot = resolveRepositoryRoot(repositoryRoot);
    if (request === null || typeof request !== "object") {
        throw new TypeError("A repository transaction request is required.");
    }
    if (!Array.isArray(request.writes)) {
        throw new TypeError("Repository transaction writes must be an array.");
    }
    if (request.retirements !== undefined && !Array.isArray(request.retirements)) {
        throw new TypeError("Repository transaction retirements must be an array.");
    }
    if (request.writes.length === 0 && (request.retirements?.length ?? 0) === 0) {
        throw new TypeError("A repository transaction requires at least one write or retirement.");
    }
    if (!Array.isArray(request.preconditions)) {
        throw new TypeError("Repository transaction preconditions must be an array.");
    }
    const transactionId = request.transactionId ?? (0, node_crypto_1.randomBytes)(18).toString("hex");
    validateTransactionId(transactionId);
    const transactionKind = request.transactionKind ?? "generic";
    if (transactionKind !== "generic"
        && transactionKind !== "promotion"
        && transactionKind !== "migration") {
        throw new TypeError("Repository transaction kind must be generic, promotion, or migration.");
    }
    const collisionKeys = new Set();
    const writes = request.writes.map((write, index) => {
        if (write === null || typeof write !== "object") {
            throw new TypeError(`Repository transaction write ${index} must be an object.`);
        }
        const target = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(resolvedRoot, write.path);
        if (collisionKeys.has(target.collisionKey)) {
            throw new TypeError(`Repository transaction contains duplicate destination '${target.repositoryRelativePath}'.`);
        }
        collisionKeys.add(target.collisionKey);
        const contents = toBuffer(write.contents, write.encoding);
        const existingMode = target.exists
            ? node_fs_1.default.statSync(target.runtimePath).mode & 0o777
            : 0o600;
        return {
            operation: "write",
            target,
            contents,
            intendedRevision: sha256(contents),
            mode: normalizeMode(write.mode ?? existingMode)
        };
    }).sort((left, right) => comparePosixPaths(left.target.repositoryRelativePath, right.target.repositoryRelativePath));
    const retirements = (request.retirements ?? []).map((retirement, index) => {
        if (retirement === null || typeof retirement !== "object") {
            throw new TypeError(`Repository transaction retirement ${index} must be an object.`);
        }
        const target = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(resolvedRoot, retirement.path);
        if (collisionKeys.has(target.collisionKey)) {
            throw new TypeError(`Repository transaction contains duplicate target '${target.repositoryRelativePath}'.`);
        }
        collisionKeys.add(target.collisionKey);
        if (!target.exists || !node_fs_1.default.statSync(target.runtimePath).isFile()) {
            throw new TypeError(`Repository transaction retirement '${target.repositoryRelativePath}' must select an existing file.`);
        }
        return {
            operation: "retire",
            target,
            intendedRevision: null,
            mode: node_fs_1.default.statSync(target.runtimePath).mode & 0o777
        };
    }).sort((left, right) => comparePosixPaths(left.target.repositoryRelativePath, right.target.repositoryRelativePath));
    const operations = [...writes, ...retirements];
    const durableRecordPath = request.durableRecordPath === undefined
        ? null
        : (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(resolvedRoot, request.durableRecordPath)
            .repositoryRelativePath;
    if (transactionKind !== "generic" &&
        (durableRecordPath === null ||
            !writes.some((write) => write.target.repositoryRelativePath === durableRecordPath))) {
        throw new TypeError(`${transactionKind === "promotion" ? "Promotion" : "Migration"} transactions require a durable record path included in their canonical writes.`);
    }
    if (transactionKind === "generic" && durableRecordPath !== null) {
        throw new TypeError("Generic repository transactions cannot claim a durable record.");
    }
    validatePreconditionCoverage(resolvedRoot, operations, request.preconditions);
    assertSameFilesystem(resolvedRoot, operations);
    const transactionRelativePath = `.day-shift/state/transactions/${transactionId}`;
    return {
        repositoryRoot: resolvedRoot,
        transactionId,
        transactionDirectory: repositoryTransactionDirectory(resolvedRoot, transactionId),
        transactionRelativePath,
        transactionKind,
        durableRecordPath,
        operations,
        preconditions: request.preconditions
    };
}
function toWriteBuffer(contents, encoding) {
    return typeof contents === "string"
        ? Buffer.from(contents, encoding ?? "utf8")
        : Buffer.from(contents);
}
function validatePreconditionCoverage(repositoryRoot, operations, preconditions) {
    const byPath = new Map();
    for (const precondition of preconditions) {
        const target = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, precondition.repositoryRelativePath);
        if (node_path_1.default.resolve(precondition.runtimePath) !== node_path_1.default.resolve(target.runtimePath)) {
            throw new TypeError(`Mutation precondition runtime identity does not match '${target.repositoryRelativePath}'.`);
        }
        const existing = byPath.get(target.repositoryRelativePath) ?? [];
        existing.push(precondition);
        byPath.set(target.repositoryRelativePath, existing);
    }
    for (const operation of operations) {
        const expectations = byPath.get(operation.target.repositoryRelativePath) ?? [];
        const presence = expectations.find((item) => item.kind === "destination-presence");
        if (presence === undefined) {
            throw new TypeError(`Repository transaction target '${operation.target.repositoryRelativePath}' requires a presence precondition.`);
        }
        if (presence.kind === "destination-presence" &&
            (presence.expectedPresence === "present" || operation.operation === "retire") &&
            !expectations.some((item) => item.kind === "content-revision")) {
            throw new TypeError(`Existing repository transaction target '${operation.target.repositoryRelativePath}' requires a content-revision precondition.`);
        }
        if (operation.operation === "retire" &&
            (presence.kind !== "destination-presence" || presence.expectedPresence !== "present")) {
            throw new TypeError(`Repository transaction retirement '${operation.target.repositoryRelativePath}' requires an expected-present precondition.`);
        }
    }
}
function assertSameFilesystem(repositoryRoot, operations) {
    const stateDevice = node_fs_1.default.statSync(nearestExistingPath(node_path_1.default.join(repositoryRoot, ".day-shift", "state"))).dev;
    for (const operation of operations) {
        const targetDevice = node_fs_1.default.statSync(nearestExistingPath(node_path_1.default.dirname(operation.target.runtimePath))).dev;
        if (targetDevice !== stateDevice) {
            throw new Error(`Repository transaction target '${operation.target.repositoryRelativePath}' is not on the staging filesystem.`);
        }
    }
}
function resolveRepositoryRoot(repositoryRoot) {
    if (typeof repositoryRoot !== "string" || repositoryRoot.trim() === "") {
        throw new TypeError("A non-empty repository root is required.");
    }
    const resolved = node_fs_1.default.realpathSync(node_path_1.default.resolve(repositoryRoot));
    const marker = node_path_1.default.join(resolved, ".day-shift", "config.toml");
    if (!node_fs_1.default.statSync(marker, { throwIfNoEntry: false })?.isFile()) {
        throw new Error(`Repository root '${repositoryRoot}' has no .day-shift/config.toml marker.`);
    }
    return resolved;
}
function invokeHook(options, phase, transactionId, entryIndex, targetPath) {
    options.hook?.({ phase, transactionId, entryIndex, path: targetPath });
}
function createStatus(transactionId, state, updatedAtMs, replacedPaths, failure) {
    return {
        schemaVersion: 1,
        transactionId,
        state,
        updatedAtMs,
        replacedPaths: [...new Set(replacedPaths)].sort(comparePosixPaths),
        failure
    };
}
function writeTransactionManifest(transactionDirectory, manifest) {
    (0, atomic_write_js_1.atomicWriteFileSync)(node_path_1.default.join(transactionDirectory, "manifest.json"), serializeJson(manifest), {
        mode: 0o600
    });
}
function writeTransactionStatus(transactionDirectory, status) {
    (0, atomic_write_js_1.atomicWriteFileSync)(node_path_1.default.join(transactionDirectory, "status"), serializeJson(status), {
        mode: 0o600
    });
}
function hasMatchingTransactionEvidence(repositoryRoot, transactionId) {
    try {
        const transactionDirectory = repositoryTransactionDirectory(repositoryRoot, transactionId);
        const manifest = readJson(node_path_1.default.join(transactionDirectory, "manifest.json"));
        const status = readJson(node_path_1.default.join(transactionDirectory, "status"));
        return manifest?.transactionId === transactionId &&
            status?.transactionId === transactionId &&
            typeof status.state === "string" &&
            ["staged", "applying", "committed", "restored", "no-write", "incomplete"].includes(status.state);
    }
    catch {
        return false;
    }
}
function requireOwnedLock(lock) {
    const current = readLockRecord(lock.lockPath);
    if (current === null ||
        current.ownerToken !== lock.record.ownerToken ||
        current.transactionId !== lock.record.transactionId) {
        throw new RepositoryWriteLockOwnershipError("Repository write-lock ownership changed before the requested owner operation.");
    }
    return current;
}
function removeOwnedLock(lockPath, expected) {
    const current = readLockRecord(lockPath);
    if (current === null ||
        current.ownerToken !== expected.ownerToken ||
        current.transactionId !== expected.transactionId) {
        throw new RepositoryWriteLockOwnershipError("Repository write lock can be removed only by its matching owner and transaction.");
    }
    node_fs_1.default.rmSync(lockPath);
}
function readLockRecord(lockPath) {
    const value = readJson(lockPath);
    if (value === null ||
        value.schemaVersion !== 1 ||
        typeof value.transactionId !== "string" ||
        typeof value.ownerToken !== "string" ||
        typeof value.hostname !== "string" ||
        !Number.isInteger(value.pid) ||
        typeof value.acquiredAtMs !== "number" ||
        typeof value.heartbeatAtMs !== "number" ||
        typeof value.expiresAtMs !== "number") {
        return null;
    }
    return value;
}
function readJson(filePath) {
    try {
        const parsed = JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
        return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
            ? parsed
            : null;
    }
    catch {
        return null;
    }
}
function defaultProcessAlive(record) {
    if (record.hostname !== node_os_1.default.hostname()) {
        return null;
    }
    try {
        process.kill(record.pid, 0);
        return true;
    }
    catch (error) {
        return isNodeError(error) && error.code === "ESRCH" ? false : null;
    }
}
function writeExclusiveJson(filePath, value) {
    const descriptor = node_fs_1.default.openSync(filePath, "wx", 0o600);
    try {
        node_fs_1.default.writeFileSync(descriptor, serializeJson(value), { encoding: "utf8" });
        node_fs_1.default.fsyncSync(descriptor);
    }
    finally {
        node_fs_1.default.closeSync(descriptor);
    }
}
function observeRevision(runtimePath) {
    const stats = node_fs_1.default.statSync(runtimePath, { throwIfNoEntry: false });
    return stats?.isFile() ? sha256(node_fs_1.default.readFileSync(runtimePath)) : null;
}
function nearestExistingPath(candidatePath) {
    let candidate = candidatePath;
    for (;;) {
        if (node_fs_1.default.statSync(candidate, { throwIfNoEntry: false }) !== undefined) {
            return candidate;
        }
        const parent = node_path_1.default.dirname(candidate);
        if (parent === candidate) {
            return candidate;
        }
        candidate = parent;
    }
}
function validateTransactionId(transactionId) {
    if (typeof transactionId !== "string" ||
        !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(transactionId)) {
        throw new TypeError("Transaction identity must be a safe, non-empty repository-local name.");
    }
}
function positiveDuration(value, fallback, label) {
    const duration = value ?? fallback;
    if (!Number.isFinite(duration) || duration <= 0) {
        throw new TypeError(`${label} must be a positive finite number.`);
    }
    return duration;
}
function normalizeMode(mode) {
    if (typeof mode !== "number" || !Number.isInteger(mode) || mode < 0 || mode > 0o777) {
        throw new TypeError("Repository transaction file mode must be an integer from 0 through 0777.");
    }
    return mode;
}
function toBuffer(contents, encoding = "utf8") {
    if (typeof contents === "string") {
        return Uint8Array.from(Buffer.from(contents, encoding));
    }
    if (contents instanceof Uint8Array) {
        return Uint8Array.from(contents);
    }
    throw new TypeError("Repository transaction contents must be a string or Uint8Array.");
}
function sha256(contents) {
    return (0, node_crypto_1.createHash)("sha256").update(contents).digest("hex");
}
function serializeJson(value) {
    return `${JSON.stringify(value, null, 2)}\n`;
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function sanitizeFailure(error, prepared) {
    return errorMessage(error)
        .split(prepared.transactionDirectory).join(prepared.transactionRelativePath)
        .split(prepared.repositoryRoot).join(".");
}
function comparePosixPaths(left, right) {
    return left < right ? -1 : left > right ? 1 : 0;
}
function isNodeError(error) {
    return error instanceof Error && "code" in error;
}
