import crypto from "node:crypto";
import { isReadForbidden as privacyIsReadForbidden, isReadRequiresConfirmation as privacyIsReadRequiresConfirmation } from "../audit/privacy_secret_contract.js";
import fs from "node:fs";
import { debug } from "../core/logger.js";
import { isPathInsideProject } from "../core/path_scope_utils.js";
// ── 摄取策略：auto_allowed / requires_confirmation / forbidden 路径 ──
const REQUIRES_CONFIRMATION_PATTERNS = [
    { pattern: /\/Downloads\//i, reason: "下载目录需确认" },
    { pattern: /\/Desktop\//i, reason: "桌面目录需确认" },
    { pattern: /\/\.ssh\//, reason: "SSH 目录敏感" },
    { pattern: /\/\.aws\//, reason: "AWS 凭证目录敏感" },
    { pattern: /\/\.config\//, reason: "配置目录敏感" },
    { pattern: /\/\.gnupg\//, reason: "GPG 目录敏感" },
    { pattern: /\.dump$|\.sql\.gz$|\.bak$/i, reason: "数据库 dump 或备份文件" },
    { pattern: /prod|production/i, reason: "生产环境相关" },
    { pattern: /^https?:\/\//i, reason: "外部 URL 未验证" },
    { pattern: /\.exe$|\.bin$|\.sh$|\.bat$/i, reason: "可执行文件" },
    { pattern: /\.fig$|\.sketch$/i, reason: "设计文件需确认" },
];
const FORBIDDEN_PATTERNS = [
    { pattern: /\/\.ssh\/id_/, reason: "SSH 私钥" },
    { pattern: /\.pem$|\.key$|\.p12$|\.pfx$/i, reason: "密钥文件" },
    { pattern: /token|secret|credential|password/i, reason: "凭证文件" },
    { pattern: /\.kube\/config/, reason: "Kubernetes 凭证" },
    { pattern: /\/\.ssh\/authorized_keys/, reason: "SSH 授权密钥" },
    { pattern: /Cookies\.plist|cookies\.sqlite|Login Data/i, reason: "浏览器 cookie/session" },
    { pattern: /\/Library\/Keychains\//, reason: "系统钥匙串" },
    { pattern: /\.gnupg\/private-keys/i, reason: "GPG 私钥" },
];
// ── 信任等级规则 ──
const TRUST_LEVEL_ORDER = {
    authoritative: 5,
    project_context: 4,
    user_provided: 3,
    external_unverified: 2,
    ai_generated: 1,
    derived: 0,
};
/** derived 和 ai_generated 不能单独支撑强结论 */
/**
 * 判断指定信任等级是否可以支撑强结论。
 * @description derived 和 ai_generated 等级不能单独支撑强结论。
 * @param trustLevel - 信任等级
 * @returns 是否可以支撑强结论
 */
export function canSupportStrongConclusion(trustLevel) {
    return TRUST_LEVEL_ORDER[trustLevel] >= TRUST_LEVEL_ORDER["user_provided"];
}
/** 提升信任等级需要 confirmation evidence */
/**
 * 判断是否可以提升信任等级。
 * @param from - 源信任等级
 * @param to - 目标信任等级
 * @param hasConfirmationEvidence - 是否有确认证据
 * @returns 是否可以提升
 */
export function canElevateTrust(from, to, hasConfirmationEvidence) {
    if (TRUST_LEVEL_ORDER[to] <= TRUST_LEVEL_ORDER[from])
        return false;
    if (to === "authoritative" && !hasConfirmationEvidence)
        return false;
    return true;
}
// ── 作用域角色规则 ──
/** input_only 不得自动变成 project_scope 或 write scope */
/**
 * 判断是否可以更改作用域角色。
 * @description input_only 不得自动变为 project_scope 或 output_target。
 * @param from - 源作用域角色
 * @param to - 目标作用域角色
 * @returns 是否允许更改
 */
export function canChangeScopeRole(from, to) {
    if (from === "input_only" && (to === "project_scope" || to === "output_target")) {
        return false;
    }
    return true;
}
/** access_mode 为 read_only 的材料不得写入 */
/**
 * 判断材料是否允许写入。
 * @description access_mode 为 read_only 的材料不得写入。
 * @param material - 输入材料
 * @returns 是否允许写入
 */
export function isWriteAllowed(material) {
    return material.access_mode === "read_write" && material.scope_role === "output_target";
}
// ── 归档解压规则 ──
/** 归档解压隔离基准路径 */
export const ARCHIVE_EXTRACTION_BASE = ".soloforge/tmp/ingest";
/**
 * 生成隔离解压路径。
 * @param materialId - 材料 ID
 * @returns 隔离解压路径
 */
export function getExtractionPath(materialId) {
    return `${ARCHIVE_EXTRACTION_BASE}/${materialId}`;
}
/** archive 不能直接解压到项目根 */
/**
 * 判断解压路径是否被隔离。
 * @description archive 不能直接解压到项目根。
 * @param extractedTo - 实际解压路径
 * @param projectRoot - 项目根目录
 * @returns 解压路径是否在隔离区域内
 */
export function isExtractionPathConfined(extractedTo, projectRoot) {
    if (extractedTo.startsWith(projectRoot) && !extractedTo.includes(ARCHIVE_EXTRACTION_BASE)) {
        return false;
    }
    return extractedTo.includes(ARCHIVE_EXTRACTION_BASE) || extractedTo.includes("/tmp/");
}
// ── 摄取状态分类 ──
/**
 * 对指定路径或引用进行摄取状态分类。
 * @description 优先检查隐私契约，再检查本地禁止/确认模式，默认为 auto_allowed。
 * @param pathOrRef - 路径或引用字符串
 * @returns 摄取状态（auto_allowed / requires_confirmation / forbidden）
 */
export function classifyIngestionStatus(pathOrRef) {
    debug("InputMaterialContract", "分类摄取状态", { pathOrRef });
    // 隐私契约优先 — 基于规范模式进行禁止/确认（）
    if (privacyIsReadForbidden(pathOrRef))
        return "forbidden";
    if (privacyIsReadRequiresConfirmation(pathOrRef))
        return "requires_confirmation";
    // 本地模式作为补充
    for (const { pattern } of FORBIDDEN_PATTERNS) {
        if (pattern.test(pathOrRef))
            return "forbidden";
    }
    for (const { pattern } of REQUIRES_CONFIRMATION_PATTERNS) {
        if (pattern.test(pathOrRef))
            return "requires_confirmation";
    }
    return "auto_allowed";
}
// ── 材料类型检测 ──
/**
 * 根据路径或引用检测材料类型。
 * @param pathOrRef - 路径或引用字符串
 * @returns 检测到的材料类型
 */
export function detectMaterialKind(pathOrRef) {
    if (/^https?:\/\//i.test(pathOrRef))
        return "url";
    if (/\.(zip|tar|tar\.gz|tgz|gz|rar|7z|jar|war)$/i.test(pathOrRef))
        return "archive";
    if (/\.(log|out)$/i.test(pathOrRef))
        return "log";
    if (/\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i.test(pathOrRef))
        return "image";
    if (/\.(fig)$/i.test(pathOrRef))
        return "figma";
    if (/\.(yaml|yml|json)$/i.test(pathOrRef) && /api|openapi|swagger/i.test(pathOrRef))
        return "api_spec";
    if (/\.(sql|ddl)$/i.test(pathOrRef) && /schema|dump/i.test(pathOrRef))
        return "db_schema";
    if (/\.(md|txt|doc|docx|pdf|rst|adoc)$/i.test(pathOrRef))
        return "document";
    if (pathOrRef.endsWith("/") || /\/$/.test(pathOrRef))
        return "directory";
    if (pathOrRef.startsWith("/"))
        return "external_file";
    if (pathOrRef.startsWith("./") || pathOrRef.startsWith("../"))
        return "project_file";
    return "user_text";
}
// ── 摄取警告检测 ──
/**
 * 检测材料的摄取警告。
 * @param material - 输入材料
 * @returns 警告信息列表
 */
export function detectIngestionWarnings(material) {
    const warnings = [];
    if (material.trust_level === "derived") {
        warnings.push("derived 材料不能单独支撑强结论");
    }
    if (material.trust_level === "ai_generated") {
        warnings.push("AI 生成材料不能单独支撑强结论");
    }
    if (material.trust_level === "external_unverified") {
        warnings.push("来源未验证，不得作为已验证事实");
    }
    if (material.scope_role === "input_only" && material.access_mode !== "read_only") {
        warnings.push("input_only 材料应当 read_only");
    }
    if (material.kind === "archive" && material.extracted_to && !material.cleanup_required) {
        warnings.push("archive 解压后应标记 cleanup_required");
    }
    if (material.size_bytes && material.size_bytes > 50 * 1024 * 1024) {
        warnings.push("文件超过 50MB，不得全文注入 prompt");
    }
    return warnings;
}
/** 禁止注入 prompt 的材料类型（全量大文件） */
export const FORBIDDEN_SOURCE_KINDS = ["figma", "image"];
/** 禁止注入 prompt 的来源前缀列表 */
export const FORBIDDEN_SOURCE_PREFIXES = [
    "figma://",
    "figma.site",
    "figma.com",
    "x-callback-url://",
    "browser://",
];
/** 额外禁止注入 prompt 的来源名称列表 */
export const EXTRA_FORBIDDEN_SOURCES = [
    "Figma MCP",
    "browser runtime",
    "figma.site",
    "截图/Design 文件",
    "猜测补充",
];
/**
 * 判断指定来源是否为禁止注入的来源。
 * @description prompt 注入不得包含全量大文件。
 * @param pathOrRef - 路径或引用
 * @param kind - 材料类型
 * @returns 是否为禁止来源
 */
export function isForbiddenSource(pathOrRef, kind) {
    if (FORBIDDEN_SOURCE_KINDS.includes(kind))
        return true;
    const lower = pathOrRef.toLowerCase();
    for (const prefix of FORBIDDEN_SOURCE_PREFIXES) {
        if (lower.includes(prefix.toLowerCase()))
            return true;
    }
    return false;
}
/**
 * 构建 prompt 注入计划。
 * @description 遍历所有材料，分类为允许/禁止来源，生成摘要和确认列表。
 * @param materials - 输入材料列表
 * @param evidences - 可选的摄取证据列表
 * @returns prompt 注入计划
 */
export function buildPromptInjectionPlan(materials, evidences) {
    const plan = {
        required_summary: [],
        evidence_refs: [],
        selected_excerpts: [],
        forbidden_notes: [],
        manual_confirm_list: [],
        allowed_sources: [],
        forbidden_sources: [...EXTRA_FORBIDDEN_SOURCES],
    };
    for (let i = 0; i < materials.length; i++) {
        const m = materials[i];
        const ev = evidences?.[i];
        const hashPart = ev?.hash ? `hash=${ev.hash}` : (ev && !ev.hash ? "hash=missing" : "");
        const statusPart = ev ? `status=${ev.status}` : "";
        plan.evidence_refs.push(`evidence_id=${ev?.evidence_id ?? "n/a"} material_id=${m.id} ${hashPart} ${statusPart}`.trim());
        if (m.access_mode === "forbidden") {
            plan.forbidden_notes.push(`材料 ${m.path_or_ref} 禁止使用`);
            continue;
        }
        if (isForbiddenSource(m.path_or_ref, m.kind)) {
            plan.forbidden_sources.push(m.path_or_ref);
            continue;
        }
        plan.allowed_sources.push(m.path_or_ref);
        if (m.trust_level === "external_unverified") {
            plan.manual_confirm_list.push(`[Manual Confirm] ${m.path_or_ref} 来源未验证`);
        }
        if (m.trust_level === "derived" || m.trust_level === "ai_generated") {
            plan.manual_confirm_list.push(`[Manual Confirm] ${m.path_or_ref} 为 ${m.trust_level}，不可作为强事实`);
        }
        plan.required_summary.push(`摘要: ${m.path_or_ref} (kind=${m.kind}, trust=${m.trust_level})`);
    }
    return plan;
}
// ── 工厂函数 ──
/**
 * 创建输入材料实例。
 * @description 自动推断材料类型、摄取状态、信任等级和作用域角色。
 * @param params - 材料参数（kind 可选，将自动推断）
 * @returns 创建的输入材料
 */
export function createInputMaterial(params) {
    debug("InputMaterialContract", "创建输入材料");
    const id = `mat-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
    const kind = params.kind ?? detectMaterialKind(params.path_or_ref);
    const status = classifyIngestionStatus(params.path_or_ref);
    let accessMode = params.access_mode ?? "read_only";
    if (status === "forbidden")
        accessMode = "forbidden";
    const trustLevel = params.trust_level ?? inferTrustLevel(kind, params.path_or_ref);
    const scopeRole = params.scope_role ?? inferScopeRole(kind);
    const extracted_to = kind === "archive" ? getExtractionPath(id) : undefined;
    return {
        id,
        kind,
        path_or_ref: params.path_or_ref,
        access_mode: accessMode,
        trust_level: trustLevel,
        scope_role: scopeRole,
        hash: params.hash,
        extracted_to,
        cleanup_required: kind === "archive",
        size_bytes: params.size_bytes,
        created_at: new Date().toISOString(),
    };
}
function inferTrustLevel(kind, pathOrRef) {
    if (kind === "url")
        return "external_unverified";
    if (kind === "figma")
        return "external_unverified";
    if (kind === "user_text" || kind === "clipboard")
        return "user_provided";
    if (kind === "project_file")
        return "project_context";
    if (kind === "image")
        return "derived";
    if (pathOrRef.startsWith("/") || pathOrRef.startsWith("~"))
        return "user_provided";
    return "user_provided";
}
function inferScopeRole(kind) {
    if (kind === "project_file")
        return "project_scope";
    return "input_only";
}
/**
 * 为指定材料创建摄取证据。
 * @description 读取文件元数据/内容，计算 hash，检测摄取警告。
 * @param material - 输入材料
 * @returns 摄取证据
 */
export function createIngestionEvidence(material, projectPath) {
    const warnings = detectIngestionWarnings(material);
    let status = "pending";
    let computedHash = material.hash;
    let sizeBytes = material.size_bytes;
    if (material.access_mode === "forbidden") {
        status = "forbidden";
    }
    else {
        const isLocalFile = material.kind === "project_file" || material.kind === "external_file"
            || material.kind === "log" || material.kind === "document" || material.kind === "archive";
        if (isLocalFile) {
            const resolvedPath = material.path_or_ref.replace(/^~\//, (process.env.HOME || "/") + "/");
            // 安全修复: 验证解析后的路径在项目目录内（防止路径穿越）
            if (projectPath && !isPathInsideProject(projectPath, resolvedPath)) {
                warnings.push(`路径在项目外，已跳过: ${material.path_or_ref}`);
                status = "failed";
            }
            else {
                try {
                    const stat = fs.statSync(resolvedPath);
                    if (!sizeBytes)
                        sizeBytes = stat.size;
                    // 大文件预检: >50MB 跳过全量 hash 计算
                    const LARGE_FILE_THRESHOLD = 50 * 1024 * 1024;
                    if (sizeBytes !== undefined && sizeBytes > LARGE_FILE_THRESHOLD) {
                        warnings.push(`文件超过 ${Math.round(LARGE_FILE_THRESHOLD / 1024 / 1024)}MB，跳过 hash 计算`);
                        status = "metadata_read";
                    }
                    else if (material.kind === "archive") {
                        // 归档文件：仅读取元数据，未解析内容
                        status = "metadata_read";
                        // 计算归档文件自身的 hash
                        try {
                            const fileBuf = fs.readFileSync(resolvedPath);
                            computedHash = "sha256:" + crypto.createHash("sha256").update(fileBuf).digest("hex");
                        }
                        catch {
                            warnings.push("archive hash 计算失败");
                        }
                    }
                    else {
                        // 非归档本地文件：完整读取并计算 hash
                        try {
                            const fileBuf = fs.readFileSync(resolvedPath);
                            computedHash = "sha256:" + crypto.createHash("sha256").update(fileBuf).digest("hex");
                            status = "read";
                        }
                        catch {
                            status = "metadata_read";
                            warnings.push("文件内容读取失败，hash 不可用");
                        }
                    }
                }
                catch {
                    status = "failed";
                    warnings.push(`文件不存在: ${material.path_or_ref}`);
                }
            } // end else (路径在项目内)
        }
    }
    return {
        evidence_id: `ev-${material.id}`,
        material_id: material.id,
        source_ref: material.path_or_ref,
        status,
        hash: computedHash,
        size_bytes: sizeBytes,
        detected_kind: material.kind,
        trust_level: material.trust_level,
        access_mode: material.access_mode,
        extracted_to: material.extracted_to,
        parsed_artifacts: [],
        warnings,
        created_at: material.created_at ?? new Date().toISOString(),
    };
}
// ── 治理校验 ──
/**
 * 校验输入材料契约的合规性。
 * @description 检查 scope、解压路径、cleanup、信任等级、摄取状态等 8 条规则。
 * @param materials - 待校验的材料列表
 * @returns 治理发现列表（hard_fail 为硬性错误，advisory 为建议项）
 */
export function validateInputMaterialContracts(materials) {
    debug("InputMaterialContract", "开始校验输入材料契约", { count: materials.length });
    const findings = [];
    for (const m of materials) {
        // 规则 1：input_only 不得进入 write scope
        if (m.scope_role === "input_only" && m.access_mode === "read_write") {
            findings.push({
                severity: "hard_fail",
                material_id: m.id,
                rule: "gc-input-only-write",
                message: `input_only 材料 ${m.id} 不得有 read_write access_mode`,
            });
        }
        // 规则 2：archive 必须有隔离解压路径
        if (m.kind === "archive") {
            if (!m.extracted_to) {
                findings.push({
                    severity: "hard_fail",
                    material_id: m.id,
                    rule: "gc-archive-no-extraction-path",
                    message: `archive ${m.id} 缺少 extracted_to 隔离路径`,
                });
            }
            else if (m.extracted_to === "." || m.extracted_to === "./" || m.extracted_to === "/") {
                findings.push({
                    severity: "hard_fail",
                    material_id: m.id,
                    rule: "gc-archive-root-extraction",
                    message: `archive ${m.id} 解压到项目根: ${m.extracted_to}`,
                });
            }
            else if (!isExtractionPathConfined(m.extracted_to, ".")) {
                findings.push({
                    severity: "hard_fail",
                    material_id: m.id,
                    rule: "gc-archive-unconfined-extraction",
                    message: `archive ${m.id} 解压路径未隔离: ${m.extracted_to}`,
                });
            }
        }
        // 规则 3：archive 必须标记 cleanup_required
        if (m.kind === "archive" && !m.cleanup_required) {
            findings.push({
                severity: "advisory",
                material_id: m.id,
                rule: "gc-archive-no-cleanup",
                message: `archive ${m.id} 未标记 cleanup_required`,
            });
        }
        // 规则 4：derived/ai_generated 不得作为 authoritative
        if ((m.trust_level === "derived" || m.trust_level === "ai_generated") &&
            m.scope_role === "evidence_ref" &&
            m.access_mode !== "forbidden") {
            findings.push({
                severity: "advisory",
                material_id: m.id,
                rule: "gc-low-trust-evidence",
                message: `${m.trust_level} 材料 ${m.id} 作为 evidence_ref 不可支撑强结论`,
            });
        }
        // 规则 5：forbidden 材料必须 access_mode=forbidden
        const status = classifyIngestionStatus(m.path_or_ref);
        if (status === "forbidden" && m.access_mode !== "forbidden") {
            findings.push({
                severity: "hard_fail",
                material_id: m.id,
                rule: "gc-forbidden-not-blocked",
                message: `材料 ${m.id} 路径触发禁止规则但 access_mode 不是 forbidden`,
            });
        }
        // 规则 6：requires_confirmation 路径应有确认
        if (status === "requires_confirmation" && m.access_mode === "read_write") {
            findings.push({
                severity: "hard_fail",
                material_id: m.id,
                rule: "gc-sensitive-write",
                message: `敏感路径 ${m.id} 需确认后才可读取，不得 read_write`,
            });
        }
        // 规则 7：每个材料必须有 trust_level 和 scope_role
        if (!m.trust_level) {
            findings.push({
                severity: "hard_fail",
                material_id: m.id,
                rule: "gc-no-trust-level",
                message: `材料 ${m.id} 缺少 trust_level`,
            });
        }
        if (!m.scope_role) {
            findings.push({
                severity: "hard_fail",
                material_id: m.id,
                rule: "gc-no-scope-role",
                message: `材料 ${m.id} 缺少 scope_role`,
            });
        }
        // 规则 8：大文件不得全文注入 prompt
        if (m.size_bytes && m.size_bytes > 50 * 1024 * 1024 && m.scope_role !== "manual_reference") {
            findings.push({
                severity: "advisory",
                material_id: m.id,
                rule: "gc-large-file-injection",
                message: `材料 ${m.id} 超过 50MB，应只注入摘要`,
            });
        }
    }
    return findings;
}
/**
 * 验证 IngestionEvidence 完整性。
 * @param evidence - 摄取证据
 * @returns 治理发现列表
 */
export function validateIngestionEvidence(evidence) {
    const findings = [];
    if (!evidence.evidence_id) {
        findings.push({
            severity: "hard_fail",
            rule: "gc-no-evidence-id",
            message: "IngestionEvidence 缺少 evidence_id",
        });
    }
    if (!evidence.material_id) {
        findings.push({
            severity: "hard_fail",
            rule: "gc-no-material-id",
            message: "IngestionEvidence 缺少 material_id",
        });
    }
    if (!evidence.created_at) {
        findings.push({
            severity: "advisory",
            rule: "gc-no-created-at",
            message: "IngestionEvidence 缺少 created_at",
        });
    }
    // 本地文件证据缺少 hash 时应有警告
    const isLocalFile = evidence.detected_kind === "project_file" || evidence.detected_kind === "external_file"
        || evidence.detected_kind === "log" || evidence.detected_kind === "document";
    if (isLocalFile && evidence.status === "read" && !evidence.hash) {
        findings.push({
            severity: "advisory",
            rule: "gc-no-hash-on-read",
            message: `IngestionEvidence ${evidence.evidence_id} status=read 但无 hash`,
        });
    }
    return findings;
}
//# sourceMappingURL=input_material_contract_registry.js.map