6ed06357f9
build / build (push) Successful in 1m27s
原来 worker 先 health 再 capture,两次拨号之间 socket 可能被换成另一个实例的 执行器:错的 agent 已经把写入者停了、快照也出了,事后 postcheck 只能拒绝登记, 副作用无法撤销。 现在 capture 请求自带期望身份(治理侧配置生成:project 必填,source_app / archive_root 配了就绑定)与覆盖三项的稳定指纹;接收方在操作互斥内、执行任何源 操作之前比对,缺字段、指纹与字段不一致、project/source_app 不符都直接拒绝, **错的执行器一次 capture 都不跑**。封存结果回显该指纹,worker 事后复核按它对照, postcheck 只作为证据防线。恢复路径不要求期望(它只把写入者恢复回来)。 负例是确定性的:两个真实 agent 各占一个 socket,把调用方指向错的那个,断言它的 capture 调用为 0 且仍能回答 health;同一份期望打到对的 socket 则只跑一次。
70 lines
3.2 KiB
Go
70 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"knowledge-governance/internal/appconfig"
|
|
"knowledge-governance/internal/libraryagent"
|
|
)
|
|
|
|
// 封存的身份契约:期望随**同一个 capture 请求**送进 agent,由接收方在操作互斥内、
|
|
// 任何源操作之前比对;worker 事后按回显的指纹再复核一次。
|
|
//
|
|
// 为什么不能在 worker 侧先 Health 再 Capture:那是两次拨号,中间 socket 可能被换成
|
|
// 另一个 agent,错的执行器已经把写入者停了才被拒登记——副作用已经发生。这里把期望
|
|
// 放进请求本身,换过的 agent 会先比对身份、比对失败就一次 run 都不调。
|
|
//
|
|
// 期望字段只来自治理侧配置(LIBRARY_SOURCE_*),绝不从执行器自报值生成。
|
|
|
|
// captureExpectation 把治理侧配置翻译成期望;缺身份就是缺配置,调用方必须失败。
|
|
func captureExpectation(config appconfig.Config) (libraryagent.Expectation, error) {
|
|
expectation := libraryagent.NewExpectation(config.SourceProject(), config.LibrarySourceApp, "")
|
|
if expectation.Project == "" {
|
|
return libraryagent.Expectation{}, errors.New("未配置封存来源实例身份(LIBRARY_SOURCE_PROJECT),拒绝在不核对执行器的情况下停写入者")
|
|
}
|
|
return expectation, nil
|
|
}
|
|
|
|
// captureExecutorPostcheck 是事后证据防线:结果里回显的期望指纹必须等于我们发出的,
|
|
// 自报身份也必须与期望一致。它挡不住副作用,所以不能替代请求内的核对。
|
|
func captureExecutorPostcheck(payload json.RawMessage, expectation libraryagent.Expectation) error {
|
|
if len(payload) == 0 {
|
|
return errors.New("封存结果里没有执行器身份,无法证明这次封存来自期望实例")
|
|
}
|
|
var document struct {
|
|
Identity struct {
|
|
Project string `json:"project"`
|
|
SourceApp string `json:"source_app"`
|
|
ExpectationFingerprint string `json:"expectation_fingerprint"`
|
|
} `json:"agent_identity"`
|
|
}
|
|
if err := json.Unmarshal(payload, &document); err != nil {
|
|
return errors.New("封存结果里的执行器身份无法解析")
|
|
}
|
|
identity := document.Identity
|
|
if identity.Project == "" {
|
|
return errors.New("封存结果里没有执行器身份,无法证明这次封存来自期望实例")
|
|
}
|
|
if identity.ExpectationFingerprint != expectation.Fingerprint {
|
|
return fmt.Errorf("封存结果回显的期望指纹不一致(agent=%q,期望=%q),该快照不能当作本实例的测试封存",
|
|
identity.ExpectationFingerprint, expectation.Fingerprint)
|
|
}
|
|
if identity.Project != expectation.Project {
|
|
return fmt.Errorf("封存结果来自另一个实例(agent=%q,期望=%q),该快照不能当作本实例的测试封存",
|
|
identity.Project, expectation.Project)
|
|
}
|
|
if expectation.SourceApp != "" && identity.SourceApp != expectation.SourceApp {
|
|
return fmt.Errorf("封存结果来自另一个源容器(agent=%q,期望=%q),该快照不能当作本实例的测试封存",
|
|
identity.SourceApp, expectation.SourceApp)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// expectationFingerprint 用于日志/证据行的稳定标识,不含路径与凭据。
|
|
func expectationFingerprint(expectation libraryagent.Expectation) string {
|
|
return strings.TrimSpace(expectation.Fingerprint)
|
|
}
|