"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MutationPlanReceiptError = exports.MUTATION_PLAN_RECEIPT_MAX_TTL_MS = exports.MUTATION_PLAN_RECEIPT_MAX_BYTES = exports.MUTATION_PLAN_RECEIPT_SCHEMA_VERSION = void 0;
exports.createMutationPlanReceiptSync = createMutationPlanReceiptSync;
exports.readMutationPlanReceiptSync = readMutationPlanReceiptSync;
exports.applyMutationPlanReceiptSync = applyMutationPlanReceiptSync;
exports.computeMutationPlanReceiptDigest = computeMutationPlanReceiptDigest;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const mutation_preconditions_js_1 = require("../filesystem/mutation-preconditions.js");
const repository_transaction_js_1 = require("../filesystem/repository-transaction.js");
const canonical_write_target_js_1 = require("../paths/canonical-write-target.js");
exports.MUTATION_PLAN_RECEIPT_SCHEMA_VERSION = 1;
exports.MUTATION_PLAN_RECEIPT_MAX_BYTES = 512 * 1024;
exports.MUTATION_PLAN_RECEIPT_MAX_TTL_MS = 24 * 60 * 60 * 1000;
class MutationPlanReceiptError extends Error {
    code;
    details;
    conflicts;
    constructor(code, message, options = {}) {
        super(message);
        this.name = 'MutationPlanReceiptError';
        this.code = code;
        this.details = [...(options.details ?? [])];
        this.conflicts = [...(options.conflicts ?? [])];
    }
}
exports.MutationPlanReceiptError = MutationPlanReceiptError;
function createMutationPlanReceiptSync(repositoryRoot, options) {
    validateCanonicalReceiptStoragePath(options.receiptPath);
    validateCommandIdentity(options.commandIdentity);
    validateTimestampWindow(options.createdAt, options.expiresAt);
    validateNormalizedInputs(options.normalizedInputs);
    validateReplayPolicy(options.replayPolicy ?? 'single-use');
    const receiptTarget = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, options.receiptPath);
    if (receiptTarget.exists) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_REPLAYED', `Mutation plan receipt '${receiptTarget.repositoryRelativePath}' already exists.`);
    }
    const writes = options.writes.map((write) => ({
        path: (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, write.path).repositoryRelativePath,
        contents: toBuffer(write.contents, write.encoding),
        mode: normalizeMode(write.mode),
    }));
    const retirements = (options.retirements ?? []).map((retirement) => ({
        path: (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, retirement.path).repositoryRelativePath,
    }));
    const operationPaths = [...writes.map((write) => write.path), ...retirements.map((item) => item.path)];
    const intendedPaths = options.intendedPaths.map((value) => (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, value).repositoryRelativePath);
    assertExactPathSequence(intendedPaths, operationPaths);
    assertPreconditionCoverage(options.preconditions, intendedPaths);
    const transactionId = options.transactionId ?? (0, node_crypto_1.randomBytes)(18).toString('hex');
    const receiptWithoutDigest = {
        schemaVersion: exports.MUTATION_PLAN_RECEIPT_SCHEMA_VERSION,
        receiptId: `mutation-plan-receipt-${(0, node_crypto_1.randomBytes)(12).toString('hex')}`,
        planId: options.planId ?? `mutation-plan-${(0, node_crypto_1.randomBytes)(12).toString('hex')}`,
        commandIdentity: options.commandIdentity,
        executor: 'repository-transaction-v1',
        authority: 'none-explicit-apply-required',
        normalizedInputs: cloneJsonRecord(options.normalizedInputs),
        intendedPaths,
        preconditions: options.preconditions.map(mutation_preconditions_js_1.serializeMutationPrecondition),
        expectedPostconditions: (0, repository_transaction_js_1.projectRepositoryTransactionExpectedPostconditions)({
            writes: writes.map((write) => ({
                path: write.path,
                contents: write.contents,
                mode: write.mode,
            })),
            retirements,
        }),
        transaction: {
            transactionId,
            transactionKind: options.transactionKind ?? 'generic',
            writes: writes.map((write) => ({
                path: write.path,
                contentsBase64: write.contents.toString('base64'),
                mode: write.mode,
            })),
            retirements,
        },
        replayPolicy: options.replayPolicy ?? 'single-use',
        createdAt: new Date(options.createdAt).toISOString(),
        expiresAt: new Date(options.expiresAt).toISOString(),
    };
    const receipt = {
        ...receiptWithoutDigest,
        digest: computeMutationPlanReceiptDigest(receiptWithoutDigest),
    };
    validateMutationPlanReceipt(receipt, repositoryRoot);
    const serialized = serializeReceipt(receipt);
    const result = (0, repository_transaction_js_1.executeRepositoryTransactionSync)(repositoryRoot, {
        transactionId: `receipt-${(0, node_crypto_1.randomBytes)(12).toString('hex')}`,
        writes: [{ path: receiptTarget.repositoryRelativePath, contents: serialized, mode: 0o600 }],
        preconditions: [
            {
                kind: 'destination-presence',
                repositoryRelativePath: receiptTarget.repositoryRelativePath,
                runtimePath: receiptTarget.runtimePath,
                expectedPresence: 'absent',
            },
        ],
    });
    if (result.outcome !== 'committed') {
        throw new MutationPlanReceiptError(result.outcome === 'conflict' ? 'MUTATION_PLAN_RECEIPT_REPLAYED' : 'MUTATION_PLAN_RECEIPT_APPLY_FAILED', 'Mutation plan receipt could not be persisted.', { details: result.failure === null ? [] : [result.failure], conflicts: result.conflicts });
    }
    return {
        receiptPath: receiptTarget.repositoryRelativePath,
        receipt,
        planIdentity: planIdentity(receipt),
        expectedPostconditions: receipt.expectedPostconditions,
        writeOutcome: 'committed',
        transactionOutcome: result.outcome,
        recovery: 'Inspect the receipt, then invoke `day-shift apply --receipt <path> --expect-command <identity>` explicitly before expiry.',
    };
}
function readMutationPlanReceiptSync(repositoryRoot, receiptPath) {
    validateReceiptStoragePath(receiptPath);
    const target = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, receiptPath);
    const stats = node_fs_1.default.lstatSync(target.runtimePath, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isFile() || stats.isSymbolicLink()) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', 'Mutation plan receipt must be an existing regular repository-local file.');
    }
    if (stats.size <= 0 || stats.size > exports.MUTATION_PLAN_RECEIPT_MAX_BYTES) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', `Mutation plan receipt must contain 1-${exports.MUTATION_PLAN_RECEIPT_MAX_BYTES} bytes.`);
    }
    let parsed;
    try {
        parsed = JSON.parse(node_fs_1.default.readFileSync(target.runtimePath, 'utf8'));
    }
    catch {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', 'Mutation plan receipt is not valid UTF-8 JSON.');
    }
    validateMutationPlanReceipt(parsed, repositoryRoot);
    const receipt = parsed;
    const { digest: _digest, ...withoutDigest } = receipt;
    if (computeMutationPlanReceiptDigest(withoutDigest) !== receipt.digest) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_ALTERED', 'Mutation plan receipt digest does not match its canonical plan content.');
    }
    return receipt;
}
function applyMutationPlanReceiptSync(repositoryRoot, options) {
    validateCommandIdentity(options.expectedCommandIdentity);
    const receipt = readMutationPlanReceiptSync(repositoryRoot, options.receiptPath);
    if (receipt.commandIdentity !== options.expectedCommandIdentity) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_COMMAND_MISMATCH', `Receipt command '${receipt.commandIdentity}' does not match explicitly expected command '${options.expectedCommandIdentity}'.`);
    }
    const appliedAt = options.appliedAt === undefined ? new Date() : new Date(options.appliedAt);
    if (Number.isNaN(appliedAt.getTime())) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', 'Apply time must be a valid ISO-8601 timestamp.');
    }
    if (appliedAt.getTime() > new Date(receipt.expiresAt).getTime()) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_EXPIRED', 'Mutation plan receipt has expired; refresh the originating preview.');
    }
    const writes = receipt.transaction.writes.map((write) => ({
        path: write.path,
        contents: Buffer.from(write.contentsBase64, 'base64'),
        mode: write.mode,
    }));
    const operationPaths = [
        ...writes.map((write) => write.path),
        ...receipt.transaction.retirements.map((item) => item.path),
    ];
    assertExactPathSequence(receipt.intendedPaths, operationPaths);
    const expectedPostconditions = (0, repository_transaction_js_1.projectRepositoryTransactionExpectedPostconditions)({
        writes,
        retirements: receipt.transaction.retirements,
    });
    if (canonicalJson(expectedPostconditions) !== canonicalJson(receipt.expectedPostconditions)) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_SCOPE_EXPANSION', 'Receipt transaction content does not match its declared expected postconditions.');
    }
    const statePath = `.day-shift/state/applied-plan-receipts/${receipt.receiptId}.json`;
    const stateTarget = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, statePath);
    if (stateTarget.exists) {
        if (receipt.replayPolicy === 'idempotent' &&
            appliedStateMatches(stateTarget.runtimePath, receipt) &&
            postconditionsHold(repositoryRoot, receipt.expectedPostconditions)) {
            return applyResult(receipt, options.receiptPath, statePath, 'already-applied', 0);
        }
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_REPLAYED', 'Mutation plan receipt was already consumed and cannot be replayed.');
    }
    const preconditions = receipt.preconditions.map((precondition) => (0, mutation_preconditions_js_1.resolvePortableMutationPreconditionSync)(repositoryRoot, precondition));
    assertPreconditionCoverage(preconditions, receipt.intendedPaths);
    const receiptTarget = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, options.receiptPath);
    preconditions.push({
        kind: 'destination-presence',
        repositoryRelativePath: receiptTarget.repositoryRelativePath,
        runtimePath: receiptTarget.runtimePath,
        expectedPresence: 'present',
    }, {
        kind: 'content-revision',
        repositoryRelativePath: receiptTarget.repositoryRelativePath,
        runtimePath: receiptTarget.runtimePath,
        expectedRevision: (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(receiptTarget.runtimePath)).digest('hex'),
    });
    preconditions.push({
        kind: 'destination-presence',
        repositoryRelativePath: stateTarget.repositoryRelativePath,
        runtimePath: stateTarget.runtimePath,
        expectedPresence: 'absent',
    });
    const stateContents = `${JSON.stringify({
        schemaVersion: 1,
        receiptId: receipt.receiptId,
        planId: receipt.planId,
        digest: receipt.digest,
        commandIdentity: receipt.commandIdentity,
        appliedAt: appliedAt.toISOString(),
    }, null, 2)}\n`;
    const result = (0, repository_transaction_js_1.executeRepositoryTransactionSync)(repositoryRoot, {
        transactionId: receipt.transaction.transactionId,
        transactionKind: receipt.transaction.transactionKind,
        writes: [...writes, { path: statePath, contents: stateContents, mode: 0o600 }],
        retirements: receipt.transaction.retirements,
        preconditions,
    });
    if (result.outcome === 'conflict') {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_STALE', 'Mutation plan receipt preconditions are stale or conflict with current repository state.', { conflicts: result.conflicts });
    }
    if (result.outcome !== 'committed') {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_APPLY_FAILED', 'Mutation plan receipt transaction did not commit.', { details: result.failure === null ? [] : [result.failure], conflicts: result.conflicts });
    }
    return applyResult(receipt, options.receiptPath, statePath, 'committed', Math.max(0, result.canonicalWrites - 1));
}
function computeMutationPlanReceiptDigest(receipt) {
    return `sha256:${(0, node_crypto_1.createHash)('sha256').update(canonicalJson(receipt)).digest('hex')}`;
}
function validateMutationPlanReceipt(value, repositoryRoot) {
    if (!isRecord(value))
        invalid('Mutation plan receipt must be an object.');
    const receipt = value;
    if (receipt.schemaVersion !== exports.MUTATION_PLAN_RECEIPT_SCHEMA_VERSION) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_UNSUPPORTED', 'Mutation plan receipt schema version is unsupported.');
    }
    if (receipt.executor !== 'repository-transaction-v1') {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_UNSUPPORTED', 'Mutation plan receipt executor is unsupported.');
    }
    if (receipt.authority !== 'none-explicit-apply-required') {
        invalid('Mutation plan receipt authority boundary is invalid.');
    }
    validateIdentity(receipt.receiptId, 'receipt');
    validateIdentity(receipt.planId, 'plan');
    validateCommandIdentity(receipt.commandIdentity);
    validateTimestampWindow(receipt.createdAt, receipt.expiresAt);
    validateNormalizedInputs(receipt.normalizedInputs);
    validateReplayPolicy(receipt.replayPolicy);
    if (!/^sha256:[a-f0-9]{64}$/u.test(receipt.digest)) {
        invalid('Mutation plan receipt digest must be a lowercase SHA-256 identity.');
    }
    if (!isRecord(receipt.transaction) || receipt.transaction.transactionKind === undefined) {
        invalid('Mutation plan receipt transaction is invalid.');
    }
    validateIdentity(receipt.transaction.transactionId, 'transaction');
    if (!['generic', 'promotion', 'migration'].includes(receipt.transaction.transactionKind)) {
        invalid('Mutation plan receipt transaction kind is invalid.');
    }
    if (!Array.isArray(receipt.transaction.writes) || !Array.isArray(receipt.transaction.retirements)) {
        invalid('Mutation plan receipt operations must be arrays.');
    }
    if (receipt.transaction.writes.length + receipt.transaction.retirements.length === 0) {
        invalid('Mutation plan receipt requires at least one operation.');
    }
    for (const write of receipt.transaction.writes) {
        if (!isRecord(write) || typeof write.contentsBase64 !== 'string' || !isCanonicalBase64(write.contentsBase64)) {
            invalid('Mutation plan receipt write content must be canonical base64.');
        }
        (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, write.path);
        normalizeMode(write.mode);
    }
    for (const retirement of receipt.transaction.retirements) {
        if (!isRecord(retirement))
            invalid('Mutation plan receipt retirement is invalid.');
        (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, retirement.path);
    }
    if (!Array.isArray(receipt.intendedPaths) || !Array.isArray(receipt.preconditions)) {
        invalid('Mutation plan receipt intended paths and preconditions must be arrays.');
    }
    const operationPaths = [
        ...receipt.transaction.writes.map((write) => write.path),
        ...receipt.transaction.retirements.map((item) => item.path),
    ];
    assertExactPathSequence(receipt.intendedPaths, operationPaths);
    for (const precondition of receipt.preconditions) {
        (0, mutation_preconditions_js_1.resolvePortableMutationPreconditionSync)(repositoryRoot, precondition);
    }
    assertPreconditionCoverage(receipt.preconditions.map((precondition) => ({
        repositoryRelativePath: precondition.path,
    })), receipt.intendedPaths);
    if (!Array.isArray(receipt.expectedPostconditions)) {
        invalid('Mutation plan receipt expected postconditions must be an array.');
    }
}
function applyResult(receipt, receiptPath, statePath, outcome, canonicalWrites) {
    return {
        receiptPath,
        receiptId: receipt.receiptId,
        planId: receipt.planId,
        planIdentity: planIdentity(receipt),
        commandIdentity: receipt.commandIdentity,
        intendedPaths: [...receipt.intendedPaths],
        expectedPostconditions: receipt.expectedPostconditions.map((item) => ({ ...item })),
        transactionOutcome: outcome,
        transactionId: receipt.transaction.transactionId,
        canonicalWrites,
        writeOutcome: outcome === 'committed' ? 'committed' : 'no-writes',
        replayPolicy: receipt.replayPolicy,
        receiptStatePath: statePath,
        rollback: 'Revert the intended paths from version control; the receipt-consumption record remains audit evidence.',
        recovery: outcome === 'committed'
            ? 'Verify the emitted expected postconditions before continuing.'
            : 'The idempotent receipt already matches its expected postconditions; no write was performed.',
    };
}
function postconditionsHold(repositoryRoot, expected) {
    return expected.every((postcondition) => {
        const target = (0, canonical_write_target_js_1.resolveCanonicalWriteTargetSync)(repositoryRoot, postcondition.path);
        if (postcondition.presence === 'absent')
            return !target.exists;
        if (!target.exists || postcondition.contentRevision === null)
            return false;
        const stats = node_fs_1.default.statSync(target.runtimePath, { throwIfNoEntry: false });
        return (stats?.isFile() === true &&
            (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(target.runtimePath)).digest('hex') === postcondition.contentRevision);
    });
}
function appliedStateMatches(runtimePath, receipt) {
    const stats = node_fs_1.default.lstatSync(runtimePath, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isFile() || stats.isSymbolicLink() || stats.size > 16 * 1024) {
        return false;
    }
    try {
        const state = JSON.parse(node_fs_1.default.readFileSync(runtimePath, 'utf8'));
        return (isRecord(state) &&
            state.schemaVersion === 1 &&
            state.receiptId === receipt.receiptId &&
            state.planId === receipt.planId &&
            state.digest === receipt.digest &&
            state.commandIdentity === receipt.commandIdentity);
    }
    catch {
        return false;
    }
}
function assertExactPathSequence(intendedPaths, operationPaths) {
    const unique = new Set(operationPaths);
    if (unique.size !== operationPaths.length ||
        intendedPaths.length !== operationPaths.length ||
        intendedPaths.some((value, index) => value !== operationPaths[index])) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_SCOPE_EXPANSION', 'Mutation plan receipt operations must exactly match the ordered intended paths.');
    }
}
function assertPreconditionCoverage(preconditions, intendedPaths) {
    const paths = new Set(preconditions.map((item) => item.repositoryRelativePath ?? item.path));
    const missing = intendedPaths.filter((item) => !paths.has(item));
    const extra = [...paths].filter((item) => item !== undefined && !intendedPaths.includes(item));
    if (missing.length > 0 || extra.length > 0) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_SCOPE_EXPANSION', 'Mutation plan receipt preconditions must cover exactly the intended paths.', {
            details: [
                ...(missing.length === 0 ? [] : [`Missing preconditions: ${missing.join(', ')}`]),
                ...(extra.length === 0 ? [] : [`Unexpected preconditions: ${extra.join(', ')}`]),
            ],
        });
    }
}
function validateReceiptStoragePath(value) {
    if (typeof value !== 'string' ||
        !/^(?:\.day-shift\/runs|sandbox)\/run-[A-Za-z0-9][A-Za-z0-9._-]*\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\.json$/u.test(value)) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', "Mutation plan receipts must use a repository-local '.day-shift/runs/run-*/...json' path (legacy 'sandbox/run-*' receipts remain readable).");
    }
}
function validateCanonicalReceiptStoragePath(value) {
    if (typeof value !== 'string' ||
        !/^\.day-shift\/runs\/run-[A-Za-z0-9][A-Za-z0-9._-]*\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\.json$/u.test(value)) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', "New mutation plan receipts must use a repository-local '.day-shift/runs/run-*/...json' path.");
    }
}
function validateTimestampWindow(createdAt, expiresAt) {
    const created = new Date(createdAt);
    const expires = new Date(expiresAt);
    if (Number.isNaN(created.getTime()) ||
        Number.isNaN(expires.getTime()) ||
        expires.getTime() <= created.getTime() ||
        expires.getTime() - created.getTime() > exports.MUTATION_PLAN_RECEIPT_MAX_TTL_MS) {
        invalid('Mutation plan receipt timestamps require a positive window of at most 24 hours.');
    }
}
function validateNormalizedInputs(value) {
    if (!isRecord(value) || Array.isArray(value)) {
        invalid('Mutation plan receipt normalized inputs must be a JSON object.');
    }
    const serialized = JSON.stringify(value);
    if (serialized === undefined || Buffer.byteLength(serialized, 'utf8') > 64 * 1024) {
        invalid('Mutation plan receipt normalized inputs exceed the 64 KB bound.');
    }
}
function validateCommandIdentity(value) {
    if (typeof value !== 'string' || !/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,5}$/u.test(value)) {
        invalid('Mutation plan receipt command identity is invalid.');
    }
}
function validateIdentity(value, label) {
    if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{2,127}$/u.test(value)) {
        invalid(`Mutation plan receipt ${label} identity is invalid.`);
    }
}
function validateReplayPolicy(value) {
    if (value !== 'single-use' && value !== 'idempotent') {
        invalid('Mutation plan receipt replay policy is invalid.');
    }
}
function normalizeMode(mode) {
    const normalized = mode === undefined ? 0o644 : Number(mode);
    if (!Number.isInteger(normalized) || normalized < 0 || normalized > 0o777) {
        invalid('Mutation plan receipt write mode must be an integer between 000 and 777.');
    }
    return normalized;
}
function toBuffer(contents, encoding) {
    return typeof contents === 'string' ? Buffer.from(contents, encoding ?? 'utf8') : Buffer.from(contents);
}
function serializeReceipt(receipt) {
    const serialized = `${JSON.stringify(receipt, null, 2)}\n`;
    if (Buffer.byteLength(serialized, 'utf8') > exports.MUTATION_PLAN_RECEIPT_MAX_BYTES) {
        throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', `Mutation plan receipt exceeds the ${exports.MUTATION_PLAN_RECEIPT_MAX_BYTES}-byte bound.`);
    }
    return serialized;
}
function planIdentity(receipt) {
    return `${receipt.planId}:${receipt.digest}`;
}
function cloneJsonRecord(value) {
    return JSON.parse(JSON.stringify(value));
}
function canonicalJson(value) {
    if (Array.isArray(value)) {
        return `[${value.map(canonicalJson).join(',')}]`;
    }
    if (isRecord(value)) {
        return `{${Object.keys(value)
            .sort()
            .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
            .join(',')}}`;
    }
    return JSON.stringify(value);
}
function isCanonicalBase64(value) {
    try {
        return Buffer.from(value, 'base64').toString('base64') === value;
    }
    catch {
        return false;
    }
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function invalid(message) {
    throw new MutationPlanReceiptError('MUTATION_PLAN_RECEIPT_INVALID', message);
}
