"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskValidationChronologyError = exports.TASK_VALIDATION_RECORDING_MODES = exports.TASK_VALIDATION_EVIDENCE_BINDINGS = exports.TASK_VALIDATION_DEPENDENCY_MODES = exports.TASK_VALIDATION_SOURCES = exports.TASK_VALIDATION_STATUSES = void 0;
exports.previewTaskValidationRecordSync = previewTaskValidationRecordSync;
exports.applyTaskValidationRecordSync = applyTaskValidationRecordSync;
exports.readTaskValidationResults = readTaskValidationResults;
exports.projectTaskValidationIdentities = projectTaskValidationIdentities;
exports.selectEffectiveTaskValidations = selectEffectiveTaskValidations;
exports.deriveTaskValidationFreshness = deriveTaskValidationFreshness;
exports.reviewCurrentAttemptValidationEvidence = reviewCurrentAttemptValidationEvidence;
const node_child_process_1 = require("node:child_process");
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const mutation_preconditions_1 = require("../filesystem/mutation-preconditions");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const paths_1 = require("../paths");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const planning_document_utils_1 = require("./planning-document-utils");
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 task_waivers_1 = require("./task-waivers");
const execution_1 = require("./task-validation/execution");
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
const MAX_STATUS_BYTES = 16 * 1024 * 1024;
exports.TASK_VALIDATION_STATUSES = ['passed', 'failed', 'not_run', 'waived', 'blocked', 'not_applicable'];
exports.TASK_VALIDATION_SOURCES = ['manual', 'ci', 'imported'];
exports.TASK_VALIDATION_DEPENDENCY_MODES = ['repository', 'declared', 'unknown'];
exports.TASK_VALIDATION_EVIDENCE_BINDINGS = ['runtime-baseline', 'evidence-only', 'preservation-only'];
exports.TASK_VALIDATION_RECORDING_MODES = ['current-attempt', 'closed-attempt-backfill'];
class TaskValidationChronologyError extends TypeError {
    attemptOpenedAt;
    observedAt;
    phase;
    constructor(attemptOpenedAt, observedAt, phase) {
        const normalizedAttemptOpenedAt = new Date(attemptOpenedAt).toISOString();
        const normalizedObservedAt = new Date(observedAt).toISOString();
        super(phase === 'preflight'
            ? `Validation cannot execute before the attempt opened_at instant. Attempt opened_at resolves to '${normalizedAttemptOpenedAt}', but the command observed '${normalizedObservedAt}'. No child command was launched and no evidence was written.`
            : `Validation evidence recorded at '${normalizedObservedAt}' cannot precede its attempt opened_at instant '${normalizedAttemptOpenedAt}'.`);
        this.name = 'TaskValidationChronologyError';
        this.attemptOpenedAt = normalizedAttemptOpenedAt;
        this.observedAt = normalizedObservedAt;
        this.phase = phase;
    }
}
exports.TaskValidationChronologyError = TaskValidationChronologyError;
const plans = new WeakMap();
function previewTaskValidationRecordSync(request) {
    return prepareTaskValidationRecordSync(request, null);
}
function prepareTaskValidationRecordSync(request, completedExecution) {
    const repositoryRoot = node_fs_1.default.realpathSync(node_path_1.default.resolve(requireText(request.repositoryRoot, 'Repository root')));
    const taskPath = requireTaskPath(request.taskDefinitionPath);
    const task = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, taskPath);
    const summaryPath = taskPath.replace(/task-definition\.md$/, 'implementation-summary.md');
    const summary = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, summaryPath);
    if (!task.exists || !summary.exists) {
        throw new TypeError('Validation recording requires an existing canonical task pair.');
    }
    const taskText = node_fs_1.default.readFileSync(task.runtimePath, 'utf8');
    const summaryText = node_fs_1.default.readFileSync(summary.runtimePath, 'utf8');
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: taskText, implementationSummary: summaryText });
    if (projection.blockingFindings.length > 0) {
        throw new TypeError(`Validation recording requires a valid task pair: ${projection.blockingFindings.map((entry) => entry.message).join(' ')}`);
    }
    const attemptId = requireIdentity(request.attemptId, 'Attempt identity');
    const current = projection.summary?.currentAttempt ?? null;
    const attempt = projection.summary?.attempts.find((entry) => entry.attemptId === attemptId) ?? null;
    const closedAttemptBackfill = request.closedAttemptBackfill === true;
    const runtimeAttributionOptOut = (0, planning_document_utils_1.readTopLevelField)((0, planning_document_utils_1.parseMarkdownFrontMatter)(taskText).frontMatterLines, 'runtime_attribution_required')?.trim() ===
        'false';
    if (closedAttemptBackfill) {
        if (attempt === null || attempt.closedAt === null) {
            throw new TypeError('Closed-attempt validation backfill requires the named materialized closed attempt.');
        }
        if (!runtimeAttributionOptOut) {
            throw new TypeError('Closed-attempt validation backfill requires explicit runtime_attribution_required: false.');
        }
    }
    else if (projection.task.status !== 'draft' || current?.attemptId !== attemptId || current.closedAt !== null) {
        throw new TypeError('Validation recording requires the named open current attempt on a draft task.');
    }
    const evidenceBinding = closedAttemptBackfill
        ? 'preservation-only'
        : runtimeAttributionOptOut
            ? 'evidence-only'
            : 'runtime-baseline';
    if (evidenceBinding === 'runtime-baseline') {
        const baseline = (0, task_cycle_runtime_attribution_1.readAttemptRuntimeBaseline)(summaryPath, summaryText, attemptId);
        if (baseline === null ||
            baseline.taskCycleId !== attemptId ||
            baseline.checkpointId !== attempt?.readinessReviewId) {
            throw new TypeError('Validation recording requires an attempt-owned baseline with matching attempt and readiness identities.');
        }
    }
    const validationId = requireIdentity(request.validationId, 'Validation identity');
    const allResults = readTaskValidationResults(summaryPath, summaryText);
    if (allResults.some((entry) => entry.validationId === validationId)) {
        throw new TypeError(`Validation identity '${validationId}' already exists; validation history is append-only.`);
    }
    const source = requireToken(request.source, exports.TASK_VALIDATION_SOURCES, 'validation source');
    const dependencyMode = requireToken(request.dependencyMode, exports.TASK_VALIDATION_DEPENDENCY_MODES, 'dependency mode');
    const sourceReference = optionalText(request.sourceReference);
    if ((source === 'ci' || source === 'imported') && sourceReference === null) {
        throw new TypeError(`${source} validation requires a source reference.`);
    }
    const outputReference = optionalText(request.outputReference);
    if (closedAttemptBackfill && (source !== 'imported' || sourceReference === null || outputReference === null)) {
        throw new TypeError('Closed-attempt validation backfill requires imported source plus source and output references.');
    }
    const commandOwnedExecutionPlan = completedExecution?.executionPlan ??
        (0, execution_1.resolveCommandOwnedExecutionPlan)({
            repositoryRoot,
            validationId,
            attemptId,
            source,
            command: optionalText(request.command),
            executionArgv: request.executionArgv,
            executionContractPath: optionalText(request.executionContractPath),
            expectJson: request.expectJson,
            executionTimeoutMs: request.executionTimeoutMs,
            supersedesValidationId: optionalIdentity(request.supersedesValidationId, 'Superseded validation identity'),
        });
    if (commandOwnedExecutionPlan !== null &&
        (commandOwnedExecutionPlan.validationId !== validationId || commandOwnedExecutionPlan.attemptId !== attemptId)) {
        throw new TypeError('Command-owned execution plan identity does not match the selected validation and attempt.');
    }
    const commandOwnedExecution = completedExecution === null
        ? null
        : {
            verification: completedExecution.verification,
            supersedesValidationId: completedExecution.executionPlan.supersedesValidationId,
            command: completedExecution.executionPlan.command,
            recordedAt: completedExecution.recordedAt,
        };
    const requestedStatus = request.status === undefined || request.status === null
        ? null
        : requireToken(request.status, exports.TASK_VALIDATION_STATUSES, 'validation status');
    if (commandOwnedExecutionPlan !== null && commandOwnedExecution === null) {
        validateDeferredCommandOwnedPreview({
            request,
            attemptId,
            attemptOpenedAt: attempt?.openedAt ?? null,
            allResults,
            commandOwnedExecutionPlan,
            requestedStatus,
        });
        const preconditions = [task, summary].flatMap((target) => [
            (0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(target),
            (0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target),
        ]);
        const snapshot = {
            taskDefinitionPath: task.repositoryRelativePath,
            implementationSummaryPath: summary.repositoryRelativePath,
            validation: null,
            executionPlan: commandOwnedExecutionPlan,
            executionEvidence: null,
            executionRecordedAt: null,
            validationIdentityProjection: projectTaskValidationIdentities(allResults.filter((entry) => entry.attemptId === attemptId)),
            intendedPaths: [summary.repositoryRelativePath],
            changedPaths: [],
            expected: preconditions.map((entry) => ({
                path: entry.repositoryRelativePath,
                kind: entry.kind,
                expectedState: entry.kind === 'content-revision' ? entry.expectedRevision : entry.expectedPresence,
            })),
            writeOutcome: 'previewed',
            transactionId: null,
            conflicts: [],
            recoveryPath: null,
            failure: null,
            failureKind: null,
        };
        plans.set(snapshot, {
            kind: 'command-owned',
            repositoryRoot,
            request: cloneRecordRequest(request),
            executionPlan: commandOwnedExecutionPlan,
            snapshot: cloneRecordResult(snapshot),
        });
        return snapshot;
    }
    if (commandOwnedExecution !== null &&
        requestedStatus !== null &&
        requestedStatus !== commandOwnedExecution.verification.observedStatus) {
        throw new TypeError(`Declared validation status '${requestedStatus}' does not match command-owned exit status ${commandOwnedExecution.verification.observedExitCode} (${commandOwnedExecution.verification.observedStatus}).`);
    }
    const status = commandOwnedExecution?.verification.observedStatus ?? requestedStatus ?? missingStatus();
    const command = commandOwnedExecution?.command ?? requireText(request.command, 'Validation command');
    const referencedPass = commandOwnedExecution === null
        ? (0, execution_1.verifyReferencedPassedValidationEvidence)({
            repositoryRoot,
            validationId,
            attemptId,
            command,
            status,
            source,
            outputReference,
        })
        : null;
    const verifiablePass = commandOwnedExecution ?? referencedPass;
    if (verifiablePass !== null &&
        request.exitCode !== undefined &&
        request.exitCode !== null &&
        request.exitCode !== verifiablePass.verification.observedExitCode) {
        throw new TypeError(`Caller-supplied exit code ${request.exitCode} does not match verified exit code ${verifiablePass.verification.observedExitCode}.`);
    }
    const exitCode = normalizeExitCode(verifiablePass?.verification.observedExitCode ?? request.exitCode ?? null, status, verifiablePass?.verification.semanticAssertions?.passed === false);
    const supersedesValidationId = status === 'waived' && source === 'manual'
        ? optionalIdentity(request.supersedesValidationId, 'Superseded validation identity')
        : verifiablePass?.supersedesValidationId ?? null;
    if (supersedesValidationId !== null) {
        const superseded = allResults.find((entry) => entry.validationId === supersedesValidationId);
        if (superseded === undefined) {
            throw new TypeError(`Superseded validation identity '${supersedesValidationId}' does not exist in append-only history.`);
        }
        if (superseded.attemptId !== attemptId) {
            throw new TypeError('Validation replacement must supersede an identity from the same attempt.');
        }
        if (allResults.some((entry) => entry.supersedesValidationId === supersedesValidationId)) {
            throw new TypeError(`Validation identity '${supersedesValidationId}' already has an explicit replacement.`);
        }
    }
    const waiverId = optionalIdentity(request.waiverId, 'Waiver identity');
    if (status === 'waived' && waiverId === null) {
        throw new TypeError('A waived validation result requires an explicit waiver identity reference.');
    }
    const recordedAt = commandOwnedExecution?.recordedAt ?? requireTimestamp(request.recordedAt, 'Validation recorded time');
    if (status === 'waived' &&
        (0, task_waivers_1.findApplicableTaskWaiver)(summaryPath, summaryText, {
            waiverType: 'validation',
            subjectId: validationId,
            attemptId,
            evaluatedAt: recordedAt,
            waiverId,
        })?.applicable !== true) {
        throw new TypeError(`Waived validation '${validationId}' requires a matching active validation waiver.`);
    }
    if (attempt?.openedAt !== null &&
        attempt?.openedAt !== undefined &&
        Date.parse(recordedAt) < Date.parse(attempt.openedAt)) {
        throw new TaskValidationChronologyError(attempt.openedAt, recordedAt, 'recording');
    }
    const paths = uniquePaths(request.scopePaths ?? []);
    const dependencies = uniquePaths(request.dependencies ?? []);
    if (dependencyMode === 'declared' && paths.length === 0 && dependencies.length === 0) {
        throw new TypeError('Declared validation scope requires at least one explicit path or dependency.');
    }
    const administrativePaths = [task.repositoryRelativePath, summary.repositoryRelativePath];
    const capturedState = captureBoundState(repositoryRoot, [...paths, ...dependencies], administrativePaths);
    const boundedState = closedAttemptBackfill ? { ...capturedState, observationsComplete: false } : capturedState;
    const validation = {
        schemaVersion: 2,
        validationId,
        supersedesValidationId,
        attemptId,
        name: requireText(request.name, 'Validation name'),
        command,
        required: request.required === true,
        status,
        exitCode,
        summary: requireText(request.summary, 'Validation summary'),
        outputReference,
        sourceReference,
        sourceRevision: boundedState.sourceRevision,
        workingTreeDigest: boundedState.workingTreeDigest,
        recordedAt,
        source,
        evidenceBinding,
        recordingMode: closedAttemptBackfill ? 'closed-attempt-backfill' : 'current-attempt',
        validationScope: { dependencyMode, paths, dependencies },
        validatedStateDigest: sha256(canonicalJson({
            sourceRevision: boundedState.sourceRevision,
            workingTreeDigest: boundedState.workingTreeDigest,
            dependencyMode,
            paths,
            dependencies,
            observations: boundedState.observations,
        })),
        waiverId,
        administrativePaths,
        observationsComplete: boundedState.observationsComplete,
        observations: boundedState.observations,
        evidenceVerification: verifiablePass?.verification ?? null,
    };
    const nextSummary = appendValidationResult(summaryText, attemptId, validation);
    const nextProjection = (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: taskText, implementationSummary: nextSummary });
    if (nextProjection.blockingFindings.length > 0) {
        throw new TypeError(`Validation recording would violate task-pair invariants: ${nextProjection.blockingFindings.map((entry) => entry.message).join(' ')}`);
    }
    const preconditions = [task, summary].flatMap((target) => [
        (0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(target),
        (0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target),
    ]);
    const snapshot = {
        taskDefinitionPath: task.repositoryRelativePath,
        implementationSummaryPath: summary.repositoryRelativePath,
        validation,
        executionPlan: commandOwnedExecutionPlan,
        executionEvidence: commandOwnedExecution?.verification ?? null,
        executionRecordedAt: commandOwnedExecution?.recordedAt ?? null,
        validationIdentityProjection: projectTaskValidationIdentities([...allResults, validation].filter((entry) => entry.attemptId === attemptId)),
        intendedPaths: [summary.repositoryRelativePath],
        changedPaths: [],
        expected: preconditions.map((entry) => ({
            path: entry.repositoryRelativePath,
            kind: entry.kind,
            expectedState: entry.kind === 'content-revision' ? entry.expectedRevision : entry.expectedPresence,
        })),
        writeOutcome: 'previewed',
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null,
        failureKind: null,
    };
    plans.set(snapshot, {
        kind: 'mutation',
        repositoryRoot,
        summaryText: nextSummary,
        preconditions,
        snapshot: cloneRecordResult(snapshot),
        recordedAt,
    });
    return snapshot;
}
function applyTaskValidationRecordSync(preview, options = {}) {
    const plan = plans.get(preview);
    if (plan === undefined)
        throw new TypeError('Validation apply requires its original in-process preview.');
    if (plan.kind === 'command-owned') {
        let refreshedPreview;
        try {
            refreshedPreview = prepareTaskValidationRecordSync(plan.request, null);
        }
        catch (error) {
            const chronologyFailure = error instanceof TaskValidationChronologyError;
            return failedCommandOwnedTransition(plan.snapshot, chronologyFailure
                ? error.message
                : `Task pair or validation preconditions became stale before command-owned validation execution; exact argv was not launched. ${errorMessage(error)}`, chronologyFailure ? 'recordability' : 'pre-execution-precondition');
        }
        const refreshedPlan = plans.get(refreshedPreview);
        if (refreshedPlan?.kind !== 'command-owned') {
            return failedCommandOwnedTransition(plan.snapshot, 'Task validation mode changed before command-owned execution; exact argv was not launched. Refresh the preview.', 'pre-execution-precondition');
        }
        let completedExecution;
        try {
            completedExecution = (0, execution_1.executeCommandOwnedValidationSync)(refreshedPlan.repositoryRoot, refreshedPlan.executionPlan);
        }
        catch (error) {
            const environmentExecutionFailure = error instanceof execution_1.TaskValidationEnvironmentExecutionError ? error.failure : null;
            return failedCommandOwnedTransition(refreshedPlan.snapshot, `Command-owned validation execution did not produce recordable evidence; no evidence was written and exact argv was not rerun. ${errorMessage(error)}`, 'environment-execution', environmentExecutionFailure);
        }
        let completedPreview;
        try {
            completedPreview = prepareTaskValidationRecordSync(plan.request, completedExecution);
        }
        catch (error) {
            const chronologyFailure = error instanceof TaskValidationChronologyError;
            const pairStaleFailure = !chronologyFailure &&
                commandOwnedPairChangedSync(refreshedPlan.repositoryRoot, refreshedPlan.snapshot);
            return failedCommandOwnedTransition({
                ...refreshedPlan.snapshot,
                executionPlan: completedExecution.executionPlan,
                executionEvidence: completedExecution.verification,
                executionRecordedAt: completedExecution.recordedAt,
            }, pairStaleFailure
                ? `Task pair changed during command-owned validation execution; no validation evidence was recorded and exact argv was not rerun. ${errorMessage(error)}`
                : chronologyFailure
                    ? `Command-owned validation execution completed, but lifecycle chronology made its result unrecordable; exact argv was not rerun. ${error.message}`
                    : `Command-owned validation execution completed, but evidence preparation failed; no validation evidence was recorded and exact argv was not rerun. ${errorMessage(error)}`, pairStaleFailure ? 'pair-stale' : chronologyFailure ? 'recordability' : 'preparation');
        }
        const completedPlan = plans.get(completedPreview);
        if (completedPlan?.kind !== 'mutation') {
            return failedCommandOwnedTransition({
                ...refreshedPlan.snapshot,
                executionPlan: completedExecution.executionPlan,
                executionEvidence: completedExecution.verification,
                executionRecordedAt: completedExecution.recordedAt,
            }, 'Command-owned validation execution completed, but no append plan was produced; no evidence was recorded and exact argv was not rerun.', 'preparation');
        }
        return applyTaskValidationMutationPlan(completedPlan, options);
    }
    return applyTaskValidationMutationPlan(plan, options);
}
function applyTaskValidationMutationPlan(plan, options) {
    (0, task_lifecycle_timestamps_1.revalidateObservedLifecycleTimestamp)(plan.recordedAt, 'recorded_at', 'Validation recorded time');
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(plan.repositoryRoot, {
        writes: [{ path: plan.snapshot.implementationSummaryPath, contents: plan.summaryText }],
        preconditions: plan.preconditions,
    }, options.transaction);
    return {
        ...cloneRecordResult(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),
        failureKind: transaction.outcome === 'committed' ? null : 'transaction',
    };
}
function readTaskValidationResults(artifactPath, summaryText) {
    const lines = (0, planning_document_utils_1.parseMarkdownFrontMatter)(summaryText).frontMatterLines;
    const results = [];
    for (const line of lines) {
        const match = line.match(/^ {4}validation_results:\s*(.*)$/);
        if (match === null || match[1].trim() === '[]')
            continue;
        let decoded;
        try {
            const scalar = JSON.parse(match[1]);
            if (typeof scalar !== 'string')
                throw new TypeError('not a string scalar');
            decoded = JSON.parse(scalar);
            if (!Array.isArray(decoded))
                throw new TypeError('not an array');
        }
        catch {
            throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
        }
        for (const entry of decoded)
            results.push(validateStoredValidation(entry, artifactPath));
    }
    projectTaskValidationIdentities(results);
    return results;
}
function projectTaskValidationIdentities(validations) {
    const byId = new Map();
    const superseded = new Set();
    const replacements = [];
    for (const validation of validations) {
        if (byId.has(validation.validationId)) {
            throw new TypeError(`Validation identity '${validation.validationId}' is duplicated in append-only history.`);
        }
        const supersedesValidationId = validation.supersedesValidationId ?? null;
        if (supersedesValidationId !== null) {
            const predecessor = byId.get(supersedesValidationId);
            if (predecessor === undefined) {
                throw new TypeError(`Validation identity '${validation.validationId}' supersedes missing or forward identity '${supersedesValidationId}'.`);
            }
            if (predecessor.attemptId !== validation.attemptId) {
                throw new TypeError(`Validation identity '${validation.validationId}' cannot supersede a result from another attempt.`);
            }
            if (superseded.has(supersedesValidationId)) {
                throw new TypeError(`Validation identity '${supersedesValidationId}' has more than one explicit replacement.`);
            }
            superseded.add(supersedesValidationId);
            replacements.push({
                validationId: validation.validationId,
                supersedesValidationId,
            });
        }
        byId.set(validation.validationId, validation);
    }
    return {
        effectiveValidationIds: validations
            .filter((validation) => !superseded.has(validation.validationId))
            .map((validation) => validation.validationId),
        supersededValidationIds: validations
            .filter((validation) => superseded.has(validation.validationId))
            .map((validation) => validation.validationId),
        replacements,
    };
}
function selectEffectiveTaskValidations(validations) {
    const projection = projectTaskValidationIdentities(validations);
    const effectiveIds = new Set(projection.effectiveValidationIds);
    return validations.filter((validation) => effectiveIds.has(validation.validationId));
}
function deriveTaskValidationFreshness(repositoryRoot, validation) {
    if (validation.evidenceBinding === 'preservation-only' || validation.recordingMode === 'closed-attempt-backfill') {
        return {
            validation,
            freshness: 'stale',
            reason: 'Preservation-only closed-attempt backfill cannot claim contemporaneous repository-state coverage.',
        };
    }
    if (!validation.observationsComplete) {
        return { validation, freshness: 'stale', reason: 'Recorded repository observation was incomplete.' };
    }
    const current = captureBoundState(repositoryRoot, [...validation.validationScope.paths, ...validation.validationScope.dependencies], validation.administrativePaths);
    if (!current.observationsComplete) {
        return { validation, freshness: 'stale', reason: 'Current repository observation is incomplete.' };
    }
    if (validation.validationScope.dependencyMode === 'declared') {
        const unchanged = canonicalJson(current.observations) === canonicalJson(validation.observations);
        return unchanged
            ? { validation, freshness: 'current', reason: 'All declared path and dependency observations are unchanged.' }
            : { validation, freshness: 'stale', reason: 'A declared path or dependency changed after validation.' };
    }
    if (current.sourceRevision !== validation.sourceRevision ||
        current.workingTreeDigest !== validation.workingTreeDigest) {
        return {
            validation,
            freshness: 'stale',
            reason: validation.validationScope.dependencyMode === 'unknown'
                ? 'Repository state changed and unknown dependency coverage cannot be proven unrelated.'
                : 'Repository-bound validation state changed.',
        };
    }
    return {
        validation,
        freshness: 'current',
        reason: validation.validationScope.dependencyMode === 'unknown'
            ? 'Repository state exactly matches the recorded unknown-dependency state.'
            : 'Repository-bound validation state is unchanged.',
    };
}
function reviewCurrentAttemptValidationEvidence(repositoryRoot, summaryPath, summaryText) {
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: '---\nartifact_type: task_definition\nid: validation-review\ntask: validation-review\nstatus: draft\nreadiness: needs_more_evidence\n---\n',
        implementationSummary: summaryText,
    });
    const attemptId = projection.summary?.currentAttemptId ?? null;
    if (attemptId === null)
        return [];
    const attemptResults = readTaskValidationResults(summaryPath, summaryText).filter((entry) => entry.attemptId === attemptId);
    const results = selectEffectiveTaskValidations(attemptResults);
    const issues = [];
    for (const result of results) {
        if (result.evidenceBinding === 'preservation-only' || result.recordingMode === 'closed-attempt-backfill') {
            continue;
        }
        if (!result.required || result.status === 'not_applicable')
            continue;
        const freshness = deriveTaskValidationFreshness(repositoryRoot, result);
        if (freshness.freshness === 'stale') {
            issues.push(`Required validation '${result.validationId}' ('${result.name}') is stale: ${freshness.reason}`);
            continue;
        }
        if (['failed', 'blocked', 'not_run'].includes(result.status)) {
            issues.push(`Required validation '${result.validationId}' ('${result.name}') remains ${result.status}.`);
        }
        if (result.status === 'waived') {
            const waiver = (0, task_waivers_1.findApplicableTaskWaiver)(summaryPath, summaryText, {
                waiverType: 'validation',
                subjectId: result.validationId,
                attemptId,
                evaluatedAt: new Date().toISOString(),
                waiverId: result.waiverId,
            });
            if (waiver?.applicable !== true) {
                issues.push(`Required validation '${result.validationId}' ('${result.name}') cites waiver '${result.waiverId}' but no matching active waiver applies.`);
            }
        }
    }
    return issues;
}
function appendValidationResult(summaryText, attemptId, result) {
    const newline = summaryText.includes('\r\n') ? '\r\n' : '\n';
    const hadFinalNewline = summaryText.endsWith('\n');
    const lines = summaryText.split(/\r?\n/);
    const parsed = (0, planning_document_utils_1.parseMarkdownFrontMatter)(summaryText);
    const range = locateAttemptRange(parsed.frontMatterLines, attemptId);
    if (range === null)
        throw new TypeError(`Attempt '${attemptId}' is not materialized.`);
    const fullStart = range.start + 1;
    const fullEnd = range.end + 1;
    const header = lines.findIndex((line, index) => index > fullStart && index < fullEnd && /^ {4}validation_results:/.test(line));
    if (header < 0)
        throw new TypeError(`Attempt '${attemptId}' has no validation_results owner.`);
    let existing = [];
    const raw = lines[header].match(/^ {4}validation_results:\s*(.*)$/)?.[1].trim() ?? '';
    if (raw !== '[]') {
        try {
            const scalar = JSON.parse(raw);
            if (typeof scalar !== 'string')
                throw new TypeError('not string');
            const decoded = JSON.parse(scalar);
            if (!Array.isArray(decoded))
                throw new TypeError('not array');
            existing = decoded.map((entry) => validateStoredValidation(entry, 'implementation-summary.md'));
        }
        catch {
            throw new TypeError(`Attempt '${attemptId}' uses an unsupported validation_results shape.`);
        }
    }
    if (raw === '') {
        throw new TypeError(`Attempt '${attemptId}' uses an unsupported validation_results shape.`);
    }
    lines[header] = `    validation_results: ${JSON.stringify(JSON.stringify([...existing, result]))}`;
    const joined = lines.join(newline);
    return hadFinalNewline && !joined.endsWith(newline) ? `${joined}${newline}` : joined;
}
function locateAttemptRange(lines, attemptId) {
    const attemptsIndex = lines.findIndex((line) => /^attempts:\s*$/.test(line));
    if (attemptsIndex < 0)
        return null;
    for (let index = attemptsIndex + 1; index < lines.length; index += 1) {
        const match = lines[index].match(/^ {2}- attempt_id:\s*(.*)$/);
        if (match === null)
            continue;
        let end = index + 1;
        while (end < lines.length && !/^ {2}-\s+/.test(lines[end]) && /^\s/.test(lines[end]))
            end += 1;
        if (decodeScalar(match[1]) === attemptId)
            return { start: index, end };
        index = end - 1;
    }
    return null;
}
function captureBoundState(repositoryRoot, paths, administrativePaths = []) {
    let complete = true;
    const revision = runGit(repositoryRoot, ['rev-parse', '--verify', 'HEAD']);
    const status = runGitBytes(repositoryRoot, [
        'status',
        '--porcelain=v1',
        '-z',
        '--untracked-files=all',
        '--',
        '.',
        ...administrativePaths.map((entry) => `:(exclude)${entry}`),
    ]);
    if (revision === null || status === null || status.length > MAX_STATUS_BYTES)
        complete = false;
    const observations = [];
    for (const relativePath of paths) {
        try {
            observations.push(observePath(repositoryRoot, relativePath));
        }
        catch {
            complete = false;
            observations.push({ path: relativePath, exists: false, kind: 'absent', contentSha256: null });
        }
    }
    return {
        sourceRevision: revision ?? 'unknown',
        workingTreeDigest: sha256(status ?? Buffer.alloc(0)),
        observationsComplete: complete,
        observations,
    };
}
function observePath(repositoryRoot, relativePath) {
    const normalized = (0, paths_1.parseRepositoryRelativePath)(relativePath).normalizedPath;
    const absolute = node_path_1.default.join(repositoryRoot, normalized);
    const stats = node_fs_1.default.lstatSync(absolute, { throwIfNoEntry: false });
    if (stats === undefined)
        return { path: normalized, exists: false, kind: 'absent', contentSha256: null };
    if (stats.isSymbolicLink()) {
        return { path: normalized, exists: true, kind: 'symlink', contentSha256: sha256(node_fs_1.default.readlinkSync(absolute)) };
    }
    if (stats.isFile()) {
        return { path: normalized, exists: true, kind: 'file', contentSha256: sha256(node_fs_1.default.readFileSync(absolute)) };
    }
    if (stats.isDirectory()) {
        const entries = node_fs_1.default
            .readdirSync(absolute, { recursive: true, withFileTypes: true })
            .filter((entry) => entry.isFile() || entry.isSymbolicLink())
            .map((entry) => node_path_1.default.relative(absolute, node_path_1.default.join(entry.parentPath, entry.name)).split(node_path_1.default.sep).join('/'))
            .sort();
        return { path: normalized, exists: true, kind: 'directory', contentSha256: sha256(entries.join('\n')) };
    }
    return { path: normalized, exists: true, kind: 'other', contentSha256: null };
}
function validateStoredValidation(value, artifactPath) {
    if (value === null || typeof value !== 'object' || Array.isArray(value)) {
        throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
    }
    const result = value;
    if ((result.schemaVersion !== 1 && result.schemaVersion !== 2) ||
        !IDENTITY_PATTERN.test(result.validationId) ||
        !IDENTITY_PATTERN.test(result.attemptId) ||
        !exports.TASK_VALIDATION_STATUSES.includes(result.status) ||
        !exports.TASK_VALIDATION_SOURCES.includes(result.source) ||
        !exports.TASK_VALIDATION_DEPENDENCY_MODES.includes(result.validationScope?.dependencyMode) ||
        !SHA256_PATTERN.test(result.workingTreeDigest) ||
        !SHA256_PATTERN.test(result.validatedStateDigest) ||
        !(0, task_lifecycle_1.isTaskLifecycleTimestamp)(result.recordedAt) ||
        !Array.isArray(result.administrativePaths) ||
        !Array.isArray(result.observations)) {
        throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
    }
    if (result.supersedesValidationId !== undefined &&
        result.supersedesValidationId !== null &&
        (!IDENTITY_PATTERN.test(result.supersedesValidationId) || result.supersedesValidationId === result.validationId)) {
        throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
    }
    if (result.evidenceBinding !== undefined && !exports.TASK_VALIDATION_EVIDENCE_BINDINGS.includes(result.evidenceBinding)) {
        throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
    }
    if (result.recordingMode !== undefined && !exports.TASK_VALIDATION_RECORDING_MODES.includes(result.recordingMode)) {
        throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
    }
    const verification = result.evidenceVerification;
    if (result.schemaVersion === 2 &&
        result.status === 'passed' &&
        (verification === undefined || verification === null)) {
        throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
    }
    if (verification !== undefined && verification !== null) {
        const semanticAssertions = verification.semanticAssertions;
        if ((verification.mode !== 'command-owned-execution' && verification.mode !== 'verified-result-reference') ||
            verification.observedStatus !== result.status ||
            (verification.observedStatus === 'passed' && verification.observedExitCode !== 0) ||
            (verification.observedStatus === 'failed' &&
                verification.observedExitCode === 0 &&
                semanticAssertions?.passed !== false) ||
            !Number.isInteger(verification.observedExitCode) ||
            verification.observedExitCode < 0 ||
            verification.observedExitCode > 255 ||
            (verification.durationMs !== undefined &&
                (!Number.isInteger(verification.durationMs) || verification.durationMs < 0)) ||
            (verification.termination !== undefined &&
                !['exit', 'timed_out', 'signal'].includes(verification.termination)) ||
            (verification.signal !== undefined && verification.signal !== null && typeof verification.signal !== 'string') ||
            !Array.isArray(verification.commandArgv) ||
            !(0, execution_1.isBoundedOutputEvidence)(verification.stdout) ||
            !(0, execution_1.isBoundedOutputEvidence)(verification.stderr) ||
            (semanticAssertions !== undefined &&
                semanticAssertions !== null &&
                !(0, execution_1.isSemanticAssertionEvidence)(semanticAssertions)) ||
            (verification.observedStatus === 'passed' && semanticAssertions?.passed === false) ||
            (result.source === 'manual' && verification.mode !== 'command-owned-execution') ||
            ((result.source === 'ci' || result.source === 'imported') && verification.mode !== 'verified-result-reference')) {
            throw new TypeError(`Validation result evidence is malformed at '${artifactPath}'.`);
        }
    }
    return result;
}
function validateDeferredCommandOwnedPreview(options) {
    requireText(options.request.name, 'Validation name');
    requireText(options.request.summary, 'Validation summary');
    if (options.request.exitCode !== undefined && options.request.exitCode !== null) {
        const exitCode = options.request.exitCode;
        if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) {
            throw new TypeError('Validation exit code must be an integer from 0 through 255.');
        }
    }
    if (options.requestedStatus !== null &&
        options.requestedStatus !== 'passed' &&
        options.requestedStatus !== 'failed') {
        throw new TypeError(`Command-owned validation can derive only passed or failed, not '${options.requestedStatus}'.`);
    }
    const supersedesValidationId = options.commandOwnedExecutionPlan.supersedesValidationId;
    if (supersedesValidationId !== null) {
        const superseded = options.allResults.find((entry) => entry.validationId === supersedesValidationId);
        if (superseded === undefined) {
            throw new TypeError(`Superseded validation identity '${supersedesValidationId}' does not exist in append-only history.`);
        }
        if (superseded.attemptId !== options.attemptId) {
            throw new TypeError('Validation replacement must supersede an identity from the same attempt.');
        }
        if (options.allResults.some((entry) => entry.supersedesValidationId === supersedesValidationId)) {
            throw new TypeError(`Validation identity '${supersedesValidationId}' already has an explicit replacement.`);
        }
    }
    optionalIdentity(options.request.waiverId, 'Waiver identity');
    const paths = uniquePaths(options.request.scopePaths ?? []);
    const dependencies = uniquePaths(options.request.dependencies ?? []);
    if (options.request.dependencyMode === 'declared' && paths.length === 0 && dependencies.length === 0) {
        throw new TypeError('Declared validation scope requires at least one explicit path or dependency.');
    }
    const prospectiveRecordedAt = options.request.recordedAt !== undefined &&
        options.request.recordedAt !== null &&
        (0, task_lifecycle_1.isTaskLifecycleTimestamp)(options.request.recordedAt)
        ? options.request.recordedAt
        : currentTimestamp();
    if (options.attemptOpenedAt !== null &&
        Date.parse(prospectiveRecordedAt) < Date.parse(options.attemptOpenedAt)) {
        throw new TaskValidationChronologyError(options.attemptOpenedAt, prospectiveRecordedAt, 'preflight');
    }
}
function cloneRecordRequest(request) {
    return {
        ...request,
        executionArgv: request.executionArgv === undefined ? undefined : [...request.executionArgv],
        expectJson: request.expectJson === undefined ? undefined : [...request.expectJson],
        scopePaths: request.scopePaths === undefined ? undefined : [...request.scopePaths],
        dependencies: request.dependencies === undefined ? undefined : [...request.dependencies],
    };
}
function failedCommandOwnedTransition(snapshot, failure, failureKind, environmentExecutionFailure = null) {
    return {
        ...cloneRecordResult(snapshot),
        changedPaths: [],
        writeOutcome: 'conflict',
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure,
        failureKind,
        environmentExecutionFailure,
    };
}
function commandOwnedPairChangedSync(repositoryRoot, snapshot) {
    for (const expected of snapshot.expected) {
        if (expected.kind !== 'content-revision')
            continue;
        const target = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, expected.path);
        if (!target.exists)
            return true;
        const observed = (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(target.runtimePath)).digest('hex');
        if (observed !== expected.expectedState)
            return true;
    }
    return false;
}
function currentTimestamp() {
    return new Date(Date.now()).toISOString();
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function cloneRecordResult(result) {
    return JSON.parse(JSON.stringify(result));
}
function normalizeExitCode(value, status, semanticAssertionFailure = false) {
    if (value !== null && (!Number.isInteger(value) || value < 0 || value > 255)) {
        throw new TypeError('Validation exit code must be an integer from 0 through 255.');
    }
    if (status === 'passed' && value !== 0)
        throw new TypeError('Passed validation requires exit code 0.');
    if (status === 'failed' && (value === null || (value === 0 && !semanticAssertionFailure))) {
        throw new TypeError('Failed validation requires a non-zero exit code unless command-owned semantic assertions failed.');
    }
    if (['not_run', 'waived', 'blocked', 'not_applicable'].includes(status) && value !== null) {
        throw new TypeError(`${status} validation must not claim an exit code.`);
    }
    return value;
}
function uniquePaths(values) {
    const output = [];
    const seen = new Set();
    for (const value of values) {
        const normalized = (0, paths_1.parseRepositoryRelativePath)(requireText(value, 'Validation path')).normalizedPath;
        if (!seen.has(normalized)) {
            seen.add(normalized);
            output.push(normalized);
        }
    }
    return output;
}
function requireTaskPath(value) {
    const parsed = (0, paths_1.parseRepositoryRelativePath)(requireText(value, 'Task definition path'));
    if (parsed.segments.at(-1) !== 'task-definition.md')
        throw new TypeError('Task definition path must end in task-definition.md.');
    return parsed.normalizedPath;
}
function requireIdentity(value, label) {
    const identity = requireText(value, label);
    if (!IDENTITY_PATTERN.test(identity))
        throw new TypeError(`${label} must use 3-128 machine-safe characters.`);
    return identity;
}
function optionalIdentity(value, label) {
    const text = optionalText(value);
    return text === null ? null : requireIdentity(text, label);
}
function requireTimestamp(value, label) {
    const timestamp = requireText(value, label);
    if (!(0, task_lifecycle_1.isTaskLifecycleTimestamp)(timestamp))
        throw new TypeError(`${label} must be ISO-8601 with an explicit timezone.`);
    return (0, task_lifecycle_timestamps_1.requireObservedLifecycleTimestamp)({
        value: timestamp,
        field: 'recorded_at',
        label
    });
}
function missingStatus() {
    throw new TypeError('Validation status is required unless command-owned execution derives passed or failed.');
}
function requireText(value, label) {
    if (typeof value !== 'string' || value.trim() === '')
        throw new TypeError(`${label} must be non-empty.`);
    return value.trim();
}
function optionalText(value) {
    return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
}
function requireToken(value, allowed, label) {
    const token = requireText(value, label);
    if (!allowed.includes(token))
        throw new TypeError(`Unsupported ${label} '${token}'.`);
    return token;
}
function runGit(repositoryRoot, args) {
    const result = (0, node_child_process_1.spawnSync)('git', [...args], { cwd: repositoryRoot, encoding: 'utf8', maxBuffer: MAX_STATUS_BYTES });
    return result.status === 0 ? result.stdout.trim() : null;
}
function runGitBytes(repositoryRoot, args) {
    const result = (0, node_child_process_1.spawnSync)('git', [...args], {
        cwd: repositoryRoot,
        encoding: 'buffer',
        maxBuffer: MAX_STATUS_BYTES + 1,
    });
    return result.status === 0 && Buffer.isBuffer(result.stdout) ? result.stdout : null;
}
function canonicalJson(value) {
    if (Array.isArray(value))
        return `[${value.map(canonicalJson).join(',')}]`;
    if (value !== null && typeof value === 'object') {
        const record = value;
        return `{${Object.keys(record)
            .sort()
            .map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
            .join(',')}}`;
    }
    return JSON.stringify(value);
}
function decodeScalar(value) {
    const trimmed = value.trim();
    if (trimmed.startsWith('"'))
        return JSON.parse(trimmed);
    return trimmed;
}
function sha256(value) {
    return `sha256:${(0, node_crypto_1.createHash)('sha256').update(value).digest('hex')}`;
}
