"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskChronologyRecoveryError = void 0;
exports.previewTaskChronologyRecoverySync = previewTaskChronologyRecoverySync;
exports.applyTaskChronologyRecoverySync = applyTaskChronologyRecoverySync;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_util_1 = require("node:util");
const mutation_preconditions_1 = require("../filesystem/mutation-preconditions");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const lifecycle_run_context_1 = require("../lifecycle-run-context");
const paths_1 = require("../paths");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const task_chronology_recovery_evidence_1 = require("./task-chronology-recovery-evidence");
const task_attempts_1 = require("./task-attempts");
const task_cycle_runtime_attribution_1 = require("./task-cycle-runtime-attribution");
const task_lifecycle_1 = require("./task-lifecycle");
const task_lifecycle_timestamps_1 = require("./task-lifecycle-timestamps");
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
const SHA256_PATTERN = /^(?:sha256:)?[a-f0-9]{64}$/;
class TaskChronologyRecoveryError extends Error {
    code;
    constructor(code, message) {
        super(message);
        this.name = 'TaskChronologyRecoveryError';
        this.code = code;
    }
}
exports.TaskChronologyRecoveryError = TaskChronologyRecoveryError;
const plans = new WeakMap();
function previewTaskChronologyRecoverySync(request) {
    validateRequest(request);
    const pair = resolvePair(request.repositoryRoot, request.taskDefinitionPath);
    const taskText = readUtf8(pair.task.runtimePath, pair.task.repositoryRelativePath);
    const summaryText = readUtf8(pair.summary.runtimePath, pair.summary.repositoryRelativePath);
    const observedTaskRevision = sha256(taskText);
    const observedSummaryRevision = sha256(summaryText);
    const requestedTaskRevision = normalizeSha(request.taskRevision);
    const inspection = inspectRun(pair, request.runDirectory);
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: taskText,
        implementationSummary: summaryText,
    });
    const selected = projection.summary?.attempts.find((attempt) => attempt.attemptId === request.attemptId) ?? null;
    const baseline = (0, task_cycle_runtime_attribution_1.readAttemptRuntimeBaseline)(pair.summary.repositoryRelativePath, summaryText, request.attemptId);
    const chronology = selected?.openedAt === null || selected === null
        ? null
        : (0, task_chronology_recovery_evidence_1.inspectTaskChronologyEvidenceSync)({
            repositoryRoot: pair.repositoryRoot,
            runDirectory: inspection.manifest.runDirectory,
            entries: inspection.manifest.entries,
            artifactPath: pair.task.repositoryRelativePath,
            attemptId: request.attemptId,
            attemptOpenedAt: selected.openedAt,
            attemptClosedAt: selected.closedAt,
            baselineCapturedAt: baseline?.capturedAt ?? null,
        });
    if (selected?.chronologyRecoveryId !== null && selected?.chronologyRecoveryId !== undefined) {
        if (chronology === null) {
            throw new TaskChronologyRecoveryError('chronology-not-proven', 'Recovered chronology evidence is no longer inspectable.');
        }
        return replayResult({
            request,
            pair,
            taskText,
            summaryText,
            inspection,
            selected,
            baselineId: baseline?.baselineId ?? null,
            baselineCapturedAt: baseline?.capturedAt ?? null,
            chronology,
        });
    }
    if (observedTaskRevision !== requestedTaskRevision) {
        throw new TaskChronologyRecoveryError('stale-task-revision', `Recovery expected task revision '${requestedTaskRevision}', but current bytes are '${observedTaskRevision}'.`);
    }
    const structuralBlockers = projection.blockingFindings.filter((finding) => finding.code !== 'stale-readiness-authorization' &&
        finding.code !== 'stale-attempt-readiness-reference');
    if (structuralBlockers.length > 0) {
        throw new TaskChronologyRecoveryError('pair-invalid', `Task pair is not structurally recoverable: ${structuralBlockers.map((finding) => finding.message).join(' ')}`);
    }
    const current = projection.summary?.currentAttempt ?? null;
    const latest = projection.summary?.attempts.at(-1) ?? null;
    const futureTimestampEntries = chronology?.evidenceEntries.filter((entry) => entry.proofKind === 'future-lifecycle-timestamp') ?? [];
    const closedFutureTimestampCandidate = projection.task.status === 'draft' &&
        projection.summary?.currentAttemptId === null &&
        latest?.attemptId === request.attemptId &&
        selected?.attemptId === request.attemptId &&
        selected.openedAt !== null &&
        selected.closedAt !== null &&
        Date.parse(selected.openedAt) < Date.parse(selected.closedAt) &&
        selected.reviewDisposition === 'superseded' &&
        futureTimestampEntries.length === 1;
    const openInvalidChronologyCandidate = projection.task.status === 'draft' &&
        projection.task.readiness === 'ready_to_implement' &&
        projection.task.readinessAuthorization.complete &&
        projection.task.readinessAuthorization.valid &&
        current?.attemptId === request.attemptId &&
        current.closedAt === null &&
        current.reviewDisposition === 'pending' &&
        ['pending', 'in_progress', 'blocked'].includes(current.implementationStatus ?? '') &&
        current.openedAt !== null;
    if (!openInvalidChronologyCandidate && !closedFutureTimestampCandidate) {
        throw new TaskChronologyRecoveryError('attempt-mismatch', 'Chronology recovery requires either the exact open current attempt with invalid chronology or the latest closed superseded predecessor with hash-verified future-timestamp evidence and no successor.');
    }
    const materialEvidence = unique([
        ...(0, task_attempts_1.inspectTaskAttemptSupersessionEvidence)(summaryText, request.attemptId),
        ...(baseline === null ? [] : ['runtime attribution baseline is present']),
        ...(chronology?.unrecordableValidationIds.length === 0
            ? []
            : ['immutable unrecordable validation execution is present']),
    ]);
    if (materialEvidence.length === 0) {
        throw new TaskChronologyRecoveryError('evidence-required', 'The selected attempt has no material evidence; preserve the ordinary no-evidence task attempt-abort recovery.');
    }
    if (chronology === null ||
        !chronology.eligible ||
        chronology.observedAt === null ||
        chronology.blockers.length > 0) {
        throw new TaskChronologyRecoveryError('chronology-not-proven', chronology?.blockers.join(' ') || 'Immutable run evidence does not prove invalid attempt or baseline chronology.');
    }
    const latestEvidenceAt = chronology.evidenceEntries
        .map((entry) => entry.recordedAt)
        .sort((left, right) => Date.parse(right) - Date.parse(left))[0];
    if (latestEvidenceAt !== undefined && Date.parse(request.recordedAt) < Date.parse(latestEvidenceAt)) {
        throw new TaskChronologyRecoveryError('invalid-input', 'Chronology recovery cannot be recorded before the latest immutable source evidence entry.');
    }
    const auditEntries = uniqueChronologyEvidenceEntries(chronology.evidenceEntries);
    const evidenceEntryIds = auditEntries.map((entry) => entry.entryId);
    const evidencePaths = auditEntries.map((entry) => entry.evidencePath);
    const evidenceHashes = auditEntries.map((entry) => entry.evidenceHash);
    const recoveryId = chronologyRecoveryId({
        runId: inspection.manifest.runId,
        attemptId: request.attemptId,
        taskRevision: requestedTaskRevision,
        evidenceEntryIds,
    });
    const futureTimestampEntry = closedFutureTimestampCandidate ? futureTimestampEntries[0] : null;
    const originalClosedAt = closedFutureTimestampCandidate ? selected?.closedAt ?? null : null;
    const effectiveClosedAt = closedFutureTimestampCandidate && futureTimestampEntry !== null && selected?.openedAt !== null
        ? Date.parse(futureTimestampEntry.recordedAt) >= Date.parse(selected.openedAt)
            ? futureTimestampEntry.recordedAt
            : selected.openedAt
        : null;
    const nextTaskText = resetTaskReadinessAuthorization(taskText);
    const recoveryEvidence = {
        recoveryId,
        runId: inspection.manifest.runId,
        taskRevision: requestedTaskRevision,
        recordedBy: request.recordedBy.trim(),
        recordedAt: request.recordedAt,
        reason: request.reason.trim(),
        observedOpenedAt: selected?.openedAt,
        observedBaselineAt: baseline?.capturedAt ?? null,
        evidenceEntryIds,
        evidenceHashes,
        unrecordableValidationIds: chronology.unrecordableValidationIds,
    };
    const nextSummaryText = closedFutureTimestampCandidate && futureTimestampEntry !== null && originalClosedAt !== null && effectiveClosedAt !== null
        ? appendFutureTimestampRecovery(summaryText, request.attemptId, {
            ...recoveryEvidence,
            originalClosedAt,
            effectiveClosedAt,
            sourceEntryId: futureTimestampEntry.entryId,
            sourceEvidenceHash: futureTimestampEntry.evidenceHash,
        })
        : closeInvalidChronologyAttempt(summaryText, request.attemptId, recoveryEvidence);
    const proposed = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: nextTaskText,
        implementationSummary: nextSummaryText,
    });
    if (proposed.blockingFindings.length > 0) {
        throw new TaskChronologyRecoveryError('pair-invalid', `Chronology recovery would violate pair invariants: ${proposed.blockingFindings.map((finding) => finding.message).join(' ')}`);
    }
    const preconditions = [pair.task, pair.summary].flatMap((target) => [
        (0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(target),
        (0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target),
    ]);
    const writes = [
        { path: pair.task.repositoryRelativePath, contents: nextTaskText },
        { path: pair.summary.repositoryRelativePath, contents: nextSummaryText },
    ].sort((left, right) => left.path.localeCompare(right.path));
    const snapshot = {
        recoveryId,
        taskId: projection.task.taskId ?? request.attemptId,
        taskDefinitionPath: pair.task.repositoryRelativePath,
        implementationSummaryPath: pair.summary.repositoryRelativePath,
        runDirectory: inspection.manifest.runDirectory,
        runId: inspection.manifest.runId,
        priorAttemptId: request.attemptId,
        recoveryState: 'invalid_chronology',
        successorKind: 'chronology_repair',
        recordedBy: request.recordedBy.trim(),
        recordedAt: request.recordedAt,
        reason: request.reason.trim(),
        observedOpenedAt: selected?.openedAt,
        observedBaselineAt: baseline?.capturedAt ?? null,
        chronologyObservedAt: chronology.observedAt,
        originalClosedAt,
        effectiveClosedAt,
        sourceEntryId: futureTimestampEntry?.entryId ?? null,
        sourceEvidenceHash: futureTimestampEntry?.evidenceHash ?? null,
        taskRevision: observedTaskRevision,
        implementationSummaryRevision: observedSummaryRevision,
        nextTaskRevision: sha256(nextTaskText),
        nextImplementationSummaryRevision: sha256(nextSummaryText),
        baselineId: baseline?.baselineId ?? null,
        evidenceEntryIds,
        evidencePaths,
        evidenceHashes,
        unrecordableValidationIds: chronology.unrecordableValidationIds,
        evidenceKinds: unique([
            ...materialEvidence,
            ...chronology.evidenceEntries.map((entry) => entry.proofKind),
        ]),
        idempotent: false,
        intendedPaths: writes.map((write) => write.path),
        changedPaths: [],
        expected: preconditions.map(toExpectedCondition),
        writeOutcome: 'previewed',
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null,
    };
    plans.set(snapshot, {
        repositoryRoot: pair.repositoryRoot,
        writes,
        preconditions,
        snapshot: cloneResult(snapshot),
    });
    return snapshot;
}
function applyTaskChronologyRecoverySync(preview, options = {}) {
    if (preview.writeOutcome === 'no-write' && preview.idempotent)
        return cloneResult(preview);
    const plan = plans.get(preview);
    if (plan === undefined) {
        throw new TaskChronologyRecoveryError('stale-preview', 'Chronology recovery apply requires the original in-process preview. Refresh the preview.');
    }
    (0, task_lifecycle_timestamps_1.revalidateObservedLifecycleTimestamp)(plan.snapshot.recordedAt, 'recorded_at', 'Chronology recovery time');
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(plan.repositoryRoot, { writes: plan.writes, preconditions: plan.preconditions }, options.transaction);
    return {
        ...cloneResult(plan.snapshot),
        changedPaths: transaction.entries.slice(0, transaction.canonicalWrites).map((entry) => entry.path),
        writeOutcome: transaction.outcome,
        transactionId: transaction.transactionId,
        conflicts: transaction.conflicts,
        recoveryPath: transaction.outcome === 'incomplete' ? transaction.transactionPath : null,
        failure: transaction.failure ?? (transaction.cleanupErrors.length > 0 ? transaction.cleanupErrors.join(' ') : null),
    };
}
function replayResult(options) {
    const { request, pair, taskText, summaryText, inspection, selected, chronology } = options;
    const requestedTaskRevision = normalizeSha(request.taskRevision);
    const auditEntries = uniqueChronologyEvidenceEntries(chronology.evidenceEntries);
    const evidenceEntryIds = auditEntries.map((entry) => entry.entryId);
    const evidencePaths = auditEntries.map((entry) => entry.evidencePath);
    const evidenceHashes = auditEntries.map((entry) => entry.evidenceHash);
    const expectedRecoveryId = chronologyRecoveryId({
        runId: inspection.manifest.runId,
        attemptId: request.attemptId,
        taskRevision: requestedTaskRevision,
        evidenceEntryIds,
    });
    const futureTimestampEntries = chronology.evidenceEntries.filter((entry) => entry.proofKind === 'future-lifecycle-timestamp');
    const futureTimestampEntry = futureTimestampEntries.length === 1 ? futureTimestampEntries[0] : null;
    const futureTimestampRecovery = selected.chronologyOriginalClosedAt !== null;
    const expectedEffectiveClosedAt = futureTimestampRecovery && futureTimestampEntry !== null && selected.openedAt !== null
        ? Date.parse(futureTimestampEntry.recordedAt) >= Date.parse(selected.openedAt)
            ? futureTimestampEntry.recordedAt
            : selected.openedAt
        : null;
    const recoveryAuditMatches = futureTimestampRecovery
        ? futureTimestampEntry !== null &&
            selected.reviewDisposition === 'superseded' &&
            selected.chronologyOriginalClosedAt === selected.closedAt &&
            selected.chronologyEffectiveClosedAt === expectedEffectiveClosedAt &&
            selected.chronologySourceEntryId === futureTimestampEntry.entryId &&
            selected.chronologySourceEvidenceHash === futureTimestampEntry.evidenceHash &&
            selected.chronologyRecoveredBy === request.recordedBy.trim() &&
            selected.chronologyRecoveredAt === request.recordedAt &&
            selected.chronologyRecoveryReason === request.reason.trim()
        : selected.reviewDisposition === 'invalid_chronology' &&
            selected.closedAt === request.recordedAt &&
            selected.dispositionBy === request.recordedBy.trim() &&
            selected.dispositionAt === request.recordedAt &&
            selected.dispositionReason === request.reason.trim();
    if (selected.chronologyRecoveryId !== expectedRecoveryId ||
        !recoveryAuditMatches ||
        selected.chronologyRecoveryRunId !== inspection.manifest.runId ||
        selected.chronologyRecoveryTaskRevision !== requestedTaskRevision ||
        selected.chronologyObservedOpenedAt !== selected.openedAt ||
        selected.chronologyObservedBaselineAt !== options.baselineCapturedAt ||
        !sameJsonStringArray(selected.chronologyEvidenceEntryIds, evidenceEntryIds) ||
        !sameJsonStringArray(selected.chronologyEvidenceHashes, evidenceHashes) ||
        !sameJsonStringArray(selected.chronologyUnrecordableValidationIds, chronology.unrecordableValidationIds)) {
        throw new TaskChronologyRecoveryError('attempt-mismatch', `Attempt '${request.attemptId}' already contains conflicting chronology recovery evidence.`);
    }
    return {
        recoveryId: selected.chronologyRecoveryId,
        taskId: (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: taskText, implementationSummary: summaryText }).task.taskId ?? request.attemptId,
        taskDefinitionPath: pair.task.repositoryRelativePath,
        implementationSummaryPath: pair.summary.repositoryRelativePath,
        runDirectory: inspection.manifest.runDirectory,
        runId: inspection.manifest.runId,
        priorAttemptId: request.attemptId,
        recoveryState: 'invalid_chronology',
        successorKind: 'chronology_repair',
        recordedBy: request.recordedBy.trim(),
        recordedAt: request.recordedAt,
        reason: request.reason.trim(),
        observedOpenedAt: selected.openedAt,
        observedBaselineAt: selected.chronologyObservedBaselineAt,
        chronologyObservedAt: chronology.observedAt,
        originalClosedAt: selected.chronologyOriginalClosedAt,
        effectiveClosedAt: selected.chronologyEffectiveClosedAt,
        sourceEntryId: selected.chronologySourceEntryId,
        sourceEvidenceHash: selected.chronologySourceEvidenceHash,
        taskRevision: sha256(taskText),
        implementationSummaryRevision: sha256(summaryText),
        nextTaskRevision: sha256(taskText),
        nextImplementationSummaryRevision: sha256(summaryText),
        baselineId: options.baselineId,
        evidenceEntryIds,
        evidencePaths,
        evidenceHashes,
        unrecordableValidationIds: chronology.unrecordableValidationIds,
        evidenceKinds: chronology.evidenceEntries.map((entry) => entry.proofKind),
        idempotent: true,
        intendedPaths: [],
        changedPaths: [],
        expected: [],
        writeOutcome: 'no-write',
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null,
    };
}
function inspectRun(pair, runDirectory) {
    const inspection = (0, lifecycle_run_context_1.inspectLifecycleRunContext)({
        repositoryRoot: pair.repositoryRoot,
        runDirectory,
        selectedArtifactPath: pair.task.repositoryRelativePath,
    });
    if (!inspection.manifestComplete ||
        inspection.manifest.lifecycleProfile !== 'full-cycle-strict' ||
        inspection.manifest.activeArtifactPath !== pair.task.repositoryRelativePath) {
        throw new TaskChronologyRecoveryError('run-ineligible', 'Chronology recovery requires a complete full-cycle-strict source run bound to the selected task artifact.');
    }
    return inspection;
}
function closeInvalidChronologyAttempt(summaryText, attemptId, evidence) {
    const newline = summaryText.includes('\r\n') ? '\r\n' : '\n';
    const hadFinalNewline = summaryText.endsWith('\n');
    const lines = summaryText.split(/\r?\n/);
    let closing = lines.findIndex((line, index) => index > 0 && line === '---');
    if (lines[0] !== '---' || closing < 0) {
        throw new TaskChronologyRecoveryError('artifact-unreadable', 'Implementation summary has malformed YAML front matter.');
    }
    const starts = lines
        .map((line, index) => ({ line, index }))
        .filter(({ line, index }) => index > 0 && index < closing && /^\s{2}-\s+attempt_id:/.test(line));
    const start = starts.find(({ line }) => parseScalar(line.replace(/^\s{2}-\s+attempt_id:\s*/, '')) === attemptId)?.index;
    if (start === undefined) {
        throw new TaskChronologyRecoveryError('attempt-mismatch', `Attempt '${attemptId}' is not materialized.`);
    }
    let end = start + 1;
    while (end < closing && !/^\s{2}-\s+attempt_id:/.test(lines[end]) && (lines[end].trim() === '' || /^\s/.test(lines[end]))) {
        end += 1;
    }
    const updates = [
        ['closed_at', evidence.recordedAt, true],
        ['review_disposition', 'invalid_chronology', false],
        ['disposition_by', evidence.recordedBy, true],
        ['disposition_at', evidence.recordedAt, true],
        ['disposition_reason', evidence.reason, true],
        ['chronology_recovery_id', evidence.recoveryId, true],
        ['chronology_recovery_run_id', evidence.runId, true],
        ['chronology_recovery_task_revision', evidence.taskRevision, true],
        ['chronology_observed_opened_at', evidence.observedOpenedAt, true],
        ['chronology_observed_baseline_at', evidence.observedBaselineAt ?? 'null', evidence.observedBaselineAt !== null],
        ['chronology_evidence_entry_ids', JSON.stringify(evidence.evidenceEntryIds), true],
        ['chronology_evidence_hashes', JSON.stringify(evidence.evidenceHashes), true],
        ['chronology_unrecordable_validation_ids', JSON.stringify(evidence.unrecordableValidationIds), true],
    ];
    for (const [field, value, quoted] of updates) {
        const pattern = new RegExp(`^\\s{4}${field}:`);
        const index = lines.findIndex((line, lineIndex) => lineIndex > start && lineIndex < end && pattern.test(line));
        const rendered = `    ${field}: ${quoted ? JSON.stringify(value) : value}`;
        if (index >= 0)
            lines[index] = rendered;
        else {
            lines.splice(end, 0, rendered);
            end += 1;
            closing += 1;
        }
    }
    const currentIndex = lines.findIndex((line, index) => index > 0 && index < closing && /^current_attempt_id:/.test(line));
    if (currentIndex < 0) {
        throw new TaskChronologyRecoveryError('pair-invalid', 'Implementation summary current_attempt_id is missing.');
    }
    lines[currentIndex] = 'current_attempt_id: null';
    const rendered = lines.join(newline);
    return hadFinalNewline && !rendered.endsWith(newline) ? `${rendered}${newline}` : rendered;
}
function appendFutureTimestampRecovery(summaryText, attemptId, evidence) {
    const newline = summaryText.includes('\r\n') ? '\r\n' : '\n';
    const hadFinalNewline = summaryText.endsWith('\n');
    const lines = summaryText.split(/\r?\n/);
    let closing = lines.findIndex((line, index) => index > 0 && line === '---');
    if (lines[0] !== '---' || closing < 0) {
        throw new TaskChronologyRecoveryError('artifact-unreadable', 'Implementation summary has malformed YAML front matter.');
    }
    const starts = lines
        .map((line, index) => ({ line, index }))
        .filter(({ line, index }) => index > 0 && index < closing && /^\s{2}-\s+attempt_id:/.test(line));
    const start = starts.find(({ line }) => parseScalar(line.replace(/^\s{2}-\s+attempt_id:\s*/, '')) === attemptId)?.index;
    if (start === undefined) {
        throw new TaskChronologyRecoveryError('attempt-mismatch', `Attempt '${attemptId}' is not materialized.`);
    }
    let end = start + 1;
    while (end < closing && !/^\s{2}-\s+attempt_id:/.test(lines[end]) && (lines[end].trim() === '' || /^\s/.test(lines[end]))) {
        end += 1;
    }
    const updates = [
        ['chronology_recovery_id', evidence.recoveryId, true],
        ['chronology_recovery_run_id', evidence.runId, true],
        ['chronology_recovery_task_revision', evidence.taskRevision, true],
        ['chronology_observed_opened_at', evidence.observedOpenedAt, true],
        ['chronology_observed_baseline_at', evidence.observedBaselineAt ?? 'null', evidence.observedBaselineAt !== null],
        ['chronology_evidence_entry_ids', JSON.stringify(evidence.evidenceEntryIds), true],
        ['chronology_evidence_hashes', JSON.stringify(evidence.evidenceHashes), true],
        ['chronology_unrecordable_validation_ids', JSON.stringify(evidence.unrecordableValidationIds), true],
        ['chronology_original_closed_at', evidence.originalClosedAt, true],
        ['chronology_effective_closed_at', evidence.effectiveClosedAt, true],
        ['chronology_source_entry_id', evidence.sourceEntryId, true],
        ['chronology_source_evidence_hash', evidence.sourceEvidenceHash, true],
        ['chronology_recovery_reason', evidence.reason, true],
        ['chronology_recovered_by', evidence.recordedBy, true],
        ['chronology_recovered_at', evidence.recordedAt, true],
    ];
    for (const [field, value, quoted] of updates) {
        const pattern = new RegExp(`^\\s{4}${field}:`);
        if (lines.some((line, lineIndex) => lineIndex > start && lineIndex < end && pattern.test(line))) {
            throw new TaskChronologyRecoveryError('attempt-mismatch', `Attempt '${attemptId}' already contains chronology correction field '${field}'.`);
        }
        lines.splice(end, 0, `    ${field}: ${quoted ? JSON.stringify(value) : value}`);
        end += 1;
        closing += 1;
    }
    const rendered = lines.join(newline);
    return hadFinalNewline && !rendered.endsWith(newline) ? `${rendered}${newline}` : rendered;
}
function resetTaskReadinessAuthorization(taskText) {
    const newline = taskText.includes('\r\n') ? '\r\n' : '\n';
    const hadFinalNewline = taskText.endsWith('\n');
    const lines = taskText.split(/\r?\n/);
    let closing = lines.findIndex((line, index) => index > 0 && line === '---');
    if (lines[0] !== '---' || closing < 0) {
        throw new TaskChronologyRecoveryError('artifact-unreadable', 'Task definition has malformed YAML front matter.');
    }
    const removable = new Set([
        'readiness_review_id',
        'readiness_review_revision',
        'readiness_review_protocol_version',
        'readiness_reviewed_by',
        'readiness_reviewed_at',
        'readiness_waiver_id',
    ]);
    for (let index = closing - 1; index > 0; index -= 1) {
        const field = lines[index].match(/^([A-Za-z0-9_]+):/)?.[1];
        if (field !== undefined && removable.has(field))
            lines.splice(index, 1);
    }
    closing = lines.findIndex((line, index) => index > 0 && line === '---');
    const readinessIndex = lines.findIndex((line, index) => index > 0 && index < closing && /^readiness:/.test(line));
    if (readinessIndex < 0) {
        throw new TaskChronologyRecoveryError('pair-invalid', 'Task definition readiness field is missing.');
    }
    lines[readinessIndex] = 'readiness: needs_more_evidence';
    const rendered = lines.join(newline);
    return hadFinalNewline && !rendered.endsWith(newline) ? `${rendered}${newline}` : rendered;
}
function resolvePair(repositoryRootValue, taskDefinitionPath) {
    const repositoryRoot = node_fs_1.default.realpathSync(node_path_1.default.resolve(requireText(repositoryRootValue, 'Repository root')));
    let parsed;
    try {
        parsed = (0, paths_1.parseRepositoryRelativePath)(taskDefinitionPath);
    }
    catch (error) {
        throw new TaskChronologyRecoveryError('invalid-input', error instanceof Error ? error.message : 'Invalid task-definition path.');
    }
    if (parsed.segments.at(-1) !== 'task-definition.md') {
        throw new TaskChronologyRecoveryError('invalid-input', 'Recovery paths must end in task-definition.md.');
    }
    const task = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, parsed.normalizedPath);
    const summary = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, [...parsed.segments.slice(0, -1), 'implementation-summary.md'].join('/'));
    if (!task.exists || !summary.exists) {
        throw new TaskChronologyRecoveryError('artifact-unreadable', 'Both task-definition.md and implementation-summary.md must exist.');
    }
    return { repositoryRoot, task, summary };
}
function readUtf8(runtimePath, relativePath) {
    const stats = node_fs_1.default.statSync(runtimePath, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isFile()) {
        throw new TaskChronologyRecoveryError('artifact-unreadable', `Artifact '${relativePath}' must be a regular file.`);
    }
    try {
        return new node_util_1.TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(node_fs_1.default.readFileSync(runtimePath));
    }
    catch (error) {
        throw new TaskChronologyRecoveryError('artifact-unreadable', `Artifact '${relativePath}' must contain valid UTF-8: ${error instanceof Error ? error.message : String(error)}`);
    }
}
function validateRequest(request) {
    if (request === null || typeof request !== 'object') {
        throw new TaskChronologyRecoveryError('invalid-input', 'A chronology recovery request is required.');
    }
    requireId(request.attemptId, 'Attempt identity');
    normalizeSha(request.taskRevision);
    requireText(request.recordedBy, 'Recovery actor');
    requireText(request.reason, 'Recovery reason');
    if (!(0, task_lifecycle_1.isTaskLifecycleTimestamp)(request.recordedAt)) {
        throw new TaskChronologyRecoveryError('invalid-input', 'Recovery time must be an ISO-8601 timestamp with an explicit timezone.');
    }
    (0, task_lifecycle_timestamps_1.requireObservedLifecycleTimestamp)({
        value: request.recordedAt,
        field: 'recorded_at',
        label: 'Chronology recovery time',
    });
}
function requireId(value, label) {
    const identity = requireText(value, label);
    if (!ID_PATTERN.test(identity)) {
        throw new TaskChronologyRecoveryError('invalid-input', `${label} must use 3-128 machine-safe characters.`);
    }
    return identity;
}
function requireText(value, label) {
    if (typeof value !== 'string' || value.trim() === '') {
        throw new TaskChronologyRecoveryError('invalid-input', `${label} must be non-empty.`);
    }
    return value.trim();
}
function normalizeSha(value) {
    const revision = requireText(value, 'Task revision');
    if (!SHA256_PATTERN.test(revision)) {
        throw new TaskChronologyRecoveryError('invalid-input', 'Task revision must be an exact SHA-256 revision.');
    }
    return rawSha(revision);
}
function rawSha(value) {
    return value.startsWith('sha256:') ? value.slice('sha256:'.length) : value;
}
function sha256(contents) {
    return (0, node_crypto_1.createHash)('sha256').update(contents).digest('hex');
}
function chronologyRecoveryId(options) {
    return `task-chronology-recovery-${sha256(JSON.stringify(options)).slice(0, 24)}`;
}
function uniqueChronologyEvidenceEntries(entries) {
    return [...new Map(entries.map((entry) => [entry.entryId, entry])).values()];
}
function parseScalar(value) {
    const trimmed = value.trim();
    if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
        try {
            const parsed = JSON.parse(trimmed);
            return typeof parsed === 'string' ? parsed : trimmed;
        }
        catch {
            return trimmed.slice(1, -1);
        }
    }
    if (trimmed.startsWith("'") && trimmed.endsWith("'"))
        return trimmed.slice(1, -1).replace(/''/g, "'");
    return trimmed;
}
function sameJsonStringArray(value, expected) {
    if (value === null)
        return false;
    try {
        const parsed = JSON.parse(value);
        return Array.isArray(parsed) && JSON.stringify(parsed) === JSON.stringify(expected);
    }
    catch {
        return false;
    }
}
function unique(values) {
    return [...new Set(values)];
}
function toExpectedCondition(precondition) {
    return {
        path: precondition.repositoryRelativePath,
        kind: precondition.kind,
        expectedState: precondition.kind === 'content-revision'
            ? precondition.expectedRevision
            : precondition.expectedPresence,
    };
}
function cloneResult(result) {
    return {
        ...result,
        evidenceEntryIds: [...result.evidenceEntryIds],
        evidencePaths: [...result.evidencePaths],
        evidenceHashes: [...result.evidenceHashes],
        unrecordableValidationIds: [...result.unrecordableValidationIds],
        evidenceKinds: [...result.evidenceKinds],
        intendedPaths: [...result.intendedPaths],
        changedPaths: [...result.changedPaths],
        expected: result.expected.map((entry) => ({ ...entry })),
        conflicts: result.conflicts.map((entry) => ({ ...entry })),
    };
}
