"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseShellFreeArgv = void 0;
exports.previewTaskValidationSetSync = previewTaskValidationSetSync;
exports.applyTaskValidationSetSync = applyTaskValidationSetSync;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
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_contract_impact_1 = require("./task-contract-impact");
const task_lifecycle_1 = require("./task-lifecycle");
const task_validation_1 = require("./task-validation");
const validation_command_1 = require("./validation-command");
const task_validation_recovery_1 = require("./task-validation-recovery");
const validation_input_coverage_1 = require("./validation-input-coverage");
var validation_command_2 = require("./validation-command");
Object.defineProperty(exports, "parseShellFreeArgv", { enumerable: true, get: function () { return validation_command_2.parseShellFreeArgv; } });
const REVISION_PATTERN = /^[a-f0-9]{64}$/u;
const IDENTITY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/u;
const DEFAULT_TIMEOUT_MS = 900_000;
const plans = new WeakMap();
function previewTaskValidationSetSync(request) {
    const prepared = prepareSet(request, true);
    plans.set(prepared.result, cloneRequest(request));
    return prepared.result;
}
function applyTaskValidationSetSync(preview) {
    const request = plans.get(preview);
    if (request === undefined) {
        throw new TypeError("Validation-set apply requires its original in-process preview.");
    }
    const prepared = prepareSet(request, false);
    if (prepared.result.outcome === "blocked") {
        return {
            ...prepared.result,
            writeOutcome: "no-write"
        };
    }
    const selected = selectExecutionItems(prepared, request);
    const items = prepared.result.items.map((item) => ({ ...item }));
    const executedValidationIds = [];
    const changedPaths = new Set();
    let outcome = "passed";
    let failure = null;
    let stoppedAt = -1;
    for (const selectedItem of selected) {
        const item = items[selectedItem.index];
        if (item.state === "already_passed")
            continue;
        if (item.state === "failed_requires_retry" && request.retryValidationId == null) {
            outcome = "failed";
            failure = `Required validation '${item.validationId}' is failed and requires its exact retry selector.`;
            stoppedAt = item.index;
            break;
        }
        let recordPreview;
        try {
            assertTaskRevision(prepared.repositoryRoot, prepared.result.taskDefinitionPath, prepared.result.taskRevision);
            recordPreview = (0, task_validation_1.previewTaskValidationRecordSync)(recordRequest(prepared, selectedItem.command, item.validationId, item.supersedesValidationId, prepared.result.timeoutMs));
        }
        catch (error) {
            outcome = "blocked";
            item.state = "unrecordable";
            item.failure = errorMessage(error);
            item.failureKind = error instanceof task_validation_1.TaskValidationChronologyError
                ? "recordability"
                : "pre-execution-precondition";
            item.retrySelector = null;
            failure = `Validation '${item.validationId}' was rejected before execution: ${item.failure}`;
            stoppedAt = item.index;
            break;
        }
        const recorded = (0, task_validation_1.applyTaskValidationRecordSync)(recordPreview);
        if (recorded.writeOutcome !== "committed" || recorded.validation === null) {
            outcome = "blocked";
            item.state = "unrecordable";
            item.status = recorded.executionEvidence?.observedStatus ?? null;
            item.exitCode = recorded.executionEvidence?.observedExitCode ?? null;
            item.recordedAt = recorded.executionRecordedAt;
            item.durationMs = recorded.executionEvidence?.durationMs ?? null;
            item.termination = recorded.executionEvidence?.termination ?? null;
            item.failure = recorded.failure ?? `Validation record ended with '${recorded.writeOutcome}'.`;
            item.failureKind = recorded.failureKind;
            item.environmentExecutionFailure = recorded.environmentExecutionFailure ?? null;
            item.executionEvidence = projectExecutionEvidence(recorded.executionEvidence, recorded.executionRecordedAt);
            item.retrySelector = null;
            failure = recorded.failureKind === "environment-execution"
                ? `Validation '${item.validationId}' could not start in the current execution environment; no validation result was recorded. Recover the execution environment before explicitly retrying. ${item.failure}`
                : `Validation '${item.validationId}' could not be recorded after its exact execution; it must not be rerun automatically. ${item.failure}`;
            stoppedAt = item.index;
            break;
        }
        for (const changedPath of recorded.changedPaths)
            changedPaths.add(changedPath);
        executedValidationIds.push(recorded.validation.validationId);
        projectRecordedItem(item, recorded.validation);
        if (recorded.validation.status !== "passed") {
            outcome = "failed";
            failure = `Required validation '${item.validationId}' ${item.termination === "timed_out" ? "timed out" : "failed"} with exit code ${item.exitCode}.`;
            stoppedAt = item.index;
            break;
        }
    }
    if (outcome !== "passed" && request.validationId == null && request.retryValidationId == null) {
        for (let index = stoppedAt + 1; index < items.length; index += 1) {
            if (items[index].state === "pending")
                items[index].state = "not_run";
        }
    }
    const remainingValidationIds = items
        .filter((item) => item.state === "pending" || item.state === "not_run")
        .map((item) => item.validationId);
    const nextAction = deriveNextAction({
        prepared,
        request,
        items,
        outcome
    });
    const recoveryActions = items.flatMap((item) => {
        if (item.state !== "unrecordable")
            return [];
        if (item.environmentExecutionFailure !== null) {
            return [(0, task_validation_recovery_1.createTaskValidationEnvironmentRecoveryAction)(item.environmentExecutionFailure)];
        }
        return item.executionEvidence === null
            ? []
            : [(0, task_validation_recovery_1.createTaskValidationUnrecordableRecoveryAction)()];
    });
    return {
        ...prepared.result,
        outcome,
        items,
        executedValidationIds,
        remainingValidationIds,
        changedPaths: [...changedPaths],
        nextAction,
        recoveryActions,
        writeOutcome: changedPaths.size > 0 ? "committed" : "no-write",
        failure
    };
}
function prepareSet(request, includePreviews) {
    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$/u, "implementation-summary.md");
    const summary = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, summaryPath);
    if (!task.exists || !summary.exists) {
        throw new TypeError("Validation-set execution 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 taskRevision = requireRevision(request.taskRevision);
    const observedRevision = sha256(taskText);
    if (observedRevision !== taskRevision) {
        throw new TypeError(`Task revision is stale: expected '${taskRevision}', observed '${observedRevision}'. No declared command was executed.`);
    }
    const attemptId = requireIdentity(request.attemptId, "Attempt identity");
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({ taskDefinition: taskText, implementationSummary: summaryText });
    if (projection.blockingFindings.length > 0) {
        throw new TypeError(`Validation-set execution requires a valid task pair: ${projection.blockingFindings.map((finding) => finding.message).join(" ")}`);
    }
    if (projection.summary?.currentAttempt?.attemptId !== attemptId
        || projection.summary.currentAttempt.closedAt !== null) {
        throw new TypeError("Validation-set execution requires the named open current attempt.");
    }
    const sections = (0, planning_document_utils_1.parseMarkdownSections)(taskText);
    const parsedCommands = parseDeclaredCommands(sections.get("Validation Commands") ?? "", taskRevision, attemptId);
    const scopePaths = parseSectionPaths(sections.get("Implementation Target Paths") ?? "");
    const dependencies = parseSectionPaths(sections.get("Dependencies") ?? "");
    if (scopePaths.length === 0 && dependencies.length === 0) {
        throw new TypeError("Declared validation-set execution requires at least one implementation target or dependency path.");
    }
    const contractImpact = (0, task_contract_impact_1.inspectTaskContractImpact)({
        repositoryRoot,
        targetPaths: scopePaths,
        validationCommandsText: sections.get("Validation Commands") ?? ""
    });
    const validationInputCoverage = (0, validation_input_coverage_1.inspectValidationInputCoverage)({
        repositoryRoot,
        commands: parsedCommands.map((command) => ({
            commandIndex: command.index,
            declaredCommand: command.declaredCommand,
            argv: command.commandArgv
        })),
        writableTargets: scopePaths,
        generatedOutputs: contractImpact.generatedOutputs.flatMap((entry) => entry.path === null ? [] : [entry.path]),
        dependencies: [
            ...dependencies,
            ...contractImpact.readOnlyDependencies.flatMap((entry) => entry.path === null ? [] : [entry.path]),
            ...contractImpact.frozenDependencies.flatMap((entry) => entry.path === null ? [] : [entry.path])
        ]
    });
    const preflightFindings = validationInputCoverage.issues.map((issue) => (0, validation_input_coverage_1.createValidationInputCoverageFinding)({
        issue,
        codePrefix: "TASK",
        artifactPath: taskPath,
        reviewBoundary: "task-validation-set-preflight"
    }));
    const timeoutMs = normalizeTimeout(request.timeoutMs);
    const allResults = (0, task_validation_1.readTaskValidationResults)(summary.repositoryRelativePath, summaryText)
        .filter((validation) => validation.attemptId === attemptId);
    const effective = [...(0, task_validation_1.selectEffectiveTaskValidations)(allResults)];
    const commands = selectValidationIdentityNamespace(parsedCommands, effective);
    const items = commands.map((command) => {
        const current = findCurrentSetValidation(command, effective);
        const validationId = current?.validationId ?? `${command.baseId}-r1`;
        const item = createItem({
            command,
            validationId,
            supersedesValidationId: null,
            current,
            summaryPath,
            scopePaths,
            dependencies,
            request,
            taskPath,
            taskRevision
        });
        return item;
    });
    const mode = request.retryValidationId != null
        ? "selected-retry"
        : request.validationId != null
            ? "selected-unrecorded"
            : "full-set";
    const result = {
        taskDefinitionPath: task.repositoryRelativePath,
        implementationSummaryPath: summary.repositoryRelativePath,
        taskRevision,
        attemptId,
        timeoutMs,
        mode,
        declaredCommandCount: commands.length,
        outcome: validationInputCoverage.complete ? "previewed" : "blocked",
        items: validationInputCoverage.complete
            ? items
            : items.map((item) => item.state === "pending" ? { ...item, state: "not_run" } : item),
        executedValidationIds: [],
        remainingValidationIds: items.filter((item) => item.state === "pending").map((item) => item.validationId),
        changedPaths: [],
        preconditions: [],
        validationInputCoverage,
        preflightFindings,
        nextAction: null,
        recoveryActions: [],
        writeOutcome: "previewed",
        failure: validationInputCoverage.complete
            ? null
            : preflightFindings.map((finding) => finding.message).join(" ")
    };
    const prepared = { repositoryRoot, taskText, summaryText, commands, scopePaths, dependencies, effective, result };
    if (includePreviews && validationInputCoverage.complete) {
        const selected = selectExecutionItems(prepared, request);
        const preconditions = [];
        for (const selection of selected) {
            const item = prepared.result.items[selection.index];
            if (item.state !== "pending")
                continue;
            const recordPreview = (0, task_validation_1.previewTaskValidationRecordSync)(recordRequest(prepared, selection.command, item.validationId, item.supersedesValidationId, timeoutMs));
            preconditions.push(...recordPreview.expected);
        }
        prepared.result.preconditions = uniquePreconditions(preconditions);
    }
    return prepared;
}
function createItem(options) {
    const state = options.current === null
        ? "pending"
        : options.current.status === "passed"
            ? "already_passed"
            : "failed_requires_retry";
    const retrySelector = options.current?.status === "failed"
        ? selector(options.request, options.taskPath, options.taskRevision, "supersede-failed", "--retry-validation-id", options.current.validationId)
        : options.current === null
            ? selector(options.request, options.taskPath, options.taskRevision, "execute-unrecorded", "--validation-id", options.validationId)
            : null;
    return {
        index: options.command.index,
        validationId: options.validationId,
        supersedesValidationId: options.supersedesValidationId,
        name: options.command.name,
        declaredCommand: options.command.declaredCommand,
        commandArgv: [...options.command.commandArgv],
        required: true,
        scopePaths: [...options.scopePaths],
        dependencies: [...options.dependencies],
        evidencePath: `${options.summaryPath}#validation:${options.validationId}`,
        state,
        status: options.current?.status ?? null,
        exitCode: options.current?.exitCode ?? null,
        recordedAt: options.current?.recordedAt ?? null,
        durationMs: options.current?.evidenceVerification?.durationMs ?? null,
        termination: options.current?.evidenceVerification?.termination ?? null,
        retrySelector,
        failure: null,
        failureKind: null,
        environmentExecutionFailure: null,
        executionEvidence: projectExecutionEvidence(options.current?.evidenceVerification ?? null, options.current?.recordedAt ?? null)
    };
}
function selectExecutionItems(prepared, request) {
    if (request.validationId != null && request.retryValidationId != null) {
        throw new TypeError("Select --validation-id or --retry-validation-id, not both.");
    }
    if (request.validationId != null) {
        const validationId = requireIdentity(request.validationId, "Selected validation identity");
        const index = prepared.result.items.findIndex((item) => item.validationId === validationId);
        if (index < 0 || prepared.result.items[index].state !== "pending") {
            throw new TypeError(`Selected validation identity '${validationId}' is not an unrecorded command in the current declared set.`);
        }
        return [{ index, command: prepared.commands[index] }];
    }
    if (request.retryValidationId != null) {
        const retryId = requireIdentity(request.retryValidationId, "Retry validation identity");
        const index = prepared.result.items.findIndex((item) => item.validationId === retryId);
        const current = prepared.effective.find((validation) => validation.validationId === retryId) ?? null;
        if (index < 0 || current?.status !== "failed") {
            throw new TypeError(`Retry validation identity '${retryId}' is not the effective failed result for a current declared command.`);
        }
        const evidenceArgv = current.evidenceVerification?.commandArgv;
        if (current.evidenceVerification?.mode !== "command-owned-execution"
            || !Array.isArray(evidenceArgv)
            || !sameArgv(evidenceArgv, prepared.commands[index].commandArgv)) {
            throw new TypeError(`Retry validation identity '${retryId}' lacks exact current command-owned argv and cannot be reconstructed from metadata.`);
        }
        const generation = validationGeneration(current.validationId, prepared.commands[index].baseId);
        const nextId = `${prepared.commands[index].baseId}-r${generation + 1}`;
        prepared.result.items[index] = {
            ...prepared.result.items[index],
            validationId: nextId,
            supersedesValidationId: retryId,
            commandArgv: [...evidenceArgv],
            evidencePath: `${prepared.result.implementationSummaryPath}#validation:${nextId}`,
            state: "pending",
            status: null,
            exitCode: null,
            recordedAt: null,
            durationMs: null,
            termination: null,
            retrySelector: null,
            failure: null,
            failureKind: null,
            environmentExecutionFailure: null,
            executionEvidence: null
        };
        return [{ index, command: { ...prepared.commands[index], commandArgv: [...evidenceArgv] } }];
    }
    return prepared.commands.map((command, index) => ({ index, command }));
}
function recordRequest(prepared, command, validationId, supersedesValidationId, timeoutMs, override) {
    const taskPath = override?.taskPath ?? prepared.result.taskDefinitionPath;
    const attemptId = override?.attemptId ?? prepared.result.attemptId;
    return {
        repositoryRoot: prepared.repositoryRoot,
        taskDefinitionPath: taskPath,
        validationId,
        attemptId,
        name: command.name,
        executionArgv: command.commandArgv,
        executionTimeoutMs: timeoutMs,
        supersedesValidationId,
        required: true,
        summary: `Executed declared validation command ${command.index + 1}.`,
        source: "manual",
        dependencyMode: "declared",
        scopePaths: prepared.scopePaths,
        dependencies: prepared.dependencies
    };
}
function deriveNextAction(options) {
    if (options.items.some((item) => item.state === "unrecordable"))
        return null;
    const failed = options.items.find((item) => item.state === "failed" || item.state === "failed_requires_retry");
    if (failed !== undefined) {
        const failedId = failed.state === "failed" ? failed.validationId : failed.validationId;
        return selector(options.request, options.prepared.result.taskDefinitionPath, options.prepared.result.taskRevision, "supersede-failed", "--retry-validation-id", failedId);
    }
    if (options.request.retryValidationId != null && options.outcome === "passed") {
        return selector(options.request, options.prepared.result.taskDefinitionPath, options.prepared.result.taskRevision, "continue-set");
    }
    return null;
}
function selector(request, taskPath, taskRevision, reason, selectionFlag, selectionId) {
    const argv = [
        "day-shift", "task", "validation-set",
        "--task-definition", taskPath,
        "--attempt-id", request.attemptId,
        "--task-revision", taskRevision,
        "--timeout-ms", String(normalizeTimeout(request.timeoutMs))
    ];
    if (selectionFlag !== undefined && selectionId !== undefined)
        argv.push(selectionFlag, selectionId);
    return { command: "task validation-set", argv, callable: true, writeCapable: true, reason };
}
function projectRecordedItem(item, validation) {
    item.state = validation.status === "passed" ? "passed" : "failed";
    item.status = validation.status;
    item.exitCode = validation.exitCode;
    item.recordedAt = validation.recordedAt;
    item.durationMs = validation.evidenceVerification?.durationMs ?? null;
    item.termination = validation.evidenceVerification?.termination ?? null;
    item.retrySelector = null;
    item.failureKind = null;
    item.environmentExecutionFailure = null;
    item.executionEvidence = projectExecutionEvidence(validation.evidenceVerification ?? null, validation.recordedAt);
}
function projectExecutionEvidence(evidence, recordedAt) {
    if (evidence === null || recordedAt === null)
        return null;
    return {
        commandArgv: [...evidence.commandArgv],
        observedStatus: evidence.observedStatus,
        observedExitCode: evidence.observedExitCode,
        recordedAt,
        durationMs: evidence.durationMs ?? null,
        termination: evidence.termination ?? null,
        signal: evidence.signal ?? null,
        stdout: {
            byteLength: evidence.stdout.byteLength,
            contentSha256: evidence.stdout.contentSha256,
            truncated: evidence.stdout.truncated
        },
        stderr: {
            byteLength: evidence.stderr.byteLength,
            contentSha256: evidence.stderr.contentSha256,
            truncated: evidence.stderr.truncated
        }
    };
}
function findCurrentSetValidation(command, effective) {
    const matches = effective.filter((validation) => {
        try {
            validationGeneration(validation.validationId, command.baseId);
            return true;
        }
        catch {
            return false;
        }
    });
    if (matches.length > 1) {
        throw new TypeError(`Declared validation '${command.baseId}' has multiple effective result identities.`);
    }
    const current = matches[0] ?? null;
    if (current !== null
        && !sameArgv(current.evidenceVerification?.commandArgv ?? [], command.commandArgv)) {
        throw new TypeError(`Stored validation '${current.validationId}' does not match the current declared argv.`);
    }
    return current;
}
function parseDeclaredCommands(section, taskRevision, attemptId) {
    return (0, validation_command_1.parseShellFencedValidationCommandLines)(section).map((declaredCommand, index) => {
        const commandArgv = (0, validation_command_1.parseShellFreeArgv)(declaredCommand);
        const legacyDigest = sha256(`${taskRevision}\u0000${index}\u0000${declaredCommand}`).slice(0, 16);
        const digest = sha256(`${taskRevision}\u0000${attemptId}\u0000${index}\u0000${declaredCommand}`).slice(0, 16);
        return {
            index,
            declaredCommand,
            commandArgv,
            baseId: `declared-validation-v2-${String(index + 1).padStart(2, "0")}-${digest}`,
            legacyBaseId: `declared-validation-${String(index + 1).padStart(2, "0")}-${legacyDigest}`,
            name: `declared validation ${String(index + 1).padStart(2, "0")}: ${boundedName(commandArgv)}`
        };
    });
}
function selectValidationIdentityNamespace(commands, effective) {
    let hasAttemptScopedResult = false;
    let hasLegacyResult = false;
    for (const validation of effective) {
        for (const command of commands) {
            hasAttemptScopedResult ||= matchesValidationBase(validation.validationId, command.baseId);
            hasLegacyResult ||= matchesValidationBase(validation.validationId, command.legacyBaseId);
        }
    }
    if (hasAttemptScopedResult && hasLegacyResult) {
        throw new TypeError("The current declared validation set mixes legacy and attempt-scoped identity namespaces.");
    }
    return commands.map((command) => hasLegacyResult
        ? { ...command, baseId: command.legacyBaseId }
        : command);
}
function parseSectionPaths(section) {
    const paths = [];
    for (const line of section.split(/\r?\n/u)) {
        const match = line.trim().match(/^-\s+(?:`([^`]+)`|([^\s].*))$/u);
        if (match === null)
            continue;
        const value = (match[1] ?? match[2]).trim();
        if (/^none(?: currently)?\.?$/iu.test(value))
            continue;
        paths.push((0, paths_1.parseRepositoryRelativePath)(value).normalizedPath);
    }
    return [...new Set(paths)];
}
function validationGeneration(validationId, baseId) {
    const match = validationId.match(new RegExp(`^${escapeRegExp(baseId)}-r([1-9][0-9]*)$`, "u"));
    if (match === null)
        throw new TypeError("Not a declared validation-set identity.");
    return Number(match[1]);
}
function matchesValidationBase(validationId, baseId) {
    try {
        validationGeneration(validationId, baseId);
        return true;
    }
    catch {
        return false;
    }
}
function normalizeTimeout(value) {
    const timeout = value ?? DEFAULT_TIMEOUT_MS;
    if (!Number.isInteger(timeout) || timeout < 1 || timeout > 86_400_000) {
        throw new TypeError("Validation-set timeout must be an integer from 1 through 86400000 milliseconds.");
    }
    return timeout;
}
function requireTaskPath(value) {
    const normalized = (0, paths_1.parseRepositoryRelativePath)(requireText(value, "Task definition path")).normalizedPath;
    if (!normalized.endsWith("/task-definition.md"))
        throw new TypeError("Task definition path must end with '/task-definition.md'.");
    return normalized;
}
function requireRevision(value) {
    const revision = requireText(value, "Task revision").toLowerCase();
    if (!REVISION_PATTERN.test(revision))
        throw new TypeError("Task revision must be exactly 64 lowercase hexadecimal characters.");
    return revision;
}
function requireIdentity(value, label) {
    const identity = requireText(value, label);
    if (!IDENTITY_PATTERN.test(identity))
        throw new TypeError(`${label} is invalid.`);
    return identity;
}
function requireText(value, label) {
    if (typeof value !== "string" || value.trim() === "")
        throw new TypeError(`${label} is required.`);
    return value.trim();
}
function assertTaskRevision(repositoryRoot, taskPath, revision) {
    const text = node_fs_1.default.readFileSync(node_path_1.default.join(repositoryRoot, taskPath), "utf8");
    if (sha256(text) !== revision)
        throw new TypeError("Task revision became stale before the next declared command; no later command was executed.");
}
function boundedName(argv) {
    const name = argv.slice(0, 3).join(" ");
    return name.length <= 120 ? name : `${name.slice(0, 117)}...`;
}
function sameArgv(left, right) {
    return left.length === right.length && left.every((entry, index) => entry === right[index]);
}
function uniquePreconditions(values) {
    const seen = new Set();
    return values.filter((value) => {
        const identity = `${value.path}\u0000${value.kind}\u0000${value.expectedState}`;
        if (seen.has(identity))
            return false;
        seen.add(identity);
        return true;
    });
}
function cloneRequest(request) {
    return { ...request };
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
}
function sha256(value) {
    return (0, node_crypto_1.createHash)("sha256").update(value).digest("hex");
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
