/**
 * Command Execution Contract
 *
 * 将命令生成、dry-run 和真实执行分离，对命令来源、风险等级、
 * cwd/env、确认要求、timeout、幂等性、长进程、输出证据、
 * 脱敏和禁止模式统一建模。
 *
 * 核心原则:
 * - 生成命令不等于执行命令
 * - sf_verify 默认 plan_only
 * - destructive/database/external_side_effect 必须人工确认
 * - forbidden pattern 必须 hard block
 */
import { redactSensitiveText } from "../audit/privacy_secret_contract.js";
import { debug } from "../core/logger.js";
// ── 按风险等级的默认确认要求 ──
const RISK_CONFIRMATION = {
    read_only: "auto_allowed",
    local_write: "workflow_allowed",
    dependency_write: "require_human",
    long_running: "require_human",
    network: "require_human",
    database: "require_human",
    destructive: "hard_block",
    external_side_effect: "require_human",
};
// ── 按用途的默认幂等性 ──
const PURPOSE_IDEMPOTENCY = {
    verify: "idempotent",
    build: "idempotent",
    test: "idempotent",
    lint: "idempotent",
    install: "non_idempotent",
    generate: "non_idempotent",
    migrate: "non_idempotent",
    dev_server: "non_idempotent",
    deploy: "non_idempotent",
    cleanup: "non_idempotent",
    diagnostic: "idempotent",
};
// ── 按用途的默认超时 ──
const PURPOSE_TIMEOUT = {
    verify: 60_000,
    build: 120_000,
    test: 120_000,
    lint: 60_000,
    install: 180_000,
    generate: 60_000,
    migrate: 120_000,
    dev_server: 30_000,
    deploy: 180_000,
    cleanup: 60_000,
    diagnostic: 30_000,
};
// ── 禁止执行的命令模式 (hard block) ──
/** 禁止执行的命令模式列表（hard block） */
export const FORBIDDEN_COMMAND_PATTERNS = [
    { pattern: /\brm\s+(-\w*r\w*f|\S+)*\s+\/\s*$/, reason: "rm -rf / 禁止执行" },
    { pattern: /\brm\s+(-\w*r\w*f|\S+)*\s+\.\s*$/, reason: "rm -rf . 禁止执行" },
    { pattern: /\bsudo\b/, reason: "sudo 命令禁止执行" },
    { pattern: /\bchmod\s+-R\s+777\b/, reason: "chmod -R 777 禁止执行" },
    { pattern: /\bcurl\b.*\|\s*sh\b/, reason: "curl | sh 模式禁止执行" },
    { pattern: /\bwget\b.*\|\s*sh\b/, reason: "wget | sh 模式禁止执行" },
    { pattern: /\bdocker\s+system\s+prune\s+(-a|--all)/, reason: "docker system prune -a 禁止执行" },
    { pattern: /\bdocker\s+compose\s+down\s+-v/, reason: "docker compose down -v 会删除 volumes" },
    { pattern: /\bkubectl\s+delete\b/, reason: "kubectl delete 禁止执行" },
    { pattern: /\bterraform\s+destroy\b/, reason: "terraform destroy 禁止执行" },
];
// ── 风险分类器 ──
const RISK_PATTERNS = [
    // 破坏性模式（最高优先级）
    { pattern: /\brm\s+(-\w*r\w*f|\S+)*\s+/, risk: "destructive", priority: 100 },
    { pattern: /\bdd\s+if=/, risk: "destructive", priority: 100 },
    { pattern: /(?:^|\||&&|;)\s*format\b/, risk: "destructive", priority: 100 },
    { pattern: /\btruncate\b/, risk: "destructive", priority: 90 },
    // 数据库
    { pattern: /\bprisma\s+migrate\b/, risk: "database", priority: 80 },
    { pattern: /\bflyway\b/, risk: "database", priority: 80 },
    { pattern: /\bliquibase\b/, risk: "database", priority: 80 },
    { pattern: /\bpsql\b/, risk: "database", priority: 80 },
    { pattern: /\bmysql\b/, risk: "database", priority: 80 },
    // 外部副作用
    { pattern: /\bkubectl\s+apply\b/, risk: "external_side_effect", priority: 70 },
    { pattern: /\bterraform\s+apply\b/, risk: "external_side_effect", priority: 70 },
    { pattern: /\bgh\s+(pr\s+create|issue\s+create)\b/, risk: "external_side_effect", priority: 70 },
    { pattern: /\bgit\s+push\b/, risk: "external_side_effect", priority: 70 },
    { pattern: /\bnpm\s+publish\b/, risk: "external_side_effect", priority: 70 },
    { pattern: /\bdocker\s+push\b/, risk: "external_side_effect", priority: 70 },
    // 长进程
    { pattern: /(?:^|\||&&|;)\s*dev\b/, risk: "long_running", priority: 60 },
    { pattern: /\bwatch\b/, risk: "long_running", priority: 60 },
    { pattern: /\bserve\b/, risk: "long_running", priority: 60 },
    { pattern: /(?:^|\||&&|;)\s*start\b/, risk: "long_running", priority: 50 },
    // 依赖写入 + 网络
    { pattern: /\b(npm|pnpm|yarn)\s+install\b/, risk: "dependency_write", priority: 40 },
    { pattern: /\b(npm|pnpm|yarn)\s+add\b/, risk: "dependency_write", priority: 40 },
    { pattern: /\bpip\s+install\b/, risk: "dependency_write", priority: 40 },
    { pattern: /\bcargo\s+install\b/, risk: "dependency_write", priority: 40 },
    { pattern: /\bgo\s+get\b/, risk: "dependency_write", priority: 40 },
    // 网络
    { pattern: /\bcurl\b/, risk: "network", priority: 30 },
    { pattern: /\bwget\b/, risk: "network", priority: 30 },
    // 本地写入
    { pattern: /\b(npm|pnpm|yarn)\s+run\s+build\b/, risk: "local_write", priority: 20 },
    { pattern: /\btsc\b/, risk: "local_write", priority: 20 },
    { pattern: /\bvite\s+build\b/, risk: "local_write", priority: 20 },
    { pattern: /\bmvn\s+(compile|package|verify)\b/, risk: "local_write", priority: 20 },
    { pattern: /\bgradle\s+(build|compileJava)\b/, risk: "local_write", priority: 20 },
    { pattern: /\bgo\s+build\b/, risk: "local_write", priority: 20 },
    { pattern: /\bcargo\s+build\b/, risk: "local_write", priority: 20 },
    { pattern: /\bmkdir\b/, risk: "local_write", priority: 15 },
    { pattern: /\bcp\b/, risk: "local_write", priority: 10 },
    { pattern: /\bmv\b/, risk: "local_write", priority: 10 },
    // 只读（最低优先级）
    { pattern: /\b(cat|ls|head|tail|grep|find|wc|sort|uniq|awk|sed)\b/, risk: "read_only", priority: 1 },
    { pattern: /\bgit\s+(status|diff|log|show|branch)\b/, risk: "read_only", priority: 1 },
    { pattern: /\b(npm|pnpm|yarn)\s+(test|run\s+test)\b/, risk: "read_only", priority: 5 },
    { pattern: /\bmvn\s+test\b/, risk: "read_only", priority: 5 },
    { pattern: /\bgo\s+test\b/, risk: "read_only", priority: 5 },
    { pattern: /\bvitest\b/, risk: "read_only", priority: 5 },
    { pattern: /\bjest\b/, risk: "read_only", priority: 5 },
];
/**
 * 对命令进行风险分类。
 * 基于命令内容匹配风险模式，返回最高优先级的风险等级。
 * @param command - 命令字符串
 * @returns 风险等级
 */
export function classifyCommandRisk(command) {
    debug("命令契约", `分类命令风险 — ${command}`);
    let bestRisk = "read_only";
    let bestPriority = 0;
    for (const { pattern, risk, priority } of RISK_PATTERNS) {
        if (pattern.test(command) && priority > bestPriority) {
            bestRisk = risk;
            bestPriority = priority;
        }
    }
    debug("命令契约", `风险分类结果 — ${bestRisk}`);
    return bestRisk;
}
// ── 查询函数 ──
/**
 * 获取指定风险等级的默认确认要求。
 * @param risk - 风险等级
 * @returns 确认要求
 */
export function getDefaultConfirmation(risk) {
    return RISK_CONFIRMATION[risk];
}
/**
 * 获取指定用途的默认幂等性。
 * @param purpose - 命令用途
 * @returns 幂等性
 */
export function getDefaultIdempotency(purpose) {
    return PURPOSE_IDEMPOTENCY[purpose];
}
/**
 * 获取指定用途的默认超时时间（毫秒）。
 * @param purpose - 命令用途
 * @returns 超时时间（毫秒）
 */
export function getDefaultTimeout(purpose) {
    return PURPOSE_TIMEOUT[purpose];
}
/**
 * 判断命令是否为禁止执行的命令。
 * @param command - 命令字符串
 * @returns 禁止结果及原因
 */
export function isForbiddenCommand(command) {
    for (const { pattern, reason } of FORBIDDEN_COMMAND_PATTERNS) {
        if (pattern.test(command)) {
            debug("命令契约", `检测到禁止命令 — ${command}，原因: ${reason}`);
            return { forbidden: true, reason };
        }
    }
    return { forbidden: false, reason: "" };
}
// ── 命令契约工厂 ──
/**
 * 创建命令执行契约。
 * 自动分类风险等级、确认要求和幂等性。
 * @param options.command - 命令字符串
 * @param options.cwd - 工作目录
 * @param options.source - 命令来源
 * @param options.purpose - 命令用途
 * @param options.mode - 执行模式（可选，默认 plan_only）
 * @param options.shell - shell 类型（可选）
 * @param options.timeout_ms - 超时时间（可选）
 * @param options.allowed_env - 允许的环境变量（可选）
 * @returns 命令执行契约实例
 */
export function createCommandContract(options) {
    debug("命令契约", `创建命令契约 — ${options.command}`);
    const risk = classifyCommandRisk(options.command);
    const confirmation = RISK_CONFIRMATION[risk];
    const idempotency = PURPOSE_IDEMPOTENCY[options.purpose];
    return {
        command_id: `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        command: options.command,
        mode: options.mode ?? "plan_only",
        cwd: options.cwd,
        shell: options.shell,
        source: options.source,
        purpose: options.purpose,
        risk,
        idempotency,
        requires_confirmation: confirmation,
        timeout_ms: options.timeout_ms ?? PURPOSE_TIMEOUT[options.purpose],
        allowed_env: options.allowed_env ?? [],
        expected_outputs: [],
        forbidden_patterns: [],
    };
}
// ── 执行证据工厂 ──
/**
 * 创建命令执行证据。
 * 自动对敏感内容进行脱敏处理。
 * @param options.command_id - 命令 ID
 * @param options.cwd - 工作目录
 * @param options.exit_code - 退出码
 * @param options.timed_out - 是否超时
 * @param options.duration_ms - 执行时长（毫秒）
 * @param options.stdout_ref - stdout 文件引用（可选）
 * @param options.stderr_ref - stderr 文件引用（可选）
 * @param options.stdout_content - 实际 stdout 内容（可选）
 * @param options.stderr_content - 实际 stderr 内容（可选）
 * @param options.changed_files - 变更文件列表（可选）
 * @param options.summary - 执行摘要
 * @returns 命令执行证据实例（含自动生成的 executed_at 时间戳）
 */
export function createExecutionEvidence(options) {
    const redactionRecords = [];
    // 摘要必须始终脱敏
    let summary = options.summary;
    const summaryRedacted = redactSensitiveText(summary, `summary:${options.command_id}`);
    if (summaryRedacted.records.length > 0) {
        summary = summaryRedacted.redacted;
        for (const r of summaryRedacted.records) {
            redactionRecords.push(`summary:${r.fields_redacted.join(",")}:${r.redaction_type}`);
        }
    }
    // 脱敏实际 stdout 内容（非文件引用）
    if (options.stdout_content) {
        const stdoutRedacted = redactSensitiveText(options.stdout_content, `stdout:${options.command_id}`);
        if (stdoutRedacted.records.length > 0) {
            for (const r of stdoutRedacted.records) {
                redactionRecords.push(`stdout:${r.fields_redacted.join(",")}:${r.redaction_type}`);
            }
        }
    }
    // 脱敏实际 stderr 内容（非文件引用）
    if (options.stderr_content) {
        const stderrRedacted = redactSensitiveText(options.stderr_content, `stderr:${options.command_id}`);
        if (stderrRedacted.records.length > 0) {
            for (const r of stderrRedacted.records) {
                redactionRecords.push(`stderr:${r.fields_redacted.join(",")}:${r.redaction_type}`);
            }
        }
    }
    return {
        command_id: options.command_id,
        executed_at: new Date().toISOString(),
        cwd: options.cwd,
        exit_code: options.exit_code,
        timed_out: options.timed_out,
        duration_ms: options.duration_ms,
        stdout_ref: options.stdout_ref,
        stderr_ref: options.stderr_ref,
        redaction_records: redactionRecords,
        changed_files: options.changed_files ?? [],
        summary,
    };
}
// ── 执行门禁 ──
/**
 * 判断命令是否可以执行。
 * 检查禁止模式、执行模式、风险等级和确认要求。
 * @param contract - 命令执行契约
 * @returns 执行许可结果
 */
export function canExecute(contract) {
    // 检查禁止模式
    const forbidden = isForbiddenCommand(contract.command);
    if (forbidden.forbidden) {
        debug("命令契约", `命令被禁止 — ${forbidden.reason}`);
        return { allowed: false, reason: `命令被禁止: ${forbidden.reason}` };
    }
    // plan_only 模式不执行命令
    if (contract.mode === "plan_only") {
        return { allowed: false, reason: "plan_only 模式不执行命令" };
    }
    // 硬阻断风险
    if (contract.requires_confirmation === "hard_block") {
        return { allowed: false, reason: `风险等级 ${contract.risk} 需要 hard block` };
    }
    // heuristic 来源的高风险命令不得直接执行
    if (contract.source === "heuristic" &&
        (contract.risk === "destructive" || contract.risk === "database" || contract.risk === "external_side_effect")) {
        return { allowed: false, reason: "heuristic 来源的高风险命令不得直接执行" };
    }
    // require_human 需要人工确认
    if (contract.requires_confirmation === "require_human") {
        return { allowed: false, reason: `风险等级 ${contract.risk} 需要人工确认` };
    }
    // cwd 必须明确声明
    if (!contract.cwd || contract.cwd.trim() === "") {
        return { allowed: false, reason: "cwd 必须明确声明" };
    }
    return { allowed: true, reason: "" };
}
// ── 重试检查 ──
/**
 * 判断命令是否可以自动重试。
 * 仅幂等命令失败后才允许自动重试。
 * @param contract - 命令执行契约
 * @param evidence - 命令执行证据
 * @returns 重试许可结果
 */
export function canAutoRetry(contract, evidence) {
    debug("命令契约", `检查自动重试 — 命令ID: ${contract.command_id}，退出码: ${evidence.exit_code}`);
    if (evidence.exit_code === 0) {
        return { allowed: false, reason: "命令已成功，无需重试" };
    }
    if (contract.idempotency === "non_idempotent") {
        return { allowed: false, reason: "非幂等命令失败后不得自动重试" };
    }
    if (contract.idempotency === "unknown") {
        return { allowed: false, reason: "未知幂等性命令重试需要人工确认" };
    }
    // 幂等命令可自动重试
    return { allowed: true, reason: "幂等命令可自动重试" };
}
// ── 长进程检查 ──
/**
 * 判断用途是否为长进程。
 * @param purpose - 命令用途
 * @returns 是否为长进程
 */
export function isLongRunningPurpose(purpose) {
    return purpose === "dev_server";
}
/**
 * 判断风险等级是否为长进程。
 * @param risk - 风险等级
 * @returns 是否为长进程
 */
export function isLongRunningRisk(risk) {
    return risk === "long_running";
}
/**
 * 验证长进程命令的配置是否合理。
 * @param contract - 命令执行契约
 * @returns 验证结果和问题列表
 */
export function validateLongRunning(contract) {
    const issues = [];
    if (contract.purpose === "dev_server" || contract.risk === "long_running") {
        if (contract.timeout_ms <= 0 || contract.timeout_ms > 300_000) {
            issues.push("长进程必须有合理 timeout (最大 5 分钟)");
        }
    }
    return { valid: issues.length === 0, issues };
}
// ── 工作目录验证 ──
/**
 * 判断工作目录是否在项目根目录范围内。
 * @param cwd - 工作目录
 * @param projectRoot - 项目根目录
 * @returns 是否允许
 */
export function isCwdAllowed(cwd, projectRoot) {
    debug("命令契约", `检查工作目录 — cwd: ${cwd}，项目根: ${projectRoot}`);
    if (!cwd) {
        debug("命令契约", "工作目录为空，拒绝");
        return false;
    }
    const normalized = cwd.replace(/\/+$/, "");
    const normalizedRoot = projectRoot.replace(/\/+$/, "");
    const allowed = normalized === normalizedRoot || normalized.startsWith(normalizedRoot + "/");
    if (!allowed) {
        debug("命令契约", `工作目录超出项目范围 — ${normalized} 不在 ${normalizedRoot} 内`);
    }
    return allowed;
}
// ── 中文反馈 ──
/**
 * 构建命令执行反馈信息。
 * @param contract - 命令执行契约
 * @returns 格式化的反馈文本
 */
export function buildCommandFeedback(contract) {
    debug("命令契约", `构建命令反馈 — 命令ID: ${contract.command_id}`);
    const lines = [];
    lines.push(`命令: ${contract.command}`);
    lines.push(`模式: ${modeLabel(contract.mode)}`);
    lines.push(`风险等级: ${riskLabel(contract.risk)}`);
    lines.push(`确认要求: ${confirmationLabel(contract.requires_confirmation)}`);
    lines.push(`来源: ${sourceLabel(contract.source)}`);
    lines.push(`目的: ${contract.purpose}`);
    lines.push(`工作目录: ${contract.cwd}`);
    lines.push(`超时: ${contract.timeout_ms}ms`);
    lines.push(`幂等性: ${contract.idempotency}`);
    if (contract.mode === "execute") {
        lines.push(`⚠️ 此命令将被真实执行`);
    }
    else if (contract.mode === "plan_only") {
        lines.push(`ℹ️ 此命令仅为计划，不会执行`);
    }
    const forbidden = isForbiddenCommand(contract.command);
    if (forbidden.forbidden) {
        lines.push(`🚫 ${forbidden.reason}`);
    }
    return lines.join("\n");
}
function modeLabel(mode) {
    const labels = {
        plan_only: "仅计划",
        dry_run: "模拟执行",
        execute: "真实执行",
    };
    return labels[mode];
}
function riskLabel(risk) {
    const labels = {
        read_only: "只读",
        local_write: "本地写入",
        dependency_write: "依赖写入",
        long_running: "长进程",
        network: "网络访问",
        database: "数据库操作",
        destructive: "破坏性",
        external_side_effect: "外部副作用",
    };
    return labels[risk];
}
function confirmationLabel(c) {
    const labels = {
        auto_allowed: "自动允许",
        workflow_allowed: "工作流允许",
        require_human: "需要人工确认",
        hard_block: "强制阻止",
    };
    return labels[c];
}
function sourceLabel(s) {
    const labels = {
        project_script: "项目脚本",
        project_config: "项目配置",
        user_provided: "用户提供",
        soloforge_generated: "SoloForge 生成",
        knowledge_template: "知识模板",
        heuristic: "启发式推断",
    };
    return labels[s];
}
// ── 治理验证 ──
/**
 * 验证命令执行合规性。
 * 检查执行模式、风险确认、证据完整性等治理规则。
 * @param options.contracts - 命令执行契约列表
 * @param options.evidences - 命令执行证据列表
 * @returns 治理发现列表
 */
export function validateCommandExecution(options) {
    debug("命令契约", `开始治理验证 — ${options.contracts.length} 个契约，${options.evidences.length} 个证据`);
    const findings = [];
    for (const c of options.contracts) {
        // 规则 1: sf_verify 默认 plan_only
        if (c.purpose === "verify" && c.mode === "execute" && c.source === "soloforge_generated") {
            findings.push({
                severity: "advisory",
                rule: "gc-verify-default-plan",
                command_id: c.command_id,
                message: `sf_verify 命令 ${c.command_id} 默认应为 plan_only，当前为 execute`,
            });
        }
        // 规则 2: plan_only 不能当成 evidence
        if (c.mode === "plan_only") {
            const hasEvidence = options.evidences.some((e) => e.command_id === c.command_id);
            if (hasEvidence) {
                findings.push({
                    severity: "hard_fail",
                    rule: "gc-plan-not-evidence",
                    command_id: c.command_id,
                    message: `plan_only 命令 ${c.command_id} 不应产生执行证据`,
                });
            }
        }
        // 规则 3: 高风险命令绕过确认
        if (c.mode === "execute" && c.requires_confirmation === "require_human") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-high-risk-no-confirm",
                command_id: c.command_id,
                message: `高风险命令 ${c.command_id} (${c.risk}) 需要人工确认才能执行`,
            });
        }
        // 规则 4: heuristic 高风险命令直接执行
        if (c.source === "heuristic" && c.mode === "execute" &&
            (c.risk === "destructive" || c.risk === "database" || c.risk === "external_side_effect")) {
            findings.push({
                severity: "hard_fail",
                rule: "gc-heuristic-high-risk",
                command_id: c.command_id,
                message: `heuristic 来源的高风险命令 ${c.command_id} (${c.risk}) 不得直接执行`,
            });
        }
        // 规则 5: cwd 未声明
        if (!c.cwd || c.cwd.trim() === "") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-cwd-required",
                command_id: c.command_id,
                message: `命令 ${c.command_id} 必须声明 cwd`,
            });
        }
        // 规则 6: long_running 被标记 passed
        if (c.risk === "long_running" || c.purpose === "dev_server") {
            const evidence = options.evidences.find((e) => e.command_id === c.command_id);
            if (evidence && evidence.exit_code === 0 && !evidence.timed_out) {
                findings.push({
                    severity: "advisory",
                    rule: "gc-long-running-passed",
                    command_id: c.command_id,
                    message: `长进程命令 ${c.command_id} 不应被标记为 passed — 启动成功不等于测试通过`,
                });
            }
        }
        // 规则 7: non_idempotent 自动 retry
        // （在 canAutoRetry 层检查）
        // 规则 8: 禁止模式
        const forbidden = isForbiddenCommand(c.command);
        if (forbidden.forbidden && c.mode === "execute") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-forbidden-pattern",
                command_id: c.command_id,
                message: `禁止命令 ${c.command_id}: ${forbidden.reason}`,
            });
        }
    }
    // 规则 9: 每次执行必须有 evidence
    for (const c of options.contracts) {
        if (c.mode === "execute") {
            const hasEvidence = options.evidences.some((e) => e.command_id === c.command_id);
            if (!hasEvidence) {
                findings.push({
                    severity: "hard_fail",
                    rule: "gc-evidence-required",
                    command_id: c.command_id,
                    message: `execute 命令 ${c.command_id} 缺少 CommandExecutionEvidence`,
                });
            }
        }
    }
    debug("命令契约", `治理验证完成 — 发现 ${findings.length} 个问题`);
    return findings;
}
//# sourceMappingURL=command_execution_contract.js.map