/**
 * 隐私/机密/数据主权合约 — 扫描与评估
 *
 * 包含敏感信息检测、脱敏处理、外发检查、prompt 注入检查、
 * 治理验证和统一隐私门禁评估等功能。
 */
import crypto from "node:crypto";
import { debugLog } from "../core/logger.js";
import { DEFAULT_HANDLING, CONTEXT_HANDLING, FORBIDDEN_READ_PATTERNS, CONFIRMATION_READ_PATTERNS, TEXT_SECRET_PATTERNS, } from "./privacy_patterns.js";
import { isGrantValid, isGrantExpired, checkGrantPermission } from "./privacy_grants.js";
// ── 检测辅助函数 ──
/**
 * 检测来源的敏感等级。
 * @param sourceRef - 来源引用路径
 * @param contentHints - 内容提示关键词（可选）
 * @returns 数据敏感标签
 */
export function detectSensitivity(sourceRef, contentHints) {
    debugLog(`隐私契约: 检测敏感等级 — ${sourceRef}`);
    // 优先检查禁止模式
    for (const { pattern, label } of FORBIDDEN_READ_PATTERNS) {
        if (pattern.test(sourceRef)) {
            return {
                label,
                source_ref: sourceRef,
                detected_by: [`pattern: ${pattern.source}`],
                confidence: 0.9,
                handling: "forbidden",
            };
        }
    }
    // 检查确认模式
    for (const { pattern, label } of CONFIRMATION_READ_PATTERNS) {
        if (pattern.test(sourceRef)) {
            return {
                label,
                source_ref: sourceRef,
                detected_by: [`pattern: ${pattern.source}`],
                confidence: 0.7,
                handling: "require_human",
            };
        }
    }
    // 基于内容的检测
    if (contentHints && contentHints.length > 0) {
        const joined = contentHints.join(" ");
        if (/token|api[_-]?key|secret[_-]?key|private[_-]?key/i.test(joined)) {
            return {
                label: "secret",
                source_ref: sourceRef,
                detected_by: ["content_keyword"],
                confidence: 0.6,
                handling: "forbidden",
            };
        }
        if (/\bpassword\b|\bpasswd\b/i.test(joined)) {
            return {
                label: "credential",
                source_ref: sourceRef,
                detected_by: ["content_keyword"],
                confidence: 0.6,
                handling: "forbidden",
            };
        }
        if (/\b\d{11}\b/.test(joined) || /\b[\w.+-]+@[\w-]+\.[\w.]+\b/.test(joined)) {
            return {
                label: "pii",
                source_ref: sourceRef,
                detected_by: ["content_pattern"],
                confidence: 0.5,
                handling: "require_human",
            };
        }
    }
    // 常规代码文件 (.ts, .js, .py, .go, .rs, .java, .tsx, .jsx) 默认为 internal
    const codeExtensions = /\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|rb|php|swift|kt)$/i;
    if (codeExtensions.test(sourceRef)) {
        return {
            label: "internal",
            source_ref: sourceRef,
            detected_by: ["file_extension"],
            confidence: 0.7,
            handling: "allow",
        };
    }
    return {
        label: "unknown",
        source_ref: sourceRef,
        detected_by: ["no_match"],
        confidence: 0.3,
        handling: DEFAULT_HANDLING["unknown"],
    };
}
// ── 脱敏处理 ──
/**
 * 对内容进行脱敏处理。
 * @param content - 原始内容
 * @param label - 敏感标签
 * @param sourceRef - 来源引用（可选）
 * @returns 脱敏结果和脱敏记录
 */
export function redactContent(content, label, sourceRef) {
    debugLog(`隐私契约: 开始内容脱敏 — 等级: ${label}, 来源: ${sourceRef ?? "未知"}`);
    let redacted = content;
    const fieldsRedacted = [];
    if (label === "secret" || label === "credential") {
        redacted = "***REDACTED***";
        fieldsRedacted.push("all");
    }
    else if (label === "pii") {
        redacted = redactPII(content);
        fieldsRedacted.push("phone", "email", "id_number", "address");
    }
    else if (label === "customer_data" || label === "production_data") {
        redacted = summarizeOnly(content);
        fieldsRedacted.push("raw_data");
    }
    else if (label === "confidential") {
        redacted = summarizeOnly(content);
        fieldsRedacted.push("details");
    }
    return {
        redacted,
        record: {
            source_ref: sourceRef ?? "",
            redaction_type: labelToRedactionType(label),
            after_hash: simpleHash(redacted),
            fields_redacted: fieldsRedacted,
        },
    };
}
function redactPII(text) {
    let result = text;
    // 手机号: 138****1234
    result = result.replace(/1[3-9]\d(\d{4})\d{4}/g, (m, last) => m.slice(0, 3) + "****" + last);
    // 邮箱: a***@domain.com
    result = result.replace(/([\w.+-])[\w.+-]*@([\w-]+\.[\w.]+)/g, (_, first, domain) => first + "***@" + domain);
    // 身份证号: 保留后 4 位
    result = result.replace(/\b\d{14}(\d{4})\b/g, "**************$1");
    // Address: keep city-level (省/市 + following district, redact detail)
    result = result.replace(/([一-龥]{2,6}(?:省|市|自治区|特别行政区))([一-龥]{2,6}(?:市|区|县|镇))([一-龥\d]+路?[一-龥\d]+)/g, "$1$2***");
    return result;
}
function summarizeOnly(text) {
    if (text.length <= 100)
        return `[摘要] ${text.slice(0, 50)}...`;
    return `[摘要] ${text.slice(0, 80)}... (共 ${text.length} 字符)`;
}
function labelToRedactionType(label) {
    const map = {
        secret: "secret_mask",
        credential: "credential_removed",
        pii: "pii_mask",
        customer_data: "customer_data_summary",
        production_data: "production_data_summary",
        confidential: "path_only",
    };
    return map[label] ?? "hash_only";
}
function simpleHash(text) {
    return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
}
// ── 外发检查 ──
/**
 * 检查内容是否可以外部发送。
 * @param content - 待发送内容
 * @param sensitivity - 敏感标签
 * @param grants - 授权列表
 * @returns 外发检查结果
 */
export function checkExternalSend(content, sensitivity, grants) {
    debugLog(`隐私契约: 检查外发许可 — 等级: ${sensitivity.label}`);
    const handling = CONTEXT_HANDLING[sensitivity.label].external_send;
    if (handling === "forbidden") {
        debugLog(`隐私契约: 外发拒绝 — 等级 ${sensitivity.label} 禁止外发`);
        return { allowed: false, reason: `敏感等级 ${sensitivity.label} 禁止外发`, requiresRedaction: false };
    }
    if (handling === "allow") {
        return { allowed: true, reason: "", requiresRedaction: false };
    }
    // require_human — 检查授权
    for (const grant of grants) {
        const perm = checkGrantPermission(grant, "external_send", sensitivity.label);
        if (perm.allowed) {
            debugLog(`隐私契约: 外发通过 — 授权 ${grant.grant_id} 允许（已脱敏）`);
            const { redacted } = redactContent(content, sensitivity.label);
            return { allowed: true, reason: `授权 ${grant.grant_id} 允许外发（已脱敏）`, requiresRedaction: true, redactedContent: redacted };
        }
    }
    debugLog(`隐私契约: 外发拒绝 — 等级 ${sensitivity.label} 需要人工授权`);
    return {
        allowed: false,
        reason: `敏感等级 ${sensitivity.label} 需要人工授权才能外发`,
        requiresRedaction: true,
    };
}
// ── Prompt 注入检查 ──
/**
 * 检查数据是否可以注入 prompt。
 * @param sensitivity - 敏感标签
 * @param grants - 授权列表
 * @returns 注入检查结果
 */
export function checkPromptInjection(sensitivity, grants) {
    debugLog(`隐私契约: 检查 prompt 注入许可 — 等级: ${sensitivity.label}`);
    const handling = CONTEXT_HANDLING[sensitivity.label].prompt_injection;
    if (handling === "forbidden") {
        debugLog(`隐私契约: 注入拒绝 — 等级 ${sensitivity.label} 禁止注入 prompt`);
        return { allowed: false, reason: `敏感等级 ${sensitivity.label} 禁止注入 prompt`, handling };
    }
    if (handling === "allow") {
        return { allowed: true, reason: "", handling };
    }
    // 检查 prompt_inject 授权
    for (const grant of grants) {
        const perm = checkGrantPermission(grant, "prompt_inject", sensitivity.label);
        if (perm.allowed) {
            return { allowed: true, reason: `授权 ${grant.grant_id} 允许注入（${handling}）`, handling };
        }
    }
    debugLog(`隐私契约: 注入拒绝 — 等级 ${sensitivity.label} 需要 ${handling} 处理`);
    return { allowed: false, reason: `敏感等级 ${sensitivity.label} 需要 ${handling} 处理`, handling };
}
// ── 中文反馈 ──
/**
 * 构建隐私处理反馈信息。
 * @param options - 反馈选项
 * @returns 格式化的反馈文本
 */
export function buildPrivacyFeedback(options) {
    debugLog(`隐私契约: 构建隐私反馈 — 未读: ${options.notRead.length}, 脱敏: ${options.redacted.length}, 需授权: ${options.requiresAuth.length}`);
    const lines = [];
    if (options.notRead.length > 0) {
        lines.push(`未读取的内容: ${options.notRead.join(", ")}`);
    }
    if (options.redacted.length > 0) {
        lines.push(`已脱敏的内容: ${options.redacted.join(", ")}`);
    }
    if (options.requiresAuth.length > 0) {
        lines.push(`需要授权的内容: ${options.requiresAuth.join(", ")}`);
    }
    if (options.notInPrompt.length > 0) {
        lines.push(`未进入 prompt 的内容: ${options.notInPrompt.join(", ")}`);
    }
    if (options.blocked) {
        lines.push(`⚠️ 存在敏感信息阻断`);
    }
    if (options.externalRedaction) {
        lines.push(`外发前已完成 redaction check`);
    }
    return lines.join("\n");
}
// ── 治理验证 ──
/**
 * 验证隐私契约合规性。
 * @param options.sensitivities - 敏感标签列表
 * @param options.grants - 授权列表
 * @param options.redactionRecords - 脱敏记录列表
 * @param options.promptInjections - prompt 注入列表
 * @param options.taskContextStores - 任务上下文存储列表
 * @param options.artifactStores - 产物存储列表
 * @param options.externalSends - 外部发送列表
 * @returns 治理发现列表
 */
export function validatePrivacyContract(options) {
    const findings = [];
    debugLog(`隐私契约: 开始治理验证 — 检查 ${options.sensitivities.length} 个敏感来源`);
    // 规则 1: secret 不得进入 prompt
    for (const inj of options.promptInjections) {
        if (inj.label === "secret" || inj.label === "credential") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-secret-no-prompt",
                source_ref: inj.source_ref,
                message: `secret/credential (${inj.label}) 不得注入 prompt — ${inj.source_ref}`,
            });
        }
    }
    // 规则 2: secret 不得写入 TaskContext
    for (const store of options.taskContextStores) {
        if (store.label === "secret" || store.label === "credential") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-secret-no-taskcontext",
                source_ref: store.source_ref,
                message: `secret/credential (${store.label}) 不得写入 TaskContext — ${store.source_ref}`,
            });
        }
    }
    // 规则 3: secret 不得写入 artifact/report
    for (const art of options.artifactStores) {
        if (art.label === "secret" || art.label === "credential") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-secret-no-artifact",
                source_ref: art.source_ref,
                message: `secret/credential (${art.label}) 不得写入 artifact — ${art.source_ref}`,
            });
        }
    }
    // 规则 4: PII 未脱敏外发
    for (const ext of options.externalSends) {
        if (ext.label === "pii" || ext.label === "customer_data" || ext.label === "production_data") {
            const hasRedaction = options.redactionRecords.some((r) => r.source_ref === ext.source_ref);
            if (!hasRedaction) {
                findings.push({
                    severity: "hard_fail",
                    rule: "gc-pii-no-unredacted-external",
                    source_ref: ext.source_ref,
                    message: `${ext.label} 未脱敏不得外发 — ${ext.source_ref}`,
                });
            }
        }
    }
    // 规则 5: secret 外发 hard fail
    for (const ext of options.externalSends) {
        if (ext.label === "secret" || ext.label === "credential") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-secret-no-external",
                source_ref: ext.source_ref,
                message: `secret/credential (${ext.label}) 外发 hard fail — ${ext.source_ref}`,
            });
        }
    }
    // 规则 6: DataAccessGrant 过期后不得使用
    for (const grant of options.grants) {
        if (isGrantExpired(grant)) {
            findings.push({
                severity: "hard_fail",
                rule: "gc-grant-expired",
                source_ref: grant.grant_id,
                message: `授权 ${grant.grant_id} 已过期，不得继续使用`,
            });
        }
    }
    // 规则 7: unknown sensitivity 不能当 public
    for (const s of options.sensitivities) {
        if (s.label === "unknown" && s.handling === "allow") {
            findings.push({
                severity: "hard_fail",
                rule: "gc-unknown-not-public",
                source_ref: s.source_ref,
                message: `unknown sensitivity 不能当做 public 处理 — ${s.source_ref}`,
            });
        }
    }
    // 规则 8: private evidence 不得注入 prompt
    for (const s of options.sensitivities) {
        if (s.label === "secret" || s.label === "credential") {
            const injected = options.promptInjections.some((i) => i.source_ref === s.source_ref);
            if (injected) {
                findings.push({
                    severity: "hard_fail",
                    rule: "gc-private-no-prompt",
                    source_ref: s.source_ref,
                    message: `private evidence (${s.label}) 不得注入 prompt — ${s.source_ref}`,
                });
            }
        }
    }
    // 规则 9: 每次脱敏必须有 RedactionRecord
    const redactedSources = new Set(options.redactionRecords.map((r) => r.source_ref));
    for (const s of options.sensitivities) {
        if (s.handling === "redact" && !redactedSources.has(s.source_ref)) {
            findings.push({
                severity: "advisory",
                rule: "gc-redaction-record-missing",
                source_ref: s.source_ref,
                message: `敏感来源 ${s.source_ref} 标记为脱敏但缺少 RedactionRecord`,
            });
        }
    }
    return findings;
}
/**
 * 扫描文本中的敏感信息。
 * @param text - 待扫描文本
 * @param sourceRef - 来源引用
 * @returns 检测到的敏感标签列表
 */
export function scanTextSensitivity(text, sourceRef) {
    debugLog(`隐私契约: 扫描文本敏感信息 — 来源: ${sourceRef}`);
    const labels = [];
    for (const { pattern, label, fields } of TEXT_SECRET_PATTERNS) {
        if (pattern.test(text)) {
            labels.push({
                label,
                source_ref: sourceRef,
                detected_by: [`text_pattern:${fields.join(",")}`],
                confidence: 0.85,
                handling: (label === "pii" || label === "production_data" || label === "customer_data") ? "require_human" : "forbidden",
            });
        }
    }
    return labels;
}
/**
 * 扫描来源引用的敏感等级。
 * @param sourceRef - 来源引用路径
 * @returns 数据敏感标签
 */
export function scanSourceRefSensitivity(sourceRef) {
    debugLog(`隐私契约: 扫描来源敏感等级 — ${sourceRef}`);
    return detectSensitivity(sourceRef);
}
/**
 * 对文本中的敏感信息进行脱敏处理。
 * @param text - 原始文本
 * @param sourceRef - 来源引用（可选）
 * @returns 脱敏结果和脱敏记录
 */
export function redactSensitiveText(text, sourceRef) {
    let result = text;
    const records = [];
    const fieldsRedacted = [];
    debugLog(`隐私契约: 开始文本脱敏 — 来源: ${sourceRef ?? '未知'}`);
    // API 密钥 / 令牌
    if (/sk-[a-zA-Z0-9]{32,}/.test(result)) {
        result = result.replace(/sk-[a-zA-Z0-9]{32,}/g, "sk-****REDACTED****");
        fieldsRedacted.push("api_key");
    }
    // AWS 访问密钥
    if (/AKIA[0-9A-Z]{16}/.test(result)) {
        result = result.replace(/AKIA[0-9A-Z]{16}/g, "AKIA****REDACTED****");
        fieldsRedacted.push("aws_access_key");
    }
    // AWS 秘密访问密钥值
    result = result.replace(/(aws_secret_access_key\s*=\s*['"])[^'"]+(['"])/g, "$1****REDACTED****$2");
    // 密码值
    result = result.replace(/(password\s*=\s*['"])[^'"]{4,}(['"])/gi, "$1****REDACTED****$2");
    // 密钥值
    result = result.replace(/(secret_key\s*=\s*['"])[^'"]+(['"])/gi, "$1****REDACTED****$2");
    // JWT 密钥值
    result = result.replace(/(jwt_secret\s*=\s*['"])[^'"]+(['"])/gi, "$1****REDACTED****$2");
    // 个人身份信息
    const piiResult = redactPII(result);
    if (piiResult !== result) {
        result = piiResult;
        fieldsRedacted.push("phone", "email", "id_number", "address");
    }
    if (fieldsRedacted.length > 0) {
        debugLog(`隐私契约: 文本脱敏完成 — 脱敏字段: ${fieldsRedacted.join(", ")}`);
        records.push({
            source_ref: sourceRef ?? "",
            redaction_type: "secret_mask",
            after_hash: simpleHash(result),
            fields_redacted: [...new Set(fieldsRedacted)],
        });
    }
    return { redacted: result, records };
}
/**
 * 评估隐私门禁，决定是否阻断操作。
 * @param options - 门禁评估选项
 * @returns 门禁结果，包含是否通过、阻断源和警告
 */
export function evaluatePrivacyGate(params) {
    const findings = [];
    const blockedSources = [];
    debugLog(`隐私契约: 开始隐私门禁评估`);
    const allLabels = [];
    const redactionRecords = [];
    let hardFail = false;
    // 扫描意图文本
    if (params.intent) {
        const intentLabels = scanTextSensitivity(params.intent, "intent");
        for (const l of intentLabels) {
            allLabels.push(l);
            if (l.label === "secret" || l.label === "credential") {
                hardFail = true;
                blockedSources.push("intent");
                findings.push({ severity: "hard_fail", rule: "gc-intent-secret", source_ref: "intent", message: `用户意图包含 ${l.label}（${l.detected_by.join(", ")}），禁止执行` });
            }
        }
    }
    // 按来源引用扫描输入材料
    if (params.input_materials) {
        for (const m of params.input_materials) {
            const refLabel = scanSourceRefSensitivity(m.path_or_ref);
            allLabels.push(refLabel);
            if (refLabel.handling === "forbidden") {
                hardFail = true;
                blockedSources.push(m.path_or_ref);
                findings.push({ severity: "hard_fail", rule: "gc-material-forbidden", source_ref: m.path_or_ref, message: `输入材料 ${m.path_or_ref} 匹配禁止模式（${refLabel.label}），禁止读取` });
            }
            else if (refLabel.handling === "require_human") {
                const hasGrant = (params.grants ?? []).some(g => isGrantValid(g) && g.sensitivity_allowed.includes(refLabel.label) && g.allowed_operations.includes("read"));
                if (!hasGrant) {
                    blockedSources.push(m.path_or_ref);
                    findings.push({ severity: "advisory", rule: "gc-material-requires-confirmation", source_ref: m.path_or_ref, message: `输入材料 ${m.path_or_ref} 标记为 ${refLabel.label}，需人工确认后读取` });
                }
            }
            // 如果提供了内容也进行扫描
            if (m.content) {
                const contentLabels = scanTextSensitivity(m.content, m.path_or_ref);
                for (const cl of contentLabels) {
                    if (cl.label === "secret" || cl.label === "credential") {
                        hardFail = true;
                        blockedSources.push(m.path_or_ref);
                        findings.push({ severity: "hard_fail", rule: "gc-content-secret", source_ref: m.path_or_ref, message: `输入材料内容包含 ${cl.label}（${cl.detected_by.join(", ")}），禁止执行` });
                    }
                    allLabels.push(cl);
                }
            }
        }
    }
    // 检查 task_context_stores 是否包含敏感内容
    if (params.task_context_stores) {
        for (const s of params.task_context_stores) {
            if (s.content) {
                const labels = scanTextSensitivity(s.content, s.source_ref);
                for (const l of labels) {
                    if (l.label === "secret" || l.label === "credential") {
                        hardFail = true;
                        findings.push({ severity: "hard_fail", rule: "gc-taskcontext-secret", source_ref: s.source_ref, message: `TaskContext 写入内容包含 ${l.label}，禁止保存` });
                    }
                }
            }
        }
    }
    // 检查外发内容
    if (params.external_sends) {
        for (const e of params.external_sends) {
            const labels = e.content ? scanTextSensitivity(e.content, e.source_ref) : [];
            const refLabel = scanSourceRefSensitivity(e.source_ref);
            if (refLabel.label === "secret" || refLabel.label === "credential") {
                hardFail = true;
                findings.push({ severity: "hard_fail", rule: "gc-external-secret", source_ref: e.source_ref, message: `外发来源 ${e.source_ref} 标记为 ${refLabel.label}，禁止外发` });
            }
            for (const l of labels) {
                if (l.label === "secret" || l.label === "credential") {
                    hardFail = true;
                    findings.push({ severity: "hard_fail", rule: "gc-external-content-secret", source_ref: e.source_ref, message: `外发内容包含 ${l.label}，禁止外发` });
                }
            }
        }
    }
    // 如有需要，脱敏意图文本
    let redactedText;
    if (params.intent) {
        const { redacted, records } = redactSensitiveText(params.intent, "intent");
        if (records.length > 0) {
            redactedText = redacted;
            redactionRecords.push(...records);
        }
    }
    if (hardFail) {
        debugLog(`隐私契约: 隐私门禁阻断 — 发现 ${blockedSources.length} 个阻断源`);
    }
    else if (findings.length > 0) {
        debugLog(`隐私契约: 隐私门禁通过 — 但有 ${findings.length} 个警告`);
    }
    else {
        debugLog(`隐私契约: 隐私门禁通过`);
    }
    return {
        allowed: !hardFail,
        hard_fail: hardFail,
        blocked_sources: [...new Set(blockedSources)],
        findings,
        redacted_text: redactedText,
        redaction_records: redactionRecords,
        labels: allLabels,
    };
}
//# sourceMappingURL=privacy_scanning.js.map