/** * PolicyRegistry — 门禁策略的中央注册表。 * * 策略通过 register() 注册到指定的 checkpoint。 * evaluate() 执行流程: * 1. 按 checkpoint 过滤出适用的策略。 * 2. 调用每个策略的 evaluate() 收集 findings。 * 3. 按 severity 排序(hard-deny 优先)。 * 4. 按 policy+message 去重(保留首次出现的,即 severity 最高的)。 * * 设计决策: * - PolicyRegistry 与 GateRunner 解耦:GateRunner 通过桥接 meta-policy * 委托给 PolicyRegistry.evaluate(),两者可独立演进。 * - 策略注册使用 name 作为唯一键,重复注册会覆盖旧策略。 */ import type { GateCheckpoint, GateSeverity } from "./taxonomy.ts"; import { SEVERITY_RANK } from "./taxonomy.ts"; import type { GateContext, GateFinding } from "./findings.ts"; /** 策略评估函数类型。接收门禁上下文,返回 findings 数组。 */ export type PolicyEvaluateFn = (ctx: GateContext) => GateFinding[]; /** 策略注册条目。 */ export interface PolicyRegistration { /** 策略唯一名称(用于 finding 归因和去重键)。 */ name: string; /** 策略评估函数。 */ evaluate: PolicyEvaluateFn; /** 策略生效的 checkpoint 列表。必须显式指定,不支持空数组(即"全部 checkpoint")。 */ checkpoints: GateCheckpoint[]; } export class PolicyRegistry { /** 策略存储,使用 Map 保证 name 唯一性。 */ private readonly policies = new Map(); /** * 注册策略。同名策略会被覆盖(最后一次注册生效)。 */ register(policy: PolicyRegistration): void { this.policies.set(policy.name, policy); } /** * Remove a policy by name. * Returns true if the policy existed, false otherwise. */ deregister(name: string): boolean { return this.policies.delete(name); } /** * 执行所有适用于给定 checkpoint 的策略。 * * 流程: * 1. 遍历已注册策略,跳过不适用于当前 checkpoint 的。 * 2. 调用策略的 evaluate() 收集 findings。 * 3. 按 severity 降序排序(hard-deny 在前),同 severity 按 policy 名字母序。 * 4. 按 policy+message 去重(保留首次出现的条目)。 * * @param checkpoint - 当前门禁检查点 * @param context - 门禁上下文 * @returns 排序且去重后的 findings 数组 */ evaluate(checkpoint: GateCheckpoint, context: GateContext): GateFinding[] { const allFindings: GateFinding[] = []; for (const policy of this.policies.values()) { if (!policy.checkpoints.includes(checkpoint)) continue; const findings = policy.evaluate({ ...context, checkpoint }); allFindings.push(...findings); } // Sort by severity (most severe first), then by policy name allFindings.sort((a, b) => { const rankDiff = (SEVERITY_RANK[b.severity] ?? 0) - (SEVERITY_RANK[a.severity] ?? 0); if (rankDiff !== 0) return rankDiff; return a.policy.localeCompare(b.policy); }); // Deduplicate by policy+message return deduplicateFindings(allFindings); } /** * List all registered policy names. */ list(): string[] { return [...this.policies.keys()]; } /** * Check if a policy with the given name is registered. */ has(name: string): boolean { return this.policies.has(name); } } /** * 内部去重函数:按 policy+message 键去重,保留首次出现的条目。 * 由于输入已按 severity 降序排序,首次出现的即为 severity 最高的。 */ function deduplicateFindings(findings: GateFinding[]): GateFinding[] { const seen = new Set(); const result: GateFinding[] = []; for (const finding of findings) { const key = `${finding.policy}::${finding.message}`; if (seen.has(key)) continue; seen.add(key); result.push(finding); } return result; } // ── 默认注册表工厂 ──────────────────────────────────────────────────────── // 导入全部 8 个 P0 策略,这些策略覆盖了 KCode 控制面的核心门禁检查。 import { evaluateWorkspacePathPolicy, WORKSPACE_PATH_POLICY_NAME } from "./policies/workspace-path-policy.ts"; import { evaluateToolGatewayPolicy, TOOL_GATEWAY_POLICY_NAME } from "./policies/tool-gateway-policy.ts"; import { evaluatePowerShellMutationPolicy, POWERSHELL_MUTATION_POLICY_NAME } from "./policies/powershell-mutation-policy.ts"; import { evaluateSourceWritePolicy, SOURCE_WRITE_POLICY_NAME } from "./policies/source-write-policy.ts"; import { evaluatePhaseGatePolicy, PHASE_GATE_POLICY_NAME } from "./policies/phase-gate-policy.ts"; import { evaluatePlanPathPolicy, PLAN_PATH_POLICY_NAME } from "./policies/plan-path-policy.ts"; import { evaluateTddPolicy, TDD_POLICY_NAME } from "./policies/tdd-policy.ts"; import { evaluateVerifyCommandPolicy, VERIFY_COMMAND_POLICY_NAME } from "./policies/verify-command-policy.ts"; /** * 创建预装全部 8 个 P0 策略的默认 PolicyRegistry。 * * 策略清单及 checkpoint 分配: * - workspace-path-policy → pre-tool, pre-write (工作区路径安全检查) * - tool-gateway-policy → pre-tool (工具网关准入检查) * - powershell-mutation-policy → pre-tool (PowerShell 变异命令检查) * - source-write-policy → pre-write (源码写入策略检查) * - phase-gate-policy → pre-phase-transition, pre-write (阶段门禁检查) * - plan-path-policy → pre-write (PLAN.md 路径批准检查) * - tdd-policy → pre-write, post-verify-result (TDD 红绿信号检查) * - verify-command-policy → pre-verify-command (验证命令安全检查) */ export function createDefaultPolicyRegistry(): PolicyRegistry { const registry = new PolicyRegistry(); registry.register({ name: WORKSPACE_PATH_POLICY_NAME, evaluate: evaluateWorkspacePathPolicy, checkpoints: ["pre-tool", "pre-write"], }); registry.register({ name: TOOL_GATEWAY_POLICY_NAME, evaluate: evaluateToolGatewayPolicy, checkpoints: ["pre-tool"], }); registry.register({ name: POWERSHELL_MUTATION_POLICY_NAME, evaluate: evaluatePowerShellMutationPolicy, checkpoints: ["pre-tool"], }); registry.register({ name: SOURCE_WRITE_POLICY_NAME, evaluate: evaluateSourceWritePolicy, checkpoints: ["pre-write"], }); registry.register({ name: PHASE_GATE_POLICY_NAME, evaluate: evaluatePhaseGatePolicy, checkpoints: ["pre-phase-transition", "pre-write"], }); registry.register({ name: PLAN_PATH_POLICY_NAME, evaluate: evaluatePlanPathPolicy, checkpoints: ["pre-write", "pre-phase-transition"], }); registry.register({ name: TDD_POLICY_NAME, evaluate: evaluateTddPolicy, checkpoints: ["pre-write", "post-verify-result"], }); registry.register({ name: VERIFY_COMMAND_POLICY_NAME, evaluate: evaluateVerifyCommandPolicy, checkpoints: ["pre-verify-command"], }); return registry; }