Documentation
¶
Overview ¶
Package validator provides a pluggable contract for verdicting AI- written staging changes. An implementation takes a tool-specific diff snapshot, applies its own policy (rule-based / LLM-as-judge / external ML service / ensemble), and returns a Verdict that the caller uses to commit or rollback, and feeds to a circuit breaker.
Deliberately carries no schema assumption: diffs flow in as opaque bytes keyed by SourceTool, letting implementations self-dispatch. The package supplies reference implementations (RuleValidator, LLMValidator) and a CompositeValidator for overlay composition (CLAUDE.md principle 10, "overlay not replace"). Provider selection (which ML or LLM backend) is a platform-layer concern.
Relationship to evolve.Reflector: Reflector is event-driven learning from historical feedback (post-decision KPI); Validator is a synchronous gate on an individual staged diff (pre-commit). The two interfaces are deliberately not merged -- their timing, inputs, and return shapes diverge. See evolve package docs for the learning surface.
Package validator 提供可插拔契约, 用于审批 AI 写入 staging 的变更. 实现接收工具特定的 diff 快照, 应用自定义策略 (规则 / LLM-as-judge / 外部 ML 服务 / 组合), 返回 Verdict 供调用方决定 commit 或 rollback, 并喂给熔断器.
刻意不假设 schema: diff 以 opaque bytes + SourceTool 流入, 实现自行 分发. 本包提供参考实现 (RuleValidator, LLMValidator) 与 CompositeValidator 叠加组合 (CLAUDE.md 原则 10 "叠加而非替换"). 具体后端选型 (用哪个 ML 或 LLM) 属 platform 层职责.
与 evolve.Reflector 的区分: Reflector 是从历史反馈事后学习 (决策后 KPI), Validator 是单个 staged diff 的同步 gate (commit 前). 两接口的时机 / 输入 / 返回形态都不同, 刻意不合并. 学习面见 evolve 包文档.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrValidatorBackend indicates the Validator's underlying backend // (LLM API, ML service, rule engine) failed. The Verdict is unset; // treat as Block. // // ErrValidatorBackend 表示 Validator 的下游 backend (LLM API / ML // 服务 / 规则引擎) 调用失败. Verdict 未产出; 按 Block 处置. ErrValidatorBackend = errors.New("validator: backend call failed") // ErrVerdictParse indicates the backend returned a response the // Validator could not parse into a Verdict. Treat as Block. // // ErrVerdictParse 表示 backend 返回的响应无法解析为 Verdict. 按 // Block 处置. ErrVerdictParse = errors.New("validator: verdict parse failed") // ErrUnknownDiffType indicates the Validator does not know how to // handle the given SourceTool. Implementations dispatching on // SourceTool should return this for unrecognised keys rather than // silently approving. // // ErrUnknownDiffType 表示 Validator 不识别给定的 SourceTool. 按 // SourceTool dispatch 的实现遇到未知 key 应返回此错误而非静默放行. ErrUnknownDiffType = errors.New("validator: unknown source_tool") )
Sentinel errors. Implementations should wrap these with %w so callers can detect error class with errors.Is.
哨兵错误. 实现方应以 %w 包装, 调用方用 errors.Is 识别错误类别.
Functions ¶
This section is empty.
Types ¶
type AlwaysApprove ¶
type AlwaysApprove struct{}
AlwaysApprove is an explicit opt-out Validator: it ignores the diff and always returns Approved=true. Its sole purpose is to make "no validation applied" an auditable, code-reviewable choice rather than an implicit default. Industries that deliberately want a Tool to run without real validation MUST wire AlwaysApprove{} as the Validator, so that the absence of a real policy is explicit at the call site and visible in logs (ValidatorName = "always-approve").
Pairs with NewValidatedTool panicking on nil Validator: together they eliminate the "I thought approval was on but it wasn't" failure mode by making opt-out both mandatory and visible.
AlwaysApprove 是显式 opt-out Validator: 忽略 diff, 永远返回 Approved=true. 唯一目的是让 "不做审批" 变成可审计 / code-review 能 抓到的显式选择, 而非隐式默认. 行业 platform 刻意想让某个 Tool 不走 审批时, 必须显式 wire AlwaysApprove{} 作为 Validator, 这样 "没审批" 在调用点就写清楚, 日志里也能看到 (ValidatorName = "always-approve").
与 NewValidatedTool 对 nil Validator panic 搭配 -- 两者一起消灭 "以为开了审批其实没开" 的故障模式, 方式是强制 opt-out 必须显式且 可见.
func (AlwaysApprove) Name ¶
func (AlwaysApprove) Name() string
Name returns "always-approve" -- the sentinel ValidatorName that audit logs and dashboards filter on to surface opt-out usage.
Name 返回 "always-approve" -- 审计日志和 dashboard 以此 ValidatorName 过滤暴露 opt-out 使用情况.
func (AlwaysApprove) Validate ¶
Validate ignores diff and returns an approved Verdict whose Reason and ValidatorName make the opt-out posture explicit downstream. Severity is left empty deliberately: AlwaysApprove asserts "no judgement", not "advisory warn", so a VerdictSink (e.g. the circuit breaker) can treat it as a clean success sample rather than a warn.
Validate 忽略 diff, 返回 approved Verdict, 其 Reason 和 ValidatorName 让 opt-out 姿态在下游显式可见. Severity 刻意留空: AlwaysApprove 断言 "不做评价" 而非 "advisory warn", 让 VerdictSink (如熔断器) 能把它当 成干净 success 样本而非 warn 样本.
type CompositeValidator ¶
type CompositeValidator struct {
// contains filtered or unexported fields
}
CompositeValidator combines multiple Validators into one. Used to layer cheap rules before LLM calls (Waterfall), or to require every Validator's approval (AllMustApprove). Realises CLAUDE.md principle 10 "overlay not replace" -- platforms can stack any number of Validators in arbitrary combinations.
CompositeValidator 组合多个 Validator. 用于便宜规则先于 LLM 调用 (Waterfall), 或要求所有 Validator 都过 (AllMustApprove). 实现 CLAUDE.md 原则 10 "叠加而非替换" -- 平台可以按任意组合叠加任意数量 Validator.
func NewCompositeValidator ¶
func NewCompositeValidator(name, policyVersion string, mode CompositionMode, children ...Validator) *CompositeValidator
NewCompositeValidator builds a CompositeValidator. name identifies this composite for audit; policyVersion is the composition tag (distinct from child Validators' own versions). mode selects the aggregation strategy. children may be empty (trivial approve).
NewCompositeValidator 构造 CompositeValidator. name 标识本 composite 供审计; policyVersion 是组合标签 (与子 Validator 自身版本独立). mode 选聚合策略. children 可为空 (trivial approve).
func (*CompositeValidator) Name ¶
func (c *CompositeValidator) Name() string
Name returns the composite's identifier.
Name 返回 composite 标识符.
func (*CompositeValidator) Validate ¶
Validate dispatches to the configured CompositionMode aggregator. An empty children list approves with Score=1.0 (vacuously satisfied). Unknown mode returns a fail-closed Block verdict plus an error.
Validate 按配置的 CompositionMode 聚合器分发. children 为空时直接 approve (Score=1.0, vacuous 满足). 未知 mode 返回 fail-closed Block verdict 附带 error.
type CompositionMode ¶
type CompositionMode int
CompositionMode controls how CompositeValidator aggregates child verdicts. Two modes are provided as a baseline; more exotic compositions (weighted voting, AND/OR trees) belong in the consuming platform layer to avoid bloating core reference implementations.
CompositionMode 决定 CompositeValidator 如何聚合子 verdict. 两种 模式作为基础; 加权投票 / AND-OR 树等更复杂的组合属于消费层职责, 避免臃肿 core 参考实现.
const ( // ModeAllMustApprove runs every child and approves only if every // child approves. Severity is the worst among children; Score is // the minimum. Provides a complete breakdown for audit -- errors // from any child are collected via errors.Join so errors.Is still // identifies class. // // ModeAllMustApprove 运行所有子 Validator, 全部 approve 才整体 // approve. Severity 取最严格, Score 取最小. 提供完整 breakdown // 供审计 -- 任一子的 error 经 errors.Join 合并, errors.Is 仍可 // 识别 class. ModeAllMustApprove CompositionMode = iota // ModeWaterfall runs children in order and short-circuits on the // first reject or error. Intended for cheap-first ordering (e.g. // RuleValidator before LLMValidator) so the expensive validator is // only reached when cheaper ones pass. // // ModeWaterfall 按顺序运行子 Validator, 遇第一个 reject 或 error // 即停. 面向便宜优先 (如 RuleValidator 先于 LLMValidator), 便宜的 // 通过才到达昂贵的. ModeWaterfall )
type DiffInput ¶
DiffInput carries the staged diff that a Validator must review. SourceTool is the dispatch key (e.g. "SQLCAS" / "SQLDryRun"), letting implementations decide how to decode Raw without the engine baking any schema assumption. Raw is the tool-specific serialized payload (typically tools.Result.Data marshalled as JSON). Metadata carries free-form context such as tenant / session / schema hints.
Why not any for the diff: losing the SourceTool self-description breaks LLM prompting, rule dispatch, and debuggability. Why not a concrete struct per tool: SQL CAS / DryRun / future HTTP-patch diffs have incompatible shapes. SourceTool + Raw is the compromise -- dispatch key plus opaque bytes.
DiffInput 承载待审的 staged diff. SourceTool 是分发键 (如 "SQLCAS" / "SQLDryRun"), 让实现自行决定如何 decode Raw, 引擎不假设任何 schema. Raw 是工具特定的序列化载荷 (通常是 tools.Result.Data 的 JSON). Metadata 承载 tenant / session / schema hints 等自由扩展上下文.
为什么不用 any 放 diff: 丢失 SourceTool 自述破坏 LLM prompt / 规则 dispatch / 调试. 为什么不给每工具具体 struct: SQL CAS / DryRun / 未来 HTTP-patch 的 diff 结构不兼容. SourceTool + Raw 是折中 -- dispatch key 加 opaque bytes.
type DiffSizeRule ¶
type DiffSizeRule struct {
MaxRows int
}
DiffSizeRule blocks when Metadata["affected_rows"] exceeds MaxRows. Rule consults Metadata (not Raw) to stay schema-agnostic: the upstream Decorator is responsible for extracting row counts from tool-specific Result payloads and populating this hint.
DiffSizeRule 在 Metadata["affected_rows"] 超过 MaxRows 时阻断. 本 Rule 查 Metadata (不解 Raw) 保持 schema-agnostic: 由上游 Decorator 从工具特定 Result 载荷提 row count 并填入此 hint.
func (*DiffSizeRule) Apply ¶
func (r *DiffSizeRule) Apply(diff DiffInput) RuleResult
Apply checks Metadata["affected_rows"] against MaxRows.
Apply 检查 Metadata["affected_rows"] 是否超过 MaxRows.
func (*DiffSizeRule) Name ¶
func (r *DiffSizeRule) Name() string
Name returns the rule identifier.
Name 返回 rule 标识符.
type FlytoLLMClient ¶
type FlytoLLMClient struct {
// contains filtered or unexported fields
}
FlytoLLMClient wraps a flyto.ModelProvider as a minimal text-in / text-out client for Validator implementations that delegate verdicts to an LLM. It is an independent copy of the evolve package's adapter (same event-aggregation algorithm, different caller label) so validator and evolve remain siblings without a cross-package dependency.
Direction of dependency: validator consumes flyto.ModelProvider; providers/ subpackages define concrete providers. The direction is validator -> flyto.
Temperature limitation (inherited from flyto.Request contract): flyto.Request is the cross-provider greatest common denominator and deliberately omits Temperature. Per-provider knobs live in the provider factory Config (anthropic.Config / minimax.Config / ...). Callers needing per-call temperature control construct separate provider instances with different factory Configs.
Event filtering:
- Aggregated: TextEvent (authoritative), TextDeltaEvent (fallback)
- Error path: ErrorEvent -> wraps ErrValidatorBackend
- Ignored: ToolUse*, Thinking*, Usage, Done, Turn*, SessionInfo, Permission*, Warning, Compact, Checkpoint*
Aggregation precedence (CLEVER): some providers emit both TextDeltaEvent (streaming increments) and TextEvent (the complete text block on block_stop). A naive "sum both" strategy double-counts. TextEvent is authoritative: on each TextEvent arrival we append the final text and reset the delta accumulator. If the stream closes without any TextEvent (providers that skip the complete-block emission) we fall back to the delta accumulator so no text is lost.
FlytoLLMClient 把 flyto.ModelProvider 包装为文本进 / 文本出的最小 客户端, 供把审批决策委托给 LLM 的 Validator 实现使用. 本文件是 evolve 包 adapter 的独立副本 (同事件聚合算法, 不同调用方标签), 让 validator 与 evolve 保持平级不互相依赖.
依赖方向: validator 消费 flyto.ModelProvider; providers/ 子包定义 具体 provider. 方向是 validator -> flyto.
Temperature 限制 (源自 flyto.Request 契约): flyto.Request 是跨 provider 最大公约数, 刻意不含 Temperature. Provider 专有旋钮放在 工厂 Config (anthropic.Config / minimax.Config 等). 需要按次温度 控制的调用方构造不同 Config 的 provider 实例.
事件过滤:
- 聚合: TextEvent (权威), TextDeltaEvent (fallback)
- 错误路径: ErrorEvent -> 包装 ErrValidatorBackend
- 忽略: ToolUse* / Thinking* / Usage / Done / Turn* / ...
聚合优先级 (CLEVER): 部分 provider 同时推 TextDeltaEvent 和 TextEvent, 简单 "都加" 会双倍计. TextEvent 为权威: 每次收到 TextEvent 追加完整 文本并重置 delta 累计. 流关闭时若无任何 TextEvent (跳过 block_stop 推送的 provider) 回退到 delta 累计器避免丢文本.
func NewFlytoLLMClient ¶
func NewFlytoLLMClient(provider flyto.ModelProvider, defaultModel, systemPrompt string) (*FlytoLLMClient, error)
NewFlytoLLMClient builds a FlytoLLMClient. provider is required. defaultModel is used when Complete's model argument is "". systemPrompt is injected verbatim into flyto.Request.System on every Complete call (pass "" to omit).
NewFlytoLLMClient 构造 FlytoLLMClient. provider 必填. defaultModel 在 Complete 的 model 参数为 "" 时使用. systemPrompt 每次 Complete 调用 原样注入 flyto.Request.System (传 "" 忽略).
func (*FlytoLLMClient) Complete ¶
func (c *FlytoLLMClient) Complete(ctx context.Context, prompt string, model string, maxTokens int) (string, error)
Complete sends prompt as a single user message and drains the provider event stream into a response string. On backend failure the returned error wraps ErrValidatorBackend so callers detect the class via errors.Is. An empty model falls back to defaultModel. maxTokens = 0 leaves the field at flyto.Request default.
Complete 以单条 user message 发送 prompt 并聚合 provider 事件流为 响应字符串. backend 失败时返回的 error 以 %w 包装 ErrValidatorBackend, 调用方用 errors.Is 识别. model 为 "" 回退到 defaultModel. maxTokens=0 使用 flyto.Request 默认.
type LLMValidator ¶
type LLMValidator struct {
// contains filtered or unexported fields
}
LLMValidator delegates verdicting to an LLM. The LLM receives a rendered prompt containing DiffInput fields and must return a JSON object shaped like Verdict. LLMValidator parses the response (lenient to markdown fencing and prose preamble) and stamps ValidatorName / PolicyVersion for audit before returning.
Prompt template is deliberately minimal and provider-agnostic: user message contains the diff plus an explicit JSON-only instruction. Structured-output variants (Anthropic tool-use, OpenAI json_schema) are left to future subtypes; this reference implementation covers the broadest provider set with plain JSON.
Errors:
- ErrValidatorBackend: LLM provider call failed
- ErrVerdictParse: response could not be unmarshalled into Verdict
- ctx.Err(): context cancellation bubbles up
Every error path returns a Verdict with Severity=Block so a caller using errors.Is to ignore error class still sees a fail-closed verdict.
LLMValidator 把审批决策委托给 LLM. LLM 接收渲染后的 prompt (含 DiffInput 字段) 返回 JSON 对象 (Verdict 形状). LLMValidator 解析 响应 (宽松处理 markdown 围栏和 prose 前后缀), 返回前打入 ValidatorName / PolicyVersion 审计字段.
Prompt template 刻意简洁且 provider 无关: user message 含 diff + 显式 JSON-only 指令. 结构化输出变体 (Anthropic tool-use / OpenAI json_schema) 留给未来子类; 本参考实现采纯 JSON 覆盖最广 provider.
错误:
- ErrValidatorBackend: LLM provider 调用失败
- ErrVerdictParse: 响应无法 Unmarshal 为 Verdict
- ctx.Err(): context 取消冒泡
所有错误路径返回 Severity=Block Verdict, 调用方即便用 errors.Is 忽略 error class 也能看到 fail-closed 结果.
func NewLLMValidator ¶
func NewLLMValidator(name, policyVersion string, client *FlytoLLMClient, model string, maxTokens int) *LLMValidator
NewLLMValidator constructs an LLMValidator. name / policyVersion flow into every Verdict for audit. client is the provider adapter. model overrides the client's default model for calls from this Validator (empty string uses the client default). maxTokens caps the response length (0 leaves at flyto.Request default).
NewLLMValidator 构造 LLMValidator. name / policyVersion 流进每个 Verdict 供审计. client 是 provider adapter. model 覆盖 client 默认 (空串则用 client 默认). maxTokens 限制响应长度 (0 用 flyto.Request 默认值).
func (*LLMValidator) Name ¶
func (v *LLMValidator) Name() string
Name returns the validator identifier.
Name 返回 validator 标识符.
func (*LLMValidator) Validate ¶
Validate renders a prompt from the diff, calls the LLM, parses the response as a Verdict, and stamps ValidatorName / PolicyVersion before returning. See the type-level godoc for error semantics.
Validate 从 diff 渲染 prompt, 调 LLM, 解析响应为 Verdict, 返回前打入 ValidatorName / PolicyVersion. 错误语义见类型级 godoc.
type PatternRule ¶
PatternRule blocks when Raw matches any forbidden regex Pattern. Typical use: catch destructive SQL keywords (DROP / TRUNCATE / GRANT / ALTER) that should never appear in staging writes.
PatternRule 在 Raw 匹配任一禁止的 regex Pattern 时阻断. 典型用途: 捕获不应在 staging 写操作中出现的破坏性 SQL 关键字 (DROP / TRUNCATE / GRANT / ALTER).
func (*PatternRule) Apply ¶
func (r *PatternRule) Apply(diff DiffInput) RuleResult
Apply scans Raw for any forbidden Pattern.
Apply 扫描 Raw 是否匹配任一禁止 Pattern.
func (*PatternRule) Name ¶
func (r *PatternRule) Name() string
Name returns the rule identifier.
Name 返回 rule 标识符.
type Rule ¶
type Rule interface {
// Name returns a stable identifier used as the key in Verdict.Details.
// Rules within one RuleValidator must not share a Name.
//
// Name 返回稳定标识符, 作为 Verdict.Details 的 key. 同 RuleValidator
// 内多个 Rule 的 Name 不能重复.
Name() string
// Apply runs the check on the diff. Pure: no I/O, no state.
//
// Apply 在 diff 上执行检查. 纯函数: 无 I/O 无状态.
Apply(diff DiffInput) RuleResult
}
Rule is a single check applied by RuleValidator. Rules must be pure -- no network calls, no state mutation. Individual Rules produce RuleResult; RuleValidator aggregates those into a Verdict with worst-case Severity and pass-ratio Score.
Rule 是 RuleValidator 调用的单个检查项. Rule 必须是纯函数 -- 不做 网络调用或变更状态. 单 Rule 产出 RuleResult, RuleValidator 聚合为 Verdict 并取最严格 Severity 与通过率 Score.
type RuleResult ¶
RuleResult is the per-rule decision. Approved=true means this rule passed; when Approved=false, Reason is filled for debuggability and Severity decides whether the failure escalates (Block) or merely flags a warning sample (Warn).
RuleResult 是单 Rule 决策. Approved=true 表示本 rule 通过; Approved= false 时 Reason 填理由便于调试, Severity 决定失败是升级阻断 (Block) 还是仅作为 warning 样本标记 (Warn).
type RuleValidator ¶
type RuleValidator struct {
// contains filtered or unexported fields
}
RuleValidator combines multiple Rules into a single Validator. The aggregated Verdict is Approved only if every Rule approves; Severity is the worst tier among failed rules (Block beats Warn); Score is the pass ratio passed_rules/total_rules, giving downstream circuit breakers a continuous signal instead of a binary flip.
RuleValidator 将多个 Rule 组合成单个 Validator. 仅当所有 Rule 都通 过时聚合 Verdict 才 Approved; Severity 取失败 Rule 中最严格档 (Block 优先于 Warn); Score 为通过率 通过数/总数, 给下游熔断器连续信号而非 二值翻转.
func NewRuleValidator ¶
func NewRuleValidator(name, version string, rules ...Rule) *RuleValidator
NewRuleValidator constructs a RuleValidator. name identifies this instance for audit / VerdictSink routing; version is the policy- bundle tag used by replay audits (pass empty string if the caller does not track versions).
NewRuleValidator 构造 RuleValidator. name 标识本实例用于审计 / VerdictSink 路由; version 是策略包标签供回放审计使用 (不跟踪版本 可传空串).
func (*RuleValidator) Name ¶
func (v *RuleValidator) Name() string
Name returns the Validator's stable identifier.
Name 返回 Validator 稳定标识符.
type Severity ¶
type Severity string
Severity is the disposition tier attached to a Verdict. Warn does not block a commit but feeds the circuit breaker as a sample; Block forces rollback and feeds the breaker as a failure. Two tiers are deliberate -- the engine needs a commit/no-commit decision plus one observability knob, not a 5-level taxonomy.
Severity 是 Verdict 的处置等级. Warn 不阻断 commit 但作为样本喂给 熔断器; Block 强制 rollback 并作为失败喂给熔断器. 两档刻意最小 -- 引擎只需 commit/no-commit 的二值决策加一个可观测旋钮, 不需要 5 级 分类体系.
Severity values. An empty Severity is treated by downstream consumers as Warn (lenient default) to avoid accidental blocking from a Validator that forgot to set the field.
Severity 取值. 空 Severity 下游消费方按 Warn 处理 (宽松默认), 避免 实现忘记设置字段导致误阻断.
type TableWhitelistRule ¶
type TableWhitelistRule struct {
Allowed []string
}
TableWhitelistRule blocks when Metadata["table_name"] is not in the Allowed list. Missing metadata skips the rule with a Warn signal -- the absence is suspicious but not conclusive (another rule may already have caught an unknown source).
TableWhitelistRule 在 Metadata["table_name"] 不在 Allowed 列表时阻断. metadata 缺失按 skip + Warn 处理 -- 缺失可疑但非铁证 (未知来源可能 被其他 rule 捕捉).
func (*TableWhitelistRule) Apply ¶
func (r *TableWhitelistRule) Apply(diff DiffInput) RuleResult
Apply checks Metadata["table_name"] against Allowed.
Apply 检查 Metadata["table_name"] 是否在 Allowed 中.
func (*TableWhitelistRule) Name ¶
func (r *TableWhitelistRule) Name() string
Name returns the rule identifier.
Name 返回 rule 标识符.
type Validator ¶
type Validator interface {
// Name returns a stable identifier used for logging, audit, and
// VerdictSink routing. Two Validators must not share the same Name
// within one process.
//
// Name 返回稳定标识符, 用于日志 / 审计 / VerdictSink 路由.
// 同进程内两个 Validator 不能重名.
Name() string
// Validate reviews the diff and returns a Verdict. On non-nil error
// the caller MUST treat the outcome as Block.
//
// Validate 审查 diff 返回 Verdict. 返回非 nil error 时调用方必须按
// Block 处置.
Validate(ctx context.Context, diff DiffInput) (Verdict, error)
}
Validator checks a staged diff and returns a Verdict. Sync-only by design -- streaming or async variants can be built as wrappers at the orchestrator layer without dragging the interface into lifecycle management. The implementation owns its own retry / timeout / cache policy (an LLM-backed Validator and a pure-rule one have fundamentally different strategies; forcing a common policy here would be a straitjacket).
Validator 校验 staged diff 并返回 Verdict. 仅同步 -- streaming 或异步 变体可在 orchestrator 层做 wrapper, 不把生命周期管理拖入接口. 实现方 自己持有 retry / timeout / cache 策略 (LLM-backed 与纯规则的 Validator 策略本质不同, 在此强制统一反而是紧身衣).
type Verdict ¶
type Verdict struct {
Approved bool
Score float64
Reason string
Severity Severity
Details map[string]any
ValidatorName string
PolicyVersion string
}
Verdict is the Validator output. Approved=true means the diff may commit; false means the caller must rollback. Score in [0, 1] is an advisory confidence that the circuit breaker may use for weighting. Reason is human-readable for audit and UI. Details carries a per-rule or per-dimension breakdown (mirrors evolve.Evaluator breakdown). ValidatorName identifies which Validator produced this Verdict for audit trails; PolicyVersion is the implementation's version tag so replay audits can tell which policy approved a past diff (mirrors evolve.Change.Version).
A non-nil error from Validate is orthogonal to the Verdict -- on error the Validator has not decided, and the caller MUST treat the situation as Block by default (fail-closed).
Verdict 是 Validator 输出. Approved=true 表示 diff 可 commit; false 则调用方必须 rollback. Score 在 [0, 1] 是建议置信度, 熔断器可用于加权. Reason 面向审计和 UI 人类可读. Details 按规则 / 维度分解 (对齐 evolve.Evaluator breakdown). ValidatorName 标识产出此 Verdict 的 Validator 用于审计; PolicyVersion 是实现的版本标签, 回放审计时可判断当时是哪版 策略放行的 (对齐 evolve.Change.Version).
Validate 返回非 nil error 与 Verdict 正交 -- 出错即未决策, 调用方必须 默认按 Block 处置 (fail-closed).