/** * 豁免记录存储 — WaiverRecord 的文件 CRUD + 审计 append。 * * 职责边界: * - 负责:WaiverRecord 的读写、按域查询、撤销、审计日志追加 * - 不负责:豁免校验逻辑(V2 无用户级创建入口;NON_WAIVABLE 由 apply-time gate_engine fail-closed 兜底)、gate 注入决策(在 gate_engine) * * 被谁调用:gate_engine evaluateGate(listActive+matchWaiver 注入)、verification/evidence/digest_builder(listActive 纳证据摘要)、adversarial_review handler(listActive 审查豁免)、task_record_cleanup(removeByTask) * 调用谁:Node.js fs(文件读写/追加) * * 数据流:WaiverRecord JSON ↔ .soloforge/waivers/{waiver_id}.json;审计 ↔ .soloforge/waiver_audit.jsonl * 持久化:.soloforge/waivers/{waiver_id}.json(每条豁免独立文件,UUID 隔离并发)+ .soloforge/waiver_audit.jsonl(追加审计) * * 设计参考:(CRUD 模式)、NavigationOverride(审计 jsonl + NON_OVERRIDABLE 防护)。 * 防后门:NON_WAIVABLE_CHECK_IDS 挡合成结构检查(apply-time gate_engine non_waivable fail-closed 兜底); * V1 创建期校验(block_check/confirm/reason≥10/scope≤3)随 sf_waiver 工具删除已无创建消费者,保留作历史语义。 */ import type { DomainName } from "./types.js"; import { type SignatureProvider } from "./signature_provider.js"; /** 不可豁免的结构性 check_id 清单。 * apply-time gate_engine 命中则 fail-closed —— 防止豁免"产物不存在/完整性/引擎异常"这类硬性结构要求。 * 这些检查由验证编排生成,不进入普通 validator 豁免路径; * V2 无用户级创建入口(sf_waiver 已删),本清单由 gate_engine apply-time 兜底消费。 */ export declare const NON_WAIVABLE_CHECK_IDS: ReadonlySet; /** * 同 conflict_source 的 active structural 豁免每类上限(四锁之四——sacp EXPECTED_SAME_MAX 同款纪律)。 * 扩容=门禁变更:改此常量须经显式评审(防止结构性豁免成为新的膨胀通道)。 */ export declare const STRUCTURAL_PER_CONFLICT_SOURCE_LIMIT = 5; /** 每域 structural 总上限(fresh 审害 major:conflict_source 是自由字符串,变体可绕过 per-source 上限—— * 总上限兜底防「换字符串分布」的膨胀复发;扩容=门禁变更同款纪律)。 */ export declare const STRUCTURAL_TOTAL_PER_DOMAIN_LIMIT = 10; /** conflict_source 归一化(trim+小写+内部空白折叠)——锁四计数与重复登记判定用归一形态。 */ export declare function normalizeConflictSource(raw: string): string; /** 豁免记录 — 标记某条 gate finding 为已知误报/接受,留审计后放行 */ export interface WaiverRecord { /** 唯一豁免 ID(UUID) */ waiver_id: string; /** 所属域 */ domain: DomainName; /** 所属任务;豁免不得跨任务复用。 */ task_id: string; /** 门禁名称(过滤用,可选) */ gate_name?: string; /** 产物路径(scope=exact 时必填;scope=check_id 时为空,表示该 check 在该域所有产物生效) */ artifact_path?: string; /** 被豁免的 check id */ check_id: string; /** check 名称快照(防 check 改名/删除后不可读) */ check_name?: string; /** 豁免范围:exact=某产物某check / check_id=该check在该域所有产物 */ scope: "exact" | "check_id"; /** finding 级豁免;scope=exact + finding_location 时只豁免该位置,其他 finding 仍参与验证。 * 如 TS-Q01 豁免参考目录单个 as any(finding_location="prototype-ref/ui/app.ts"),其余命中仍查。 * undefined=check 级(豁免整个 check)。须配合 executor 输出 CheckResult.findings(含 location)。 * 注:non_waivable check(如 CRUD-COMPLETE 策略完整性)不可豁免——豁免入口(sf_waiver create 期 + * apply-time gate_engine)双层拒绝,示例须用可豁免 check。 */ finding_location?: string; /** 豁免理由(必填,≥10字符,防敷衍;审计可追溯) */ reason: string; /** 创建时间戳 */ created_at: number; /** 创建者(恒为 user:waiver 需人工 confirm,引擎/AI 不得自行创建) */ created_by: "user"; /** 创建时的规则集指纹(漂移检测:规则变了 doctor 报 stale 需复核) */ ruleset_hash_at_creation: string; /** 创建时该 check 的规则指纹;只有目标 check 的规则未变时豁免才有效。 */ rule_hash_at_creation: string; /** 创建时产物内容摘要(与 gate_engine artifactGitHash 同源 getArtifactBundleHash)。 * scope=exact 必填:内容变化后该 waiver 失效(内容维度防"内容大改仍放行")。 * undefined(存量或算不出)→ 内容过滤判不匹配 → waiver 失效(保守,不放行)。 */ artifact_content_digest_at_creation?: string; /** 撤销时间戳(未撤销则 undefined,listActive 据此过滤) */ removed_at?: number; /** 撤销理由(审计) */ removed_reason?: string; /** * 豁免物种(sacp 反哺 C 批 WG.1):individual=现状个案(默认,完全向后兼容——存量无此字段即 individual); * structural=结构性豁免——跨任务生效(gate_engine 匹配放开 task_id 过滤),四锁条件在 create 期强制: * conflict_source 锚定矛盾源/expires_at 未来/approved_by=user/同 conflict_source 每类上限。 */ waiver_type?: "individual" | "structural"; /** structural 必填:矛盾源锚定(如 artifact:prototype_spec:docs/analyze——锚矛盾源而非被验证产物路径) */ conflict_source?: string; /** structural 必填:过期时间(过往即失效,不自动续期——续期须人工重新确认) */ expires_at?: number; /** structural 必填:批准人(恒 user——同 created_by 纪律,AI 不得自批) */ approved_by?: string; /** structural 必填:分类(如 legacy-prototype/parallel-batch——doctor 聚类展示用) */ category?: string; /** HMAC provenance 签名(有 secret 时签发,read 验签排除 node script.js 伪造豁免;与 ReviewRecord 同源机制) */ hmac?: string; } /** 豁免审计条目(追加到 waiver_audit.jsonl) */ export interface WaiverAuditEntry { action: "created" | "removed"; waiver_id: string; check_id: string; domain: DomainName; artifact_path?: string; scope: "exact" | "check_id"; reason: string; timestamp: number; } /** 生成 waiver_id(UUID) */ export declare function generateWaiverId(): string; /** * 匹配豁免:exact(check_id+artifact_path) 优先,其次 check_id-wide。 * evaluateGate 注入时调用,判断某 failed check 是否被豁免。 * * @param checkId - 失败的 check id * @param artifactPath - 当前产物路径;项目级检查未绑定单一产物时只匹配 check_id-wide * @param waivers - 当前域的 active 豁免列表 * @returns 命中的豁免(exact 优先);无匹配返回 undefined */ export declare function matchWaiver(checkId: string, artifactPath: string | undefined, waivers: WaiverRecord[], /** finding 级匹配:传 finding.location 时,优先找 finding_location 匹配的 waiver(只豁免该 finding)。 * finding 级无匹配时回退 check 级(exact 无 finding_location 豁免整个 check)+ check_id-wide。 * 不传时按 check 级匹配。 */ findingLocation?: string, /** 逻辑基准 path_pattern(与 artifactPath 的 resolved 互补比对)。 * brownfield override 后 artifactPath 是 resolved override 路径,但 waiver.artifact_path 可能是 * path_pattern 与 resolved path 都参与匹配,避免路径 override 后 waiver 静默失效。 */ artifactPathPattern?: string): WaiverRecord | undefined; /** * 豁免记录存储 — 纯文件 CRUD + 审计,无业务校验逻辑。 * 通过构造函数注入 projectRoot,方便测试时使用临时目录。 */ export declare class WaiverStore { private readonly projectRoot; private readonly provider; constructor(projectRoot: string, provider?: SignatureProvider); /** 同一豁免记录的修改必须跨进程串行,避免撤销与重复写互相覆盖。 */ private withWaiverLock; /** 豁免记录文件路径 */ private filePath; /** 审计日志路径 */ private auditPath; /** 追加审计条目(失败 throw,非静默:由 create/remove catch 回滚主记录,保持主记录与审计一致)。 * 治 Task-010:原 catch+debug 静默丢审计致主记录残留无审计,审计链不一致。 */ private appendAudit; /** 创建豁免(原子写主记录 + 审计 append) */ create(record: WaiverRecord): Promise; /** 读取 + schema 校验 + HMAC 验签(有 secret 失败排除伪造)。无 secret 跳过验签(旧项目兼容)。 */ private readAndValidate; /** 读取单条豁免(不存在/损坏返回 null) */ read(waiverId: string): Promise; /** 列出 active 豁免(未 removed)。可选按域过滤。损坏文件 skip。 */ listActive(domain?: DomainName): Promise; /** 统计某域 scope=check_id 的 active 豁免数(豁免校验防滥用,每域≤3) */ countCheckIdWide(domain: DomainName): Promise; /** 撤销豁免(标 removed_at 重写主记录 + 审计)。幂等:已撤销返回 true,不存在返回 false。 */ remove(waiverId: string, reason?: string): Promise; /** 批量撤销某任务的活跃豁免;restart/cancel/reset 用于防旧豁免继续放行新一轮 gate。 * structural 豁免除外(fresh 审查 major):结构性豁免的生命周期归 expires_at 与人工 revoke 管, * 不随 origin 任务 cancel/reset 连坐清除——否则「cancel 整批蒸发」病理在跨任务物种上重演, * 其他任务正依赖的豁免因一个无关任务取消而消失。 */ removeByTask(taskId: string, reason: string, predicate?: (record: WaiverRecord) => boolean): Promise; }