Files
knowledge-governance/internal/libraryagent/expectation.go
T
姚章浩 6ed06357f9
build / build (push) Successful in 1m27s
fix(library): 期望执行器身份随 capture 请求送达并在执行前核对
原来 worker 先 health 再 capture,两次拨号之间 socket 可能被换成另一个实例的
执行器:错的 agent 已经把写入者停了、快照也出了,事后 postcheck 只能拒绝登记,
副作用无法撤销。

现在 capture 请求自带期望身份(治理侧配置生成:project 必填,source_app /
archive_root 配了就绑定)与覆盖三项的稳定指纹;接收方在操作互斥内、执行任何源
操作之前比对,缺字段、指纹与字段不一致、project/source_app 不符都直接拒绝,
**错的执行器一次 capture 都不跑**。封存结果回显该指纹,worker 事后复核按它对照,
postcheck 只作为证据防线。恢复路径不要求期望(它只把写入者恢复回来)。

负例是确定性的:两个真实 agent 各占一个 socket,把调用方指向错的那个,断言它的
capture 调用为 0 且仍能回答 health;同一份期望打到对的 socket 则只跑一次。
2026-10-29 13:23:56 +08:00

73 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package libraryagent
import (
"crypto/sha256"
"encoding/hex"
"errors"
"strings"
)
// Expectation 是调用方对"这一条请求该由哪个执行器执行"的声明。
//
// 为什么是调用方声明而不是执行器自报:自报值只能证明"我是谁",不能证明"你找的
// 就是我"。心跳之后、真正停写入者之前,socket 可能被换成另一个 agent;只有把
// 期望随**同一个请求**送进去、由接收方在操作互斥内、执行任何源操作之前比对,
// 才能让"换过 socket"变成零副作用(错的那个 noche 一次 run 都不调)。
type Expectation struct {
Project string `json:"project"`
SourceApp string `json:"source_app,omitempty"`
ArchiveRoot string `json:"archive_root,omitempty"`
// Fingerprint 覆盖上面三项,供接收方回显、调用方事后核对:
// 证明"执行器看到的就是我发的那份期望",而不是被替换后重新生成的。
Fingerprint string `json:"fingerprint"`
}
// NewExpectation 由治理侧配置构造期望(绝不从执行器应答里取字段)。
func NewExpectation(project, sourceApp, archiveRoot string) Expectation {
expectation := Expectation{
Project: strings.TrimSpace(project),
SourceApp: strings.TrimSpace(sourceApp),
ArchiveRoot: strings.TrimSpace(archiveRoot),
}
expectation.Fingerprint = expectation.fingerprint()
return expectation
}
func (e Expectation) fingerprint() string {
sum := sha256.Sum256([]byte(e.Project + "\x00" + e.SourceApp + "\x00" + e.ArchiveRoot))
return "sha256:" + hex.EncodeToString(sum[:])
}
// Complete 表示期望足够具体:没有 project 的期望无法核对身份,必须 fail closed;
// 指纹与字段不一致说明请求在半路被改过。
func (e Expectation) Complete() bool {
return e.Project != "" && e.Fingerprint == e.fingerprint()
}
// matchExpectation 在操作互斥内比对"本执行器配置"与"调用方期望"。
// 只比较期望里给出的字段:project 必填,source_app/archive_root 给了就必须一致。
func (c Config) matchExpectation(expectation Expectation) error {
if !expectation.Complete() {
return errors.New("capture 请求缺少完整的期望执行器身份,拒绝执行")
}
if c.Project != expectation.Project {
return errors.New("执行器与请求期望的实例不一致:" +
"agent=" + quoteOrEmpty(c.Project) + ",期望=" + quoteOrEmpty(expectation.Project) + ",拒绝执行")
}
if expectation.SourceApp != "" && c.SourceApp != expectation.SourceApp {
return errors.New("执行器与请求期望的源容器不一致:" +
"agent=" + quoteOrEmpty(c.SourceApp) + ",期望=" + quoteOrEmpty(expectation.SourceApp) + ",拒绝执行")
}
if expectation.ArchiveRoot != "" && c.ArchiveRoot != expectation.ArchiveRoot {
return errors.New("执行器与请求期望的归档根不一致,拒绝执行")
}
return nil
}
func quoteOrEmpty(value string) string {
if value == "" {
return `""`
}
return `"` + value + `"`
}