/**
 * 失败报告 — 问题十三 (Failure / Recovery Policy)。
 *
 * 核心规则:
 * - 失败必须分类为明确 FailureClass
 * - deterministic 不无限重试
 * - scope_insufficient 不静默扩大
 * - external_side_effect 部分成功必须记录已发生动作
 * - retry 必须有预算、原因和新证据
 * - unknown failure 不标记 completed
 */
import { createDiagnosticEvent } from "./diagnostic_registry.js";
import { createHash } from "node:crypto";
// 内置恢复策略
const BUILTIN_RECOVERY_POLICIES = [
    { policy_id: "rp-deterministic", failure_class: "deterministic", max_retries: 0, action: "escalate",
        requires_new_evidence: true, requires_human_confirmation: true, diagnostic_code: "SF-FAILURE-0001" },
    { policy_id: "rp-fixable", failure_class: "fixable", max_retries: 3, action: "retry",
        requires_new_evidence: true, requires_human_confirmation: false, diagnostic_code: "SF-FAILURE-0001" },
    { policy_id: "rp-scope-insufficient", failure_class: "scope_insufficient", max_retries: 1, action: "require_human",
        requires_new_evidence: true, requires_human_confirmation: true, diagnostic_code: "SF-FAILURE-0001" },
    { policy_id: "rp-external-side-effect", failure_class: "external_side_effect", max_retries: 0, action: "rollback",
        requires_new_evidence: true, requires_human_confirmation: true, diagnostic_code: "SF-FAILURE-0002" },
    { policy_id: "rp-state-inconsistent", failure_class: "state_inconsistent", max_retries: 0, action: "abort",
        requires_new_evidence: true, requires_human_confirmation: true, diagnostic_code: "SF-STATE-0001" },
    { policy_id: "rp-human-required", failure_class: "human_required", max_retries: 0, action: "require_human",
        requires_new_evidence: false, requires_human_confirmation: true, diagnostic_code: "SF-FAILURE-0001" },
    { policy_id: "rp-unknown", failure_class: "unknown", max_retries: 0, action: "escalate",
        requires_new_evidence: true, requires_human_confirmation: true, diagnostic_code: "SF-FAILURE-0001" },
];
/** 按 FailureClass 获取恢复策略 */
export function getRecoveryPolicy(failureClass) {
    return BUILTIN_RECOVERY_POLICIES.find((p) => p.failure_class === failureClass);
}
/** 列出所有恢复策略 */
export function listRecoveryPolicies() {
    return [...BUILTIN_RECOVERY_POLICIES];
}
// 敏感内容模式 — raw output/secret/PII
const SENSITIVE_PATTERNS = [
    /sk-[a-zA-Z0-9]{20,}/, // API key
    /\b\d{11}\b/, // 手机号
    /password\s*[:=]\s*\S+/i, // password=
    /secret\s*[:=]\s*\S+/i, // secret=
    /api_key\s*[:=]\s*\S+/i, // api_key=
    /token\s*[:=]\s*\S+/i, // token=
    /raw_stdout/i, // raw stdout 标记
    /raw_stderr/i, // raw stderr 标记
];
/** 脱敏: 全局替换所有敏感内容，返回 redacted summary */
function redactErrorOutput(raw) {
    let result = raw;
    // 全局替换所有敏感模式（使用 replaceAll 语义，确保多次出现都处理）
    for (const pattern of SENSITIVE_PATTERNS) {
        result = result.replace(new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g"), "[REDACTED]");
    }
    // 截断防止大段输出
    if (result.length > 200) {
        result = result.substring(0, 200) + "...[truncated]";
    }
    return result;
}
/** 稳定内容哈希 */
function stableHash(content) {
    return createHash("sha256").update(content).digest("hex").substring(0, 16);
}
/** 检查是否包含敏感内容 */
export function containsSensitiveContent(content) {
    return SENSITIVE_PATTERNS.some((p) => p.test(content));
}
/** 创建失败报告（含自检: 脱敏后仍含敏感内容时强制二次脱敏） */
export function createFailureReport(taskId, failureClass, errorOutput, retryCount, retryBudget, contractId, externalSideEffects) {
    const policy = getRecoveryPolicy(failureClass);
    const diagCode = policy?.diagnostic_code ?? "SF-FAILURE-0001";
    const diagEvent = createDiagnosticEvent(diagCode, { failure_class: failureClass, retry_count: retryCount }, contractId, taskId);
    const hash = stableHash(errorOutput);
    let redacted = redactErrorOutput(errorOutput);
    // 自检: 脱敏后仍含敏感内容时强制替换为占位符
    if (containsSensitiveContent(redacted)) {
        redacted = "[内容已全部脱敏，原始输出不可展示]";
    }
    return {
        failure_id: `fr-${taskId}-${hash}`,
        task_id: taskId,
        contract_id: contractId,
        failure_class: failureClass,
        redacted_summary: redacted,
        error_output_hash: hash,
        evidence_ref: undefined,
        retry_count: retryCount,
        retry_budget: retryBudget,
        retry_remaining: Math.max(0, retryBudget - retryCount),
        occurred_at: new Date().toISOString(),
        actions_taken: [],
        external_side_effects: externalSideEffects,
        diagnostic_event: diagEvent,
    };
}
/**
 * 判断是否允许重试。
 * - deterministic 不允许
 * - 超预算不允许
 * - human_required 不允许
 */
export function canRetry(failureClass, retryCount, retryBudget) {
    if (failureClass === "deterministic" || failureClass === "human_required" || failureClass === "unknown") {
        return false;
    }
    return retryCount < retryBudget;
}
/**
 * 验证失败处理规则。
 */
export function validateFailureHandling(report) {
    const errors = [];
    if (report.failure_class === "deterministic" && report.retry_count > 0) {
        errors.push({
            error: `deterministic 失败不应重试，但已重试 ${report.retry_count} 次`,
            severity: "hard_fail",
        });
    }
    if (report.failure_class === "external_side_effect" && !report.external_side_effects) {
        errors.push({
            error: "external_side_effect 失败必须记录已发生动作",
            severity: "hard_fail",
        });
    }
    if (report.retry_remaining < 0) {
        errors.push({
            error: `重试次数超过预算: ${report.retry_count}/${report.retry_budget}`,
            severity: "hard_fail",
        });
    }
    // 红线: redacted_summary 不得包含敏感原文
    if (containsSensitiveContent(report.redacted_summary)) {
        errors.push({
            error: "FailureReport redacted_summary 仍包含敏感内容（secret/PII/raw output）",
            severity: "hard_fail",
        });
    }
    return errors;
}
//# sourceMappingURL=failure_report.js.map