/** * 对抗审查记录存储 — AdversarialReviewRecord 的文件 CRUD + K 次独立采样完成/交集聚合判定。 * * 职责边界: * - 负责:对抗审查跨轮记录的读写、采样完成判定(K 轮即达标)、交集聚合(多数复现) * - 不负责:组装审查 prompt、调用 LLM、业务升级 * * 被谁调用:sf_review * 调用谁:Node.js fs * * 数据流:AdversarialReviewRecord JSON ↔ .soloforge/reviews/{review_id}.json * 持久化:.soloforge/reviews/{review_id}.json(一个 review session 一个文件,累积所有轮次) * * 为何不复用 :EvidenceView 是覆盖式(同产物最新 verify 覆盖旧)、 * findings 是 gate check 级结构(GateFinding)、无 round 维度;对抗审查需要跨轮累积 + * 自由文本 findings + 采样聚合,语义不同,独立存储避免污染门禁记录。 * * 确定性边界原则:放弃"零 error 收敛"(LLM 裁判 EIR>0 数学上不可达,重审黑洞), * 改 K 次独立采样取交集。交集 error 不直接等于真实缺陷,但必须进入 * triage → confirmed/false_positive/accepted_risk → fixed/waived 闭环,防"只留痕不消费"。 */ import { type FileSnapshot } from "./adversarial_review/diff.js"; import { type SignatureProvider } from "./signature_provider.js"; /** 单条审查发现(客户端 AI 从对抗 prompt 输出后结构化提交)。 * 强制可证伪:必须 quote 引用原文,不得空泛,否则对抗 agent 沦为永远找茬。 */ export interface ReviewFinding { /** finding 唯一标识(= reviewFindingKey(artifact,location,quote)),resolution 只按此键匹配。 */ id: string; /** 严重度:error/warning/info 均如实记录(采样模式不再阻断,留痕) */ severity: "error" | "warning" | "info"; /** 所属产物路径 */ artifact: string; /** 产物内位置(章节/行) */ location: string; /** 引用原文(强制:必须引用,不得空泛) */ quote: string; /** 问题描述 */ issue: string; /** 修复建议 */ suggestion: string; /** 审查发现裁决状态:needs_triage=多数复现但未核验;confirmed=已核验真实;false_positive=误报;accepted_risk=用户接受风险 */ resolution_status?: "needs_triage" | "confirmed" | "false_positive" | "accepted_risk"; /** 闭环状态:open=待处理;fixed=已修复复验;waived=已接受/豁免 */ closure_status?: "open" | "fixed" | "waived"; /** 裁决/修复证据(源码位置、产物位置、verify 记录、用户确认引用等) */ resolution_evidence?: string; /** 最近一次裁决/闭环更新时间 */ resolved_at?: number; /** accepted_risk 闭环的用户确认标记:仅当通过 sf_review confirm=true 应用 accepted_risk 时置 true。 * judgment.blockingReviewFindings 据此判定 accepted_risk 是否真的非阻塞——防 AI 自签 accepted_risk 绕过修复 * accepted_risk 须 waived + evidence + 用户确认。 */ user_confirmed?: boolean; /** 证据类型(K=3 证据强度维度): * deterministic = 确定性铁证(grep 0 命中 / 编译错误 / 引用具体 check_id),单轮发现也 retained(不走 ceil(K/2) 交集阈值); * llm_subjective = LLM 主观判断,走 K=3 交集阈值。未标注时按 llm_subjective 处理。 */ evidence_type?: "deterministic" | "llm_subjective"; /** 关键性维度:安全、资金和合规类发现允许单轮进入交集。 * security/finance/compliance = 安全/资金/合规类发现,单轮也进交集强制裁决(不走 ceil(K/2) 阈值)—— * 这类发现天然难多轮复现(不同利益方视角才看到不同缺口,如横向越权/支付防篡改),K=3 交集会漏判真实缺口。 * 未标注时按普通 finding 走 K=3 交集阈值。 */ criticality?: "security" | "finance" | "compliance"; /** 修复验证锚点(跨产物 finding:修复发生在 quote 来源文件之外,如问题在 openapi、quote 引自 md)。 * resolve=fixed / auto_fix 核验时若 fix_verification.artifact !== finding.artifact,按锚点核验而非 quote 消失: * expected_present 在 fix.artifact 出现 OR expected_absent 在 fix.artifact 消失 → 核验通过。 * 锚点由 AI 填、引擎验证;AI 填假锚点核验虽可能过,但 fixed 一律触发差量重审兜底(auto_fixed_pending_re_review), * 与 quote 消失可能被删措辞伪造 + 差量重审兜底同信任边界。 */ fix_verification?: { artifact: string; expected_present?: string; expected_absent?: string; }; /** 修复动作声明(审查时标注):remove=该 finding 的修复方式是删除整个目标产物(删除型修复)。 * resolve confirmed 时若 fix_action="remove",走 removed 核验(baseline 有 + 当前无)而非 quote 消失。 */ fix_action?: "remove"; /** 修复证据(三型核验):显式声明修复类型 + 锚点,支持 ADD/MODIFY/REMOVE。优先于 fix_action/fix_verification/quote 消失。 * ADD=新增代码(new_anchor 出现即 ok,不要求旧 quote 消失);MODIFY=改写(old_quote 消失 AND new_anchor 出现,双向防空删); * REMOVE=删除(old_quote 消失)。 */ repair_evidence?: RepairEvidence; } /** 修复证据模型(三型核验)。AI 填、引擎按 repair_type 核验;old_quote/new_anchor 为锚点,artifact 为修复目标产物。 */ export interface RepairEvidence { /** 修复前原文(REMOVE/MODIFY 用,证明曾存在;ADD 可空——ADD 是新增,无旧 quote)。 */ old_quote?: string; /** 修复后新代码锚点(MODIFY/ADD 必填;>= MIN_ANCHOR_LEN 词边界核验,防短/泛 token 假满足)。 */ new_anchor?: string; /** 修复类型:ADD=新增代码 / MODIFY=改写代码 / REMOVE=删除代码或产物。 */ repair_type: "ADD" | "MODIFY" | "REMOVE"; /** 核验方法声明(审计用):quote_absent=旧 quote 消失 / anchor_present=新 anchor 出现 / structural=结构定位(file/symbol/range)。 */ verification_method: "quote_absent" | "anchor_present" | "structural"; /** 新代码所在产物(默认 finding.artifact;跨产物修复填修复文件路径)。 */ artifact?: string; } /** 单轮审查记录(一轮 = 一次独立采样) */ export interface ReviewRoundRecord { /** 轮次号(从 1 开始) */ round: number; /** 本轮所有发现 */ findings: ReviewFinding[]; /** error 级数量 */ error_count: number; /** warning 级数量 */ warning_count: number; /** 审查者 session/agent 标识(round>=2 必填,多轮须不同)。 * 引擎校验声明的结构独立;真实隔离仍靠客户端实际派发全新 subagent/session。 */ reviewer_session_id?: string; /** 评估时间戳 */ reviewed_at: number; } /** 对抗审查会话记录(累积所有轮次,不覆盖) */ export interface AdversarialReviewRecord { /** 会话 ID(round>=2 时客户端带回) */ review_id: string; /** 关联任务 */ task_id: string; /** 审查模式:per_artifact 单产物深度 / cross_artifact 跨产物一致性 */ mode: "per_artifact" | "cross_artifact"; /** per_artifact 模式指定单产物 kind;handler 存 kind 而非文件路径,门禁据此匹配 verified_artifacts.kind。 */ artifact_path?: string; /** per_artifact + code 切片级审查:本条 review 审的切片("SLC-11")。undefined=整产物旧模式(design 单产物 / 整 code)。 * 门禁在切片循环场景检查每个切片的 per-slice review:artifact_path=code + slice_id=SLC-NN 标识切片级 review。 * 重审查找按 artifact_path + slice_id 同切片匹配(不同切片是独立审查,不互为基线)。 */ slice_id?: string; /** 审查的产物列表 */ artifacts: string[]; /** per 模式承接对照素材(target 的 requires 上游 + companion_files + references 路径)。 * 首轮收集存档、后续轮复用;cross 模式无(对照靠研讨记录 + 跨产物清单)。 */ references?: string[]; /** 所有轮次记录(累积,每轮一次独立采样) */ rounds: ReviewRoundRecord[]; /** 总轮次 */ total_rounds: number; /** 是否已完成 K 次独立采样(完成后交集 error 必须裁决/闭环,不再只校验"审过") */ completed: boolean; /** 完成轮次 */ completed_round?: number; /** K 次独立采样目标轮数 */ sample_k: number; /** K 次采样后的交集结果(出现次数 ≥ ceil(K/2) 的 findings) */ intersection_findings?: ReviewFinding[]; /** 完成时仍需处理的 findings(error 级会被交付证据策略消费) */ retained_findings: ReviewFinding[]; /** 会话状态 */ status: "in_progress" | "completed"; /** 创建时间 */ created_at: number; /** 更新时间 */ updated_at: number; /** per_artifact 模式:审查创建时该产物完整验证记录的内容摘要。 * 产物摘要不变时审查保持新鲜,内容变化时失效。 */ artifact_git_hash?: string; /** 本次差量审查所依据的前次 completed 审查;首次审查时为空。 * 配合 baseline 快照 sidecar(reviews//baseline/)实现「自上次审查以来内容变更」聚焦审查。 */ baseline_review_id?: string; /** 差量审查聚焦上下文:内容 diff 与前次留存 finding,注入每轮 prompt。 * 这是客观变更集而非审查结论,跨轮复用不破坏独立采样。 */ focus_context?: string; /** 差量重审可使用已有完整基线进行 K=2 独立复核(RE_REVIEW_SAMPLE_K);error 仍须正常裁决和闭环。 */ is_re_review?: boolean; /** 创建审查时的标准修订;按 core/api_internal 维度判定审查证据是否过期。 */ review_criteria_revision: { core: number; api_internal: number; }; /** auto_fix 批量判 fixed 后置 true,要求新的审查证据确认没有误判。 */ auto_fixed_pending_re_review?: boolean; /** 重审收敛信号:本次 intersection 无 error,或 error 全是 baseline 前次 retained * 已知复发(无新增)→ true。治"fixed→needs_re_review=true 永真、重审无限循环、客户端不知何时可停"。 * true=审查深度已足够,可停止重审闭环;undefined=首次审查或非重审;false=本次有新 error。 */ converged?: boolean; /** cross_artifact 创建时记录各关联产物的完整验证 hash,用于逐产物判定新鲜度。 */ cross_artifact_hashes?: Record; /** cross_artifact 审查范围;不同 scope 使用各自的差量基线。 */ cross_scope?: "design" | "prototype" | "deliberation"; /** 引擎写入标记:store.writeUnlocked 置 "sf_review"。assertReviewRecord 对 completed 记录校验此标记, * 防 AI 直接 node-e 写 .soloforge/reviews/ 伪造 completed 记录绕交付门禁(bash_check 拦 shell 载体,解释器残口靠此+HMAC 兜底)。 * 旧记录(无此字段)由 readAndValidate 补标信任;created_via 止血(AI 读 schema 知字段名可伪造),HMAC(hmac 字段)根治(需读 .soloforge/secret/ 密钥)。 */ created_via?: "sf_review"; /** BETA: start 时引擎派发的 reviewer_session_id token(sample_k-1 个,round=1 首轮无需 token)。 * submit 须用这些 token 防 AI 自填字符串伪造 K-sample 独立性(MCP 不能 spawn subagent,token 是当前架构上限)。 * undefined=旧记录或测试手动构造(向后兼容,submit 不校验 token)。appendRounds 消费后移除。 */ pending_reviewer_tokens?: string[]; /** 引擎写入时的 HMAC 签名(基于 .soloforge/secret/hmac.key + record 规范化内容)。verifyProvenance 读取侧验签, * 无有效签名=外部伪造→排除。无 secret 的项目(init 前/旧项目)不签不验,靠 created_via 止血。 */ hmac?: string; /** 被 newer completed review 取代(同 mode+artifact_path+slice_id 或 mode+cross_scope 的旧 in_progress)。 * supersede 在 completed 路径由引擎写入(handler 完成路径),走 store.write 自动 HMAC 签名; * node-e 伪造 superseded_by 不重签 → verifyProvenance 验签失败丢弃(HMAC 根治,created_via 止血不覆盖 in_progress)。 */ superseded_by?: string; /** supersede 时间戳(与 superseded_by 同源同信任边界)。 */ superseded_at?: number; /** 审查执行记录 id(completed 时 store 写 ReviewRun 并设此字段;evaluateAdversarialReviewEvidence 须查到对应 run + 校验,防手写 passed review 绕门禁)。 */ review_run_id?: string; } export type AppendReviewRoundResult = { ok: true; record: AdversarialReviewRecord; } | { ok: false; error_code: "not_found" | "task_mismatch" | "already_completed" | "unexpected_round" | "independence_violation"; message: string; }; /** * 采样完成判定:已提交 K 轮独立采样。 * 确定性边界原则:不再判定"零 error 收敛"(LLM 裁判主观不可收敛), * 只判"是否完成 K 轮"。完成不等于可推进;多数复现 error 还要裁决/闭环。 * 注:首轮(round=1)只发 prompt 不提交 findings,故 rounds.length 对应已完成的采样轮数。 */ export declare function checkSamplingComplete(rounds: ReviewRoundRecord[], k: number): boolean; /** finding 跨轮匹配键(交集 computeIntersection + resolutions 闭环共用,避免双源漂移):artifact + location 归一化。 * 刻意不含 issue —— issue 是 LLM 自由文本描述,K 次独立审查同一问题表述几乎必不同, * 精确匹配致语义复现判 0(FacadeGuard K=3 对触发 8 次修复仍判 0)。 * 改用 location(位置比描述稳定,prompt 引导用 章节号/L行号锚点)做跨轮匹配键。 */ export declare function reviewFindingKey(artifact: string, location: string, quote?: string): string; /** * K 次独立采样取交集:出现次数 ≥ threshold 的 findings 入交集。 * threshold 默认 ceil(K/2)(多数)。每轮独立采样(不参考前轮),交集过滤掉 * 单次随机噪声,只留多轮复现的稳定发现。 */ export declare function computeIntersection(rounds: ReviewRoundRecord[], threshold?: number): ReviewFinding[]; /** 统计 findings 中各严重度数量 */ export declare function countBySeverity(findings: ReviewFinding[]): { error_count: number; warning_count: number; }; /** 单次审查会话的状态(断点续审进度视图)。 */ type ReviewSessionStatus = "completed" | "in_progress"; /** 任务对抗审查进度(断点续审——客户端跨会话不必记忆 review_id,引擎按 task 派生"哪条没审完")。 * 纯派生自 listByTask 现有字段。不判"应审未审",该要求由 TaskService 的证据 policy 决定。 * 只报已发起会话的进度 + 可续审入口(resumable)。 */ export interface ReviewProgress { per_artifact: Array<{ kind: string; slice_id?: string; status: ReviewSessionStatus; review_id: string; rounds_done: number; sample_k: number; }>; cross_artifact: { status: ReviewSessionStatus; review_id?: string; rounds_done: number; sample_k: number; }; /** 可续审会话(in_progress,客户端按 next_round 续传 findings + review_id 完成采样) */ resumable: Array<{ review_id: string; mode: "per_artifact" | "cross_artifact"; kind?: string; slice_id?: string; next_round: number; sample_k: number; }>; } /** * 对抗审查记录存储 — 纯文件 CRUD,采样完成/交集聚合为纯函数(上方导出)。 * 通过构造函数注入 projectRoot,方便测试用临时目录。 */ export declare class AdversarialReviewStore { private readonly projectRoot; private readonly provider; private readonly reviewRuns; constructor(projectRoot: string, provider?: SignatureProvider); /** 同一审查会话的修改必须跨进程串行,避免多个 MCP/CLI 实例覆盖轮次。 */ private withReviewLock; /** 会话记录文件路径 */ private filePath; /** 生成新 review_id */ generateReviewId(): string; /** BETA: 生成 reviewer_session_id token(start 派发,submit 须回传此值防 AI 自填伪造独立性)。 */ generateReviewerSessionToken(): string; /** 创建新会话记录 */ create(record: AdversarialReviewRecord): Promise; /** 读取并校验记录(补旧记录 created_via 标记 + schema 校验)。统一读取入口,read/listByTask/listAll/delete 共用, * 保证所有读取路径一致地补标 + 校验,避免某条路径漏校验致伪造记录溜过。 */ private readAndValidate; /** 读取会话记录(不存在返回 null) */ read(reviewId: string): Promise; /** 覆盖写入(追加轮次后更新) */ write(record: AdversarialReviewRecord): Promise; private writeUnlocked; /** 差量重审基线快照目录(reviews//baseline/,与 .json 并列)。 * listByTask/listAll 只扫 .json 文件,此 sidecar 目录不被误认为记录。 */ private baselineDir; /** 写入审查创建时该产物的文件内容基线(供下次重审算 diff)。委托 diff 模块。 */ writeBaseline(reviewId: string, snapshots: FileSnapshot[]): Promise; /** 读取某审查的基线快照(旧审查无 sidecar 返回 [])。 */ readBaseline(reviewId: string): Promise; /** 暴露基线目录路径(handler 算 diff 时需传给 computeArtifactDiff)。 */ baselinePath(reviewId: string): string; /** 删除某审查的基线 sidecar(任务归档/审查删除时连带清理)。 */ removeBaseline(reviewId: string): Promise; /** 原子追加轮次(load-validate-push-write 在 per-reviewId 锁内,防并发 reviewer 覆盖/重复 round 凑数/旧 completed 刷新时间戳)。 * 首轮 round=1 只发 prompt 不写入 rounds,因此第一条可追加采样必须是 round=2,之后严格递增。 */ appendRound(reviewId: string, round: ReviewRoundRecord, expectedTaskId?: string): Promise; /** 批量轮次在同一锁和同一次写入中校验并追加,避免批次中途失败留下半提交状态。 */ appendRounds(reviewId: string, rounds: ReviewRoundRecord[], expectedTaskId?: string): Promise; /** 标记 review 被 byReviewId 取代(supersede,锁内 read-check-write 防 β 并发 lost update)。 * 锁内重读最新(不用调用方传的 stale 副本),复查仍 in_progress 且未 superseded 才写;走 writeUnlocked 自动 HMAC。 * handler completed 路径扫到同键旧 in_progress 时调此,不得直接 store.write(无锁,与并发 submit 写同 review 会丢轮次)。 */ markSuperseded(reviewId: string, byReviewId: string, at: number): Promise; /** 列出指定任务的所有审查会话(按 updated_at 降序) */ listByTask(taskId: string): Promise; /** 列出所有任务的审查会话(升级影响分析、管理诊断,及交付消费的内容锚点继承候选—— * 继承须过 artifact_git_hash 严格一致 + 时序 + 非 superseded + run 同源校验,见 evidence_evaluator_v2)。 */ listAll(): Promise; /** 删除指定任务的所有审查会话。scope 变化会改变审查输入/引用范围,旧审查不可复用。 */ deleteByTask(taskId: string): Promise; /** 删除指定任务中满足条件的审查会话。用于 reset/restart 只清失效阶段,保留仍有效的上游审查。 */ deleteByTaskWhere(taskId: string, predicate: (record: AdversarialReviewRecord) => boolean): Promise; /** 派生任务对抗审查进度(断点续审)。纯派生自 listByTask,无 schema 改动。 * per_artifact 按 (artifact_path=kind, slice_id) 分组取最新会话(listByTask 已按 updated_at 降序); * resumable = 所有 in_progress 会话的续审入口(客户端按 next_round 续传 findings + review_id 完成采样)。 * 不判"应审未审";本方法只报告已发起会话状态。 */ getReviewProgress(taskId: string): Promise; } export {};