Files
knowledge-governance/cmd/worker/library_capture_identity_test.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

74 lines
3.0 KiB
Go

package main
import (
"encoding/json"
"strings"
"testing"
"knowledge-governance/internal/appconfig"
"knowledge-governance/internal/libraryagent"
)
// 期望只从治理侧配置生成;缺 project 就是缺配置,必须在停写入者之前失败。
func TestCaptureExpectationComesFromGovernanceConfigAndFailsClosed(t *testing.T) {
expect, err := captureExpectation(appconfig.Config{LibrarySourceInstance: "kgmaint-weknora", LibrarySourceApp: "kgmaint-app"})
if err != nil {
t.Fatal(err)
}
if expect.Project != "kgmaint-weknora" || expect.SourceApp != "kgmaint-app" {
t.Fatalf("expectation = %+v", expect)
}
if !expect.Complete() {
t.Fatalf("expectation should be complete: %+v", expect)
}
if _, err = captureExpectation(appconfig.Config{}); err == nil ||
!strings.Contains(err.Error(), "LIBRARY_SOURCE_PROJECT") {
t.Fatalf("empty identity accepted: %v", err)
}
// 期望不取自任何执行器应答:显式给 project 就按它绑定,配置里没有别的来源。
explicit, err := captureExpectation(appconfig.Config{LibrarySourceProject: "kgmaint-weknora"})
if err != nil || explicit.Project != "kgmaint-weknora" || explicit.SourceApp != "" {
t.Fatalf("explicit = %+v err=%v", explicit, err)
}
}
// 事后证据防线:回显的期望指纹、project、source_app 任何一项不符都不能登记。
func TestCaptureExecutorPostcheckUsesTheEchoedExpectationFingerprint(t *testing.T) {
expect := libraryagent.NewExpectation("kgmaint-weknora", "kgmaint-app", "")
good := payloadOf(t, "kgmaint-weknora", "kgmaint-app", expect.Fingerprint)
if err := captureExecutorPostcheck(good, expect); err != nil {
t.Fatalf("matching identity rejected: %v", err)
}
swapped := payloadOf(t, "weknora071full", "WKNFull-app", expect.Fingerprint)
if err := captureExecutorPostcheck(swapped, expect); err == nil || !strings.Contains(err.Error(), "另一个实例") {
t.Fatalf("wrong project accepted: %v", err)
}
regenerated := payloadOf(t, "kgmaint-weknora", "kgmaint-app",
libraryagent.NewExpectation("kgmaint-weknora", "kgmaint-app", "/other/archive").Fingerprint)
if err := captureExecutorPostcheck(regenerated, expect); err == nil || !strings.Contains(err.Error(), "期望指纹不一致") {
t.Fatalf("regenerated expectation accepted: %v", err)
}
if err := captureExecutorPostcheck(json.RawMessage(`{"snapshot_id":"abc"}`), expect); err == nil ||
!strings.Contains(err.Error(), "没有执行器身份") {
t.Fatalf("missing identity accepted: %v", err)
}
if err := captureExecutorPostcheck(payloadOf(t, "kgmaint-weknora", "kgseal-app", expect.Fingerprint), expect); err == nil ||
!strings.Contains(err.Error(), "另一个源容器") {
t.Fatalf("wrong source app accepted: %v", err)
}
}
func payloadOf(t *testing.T, project, sourceApp, fingerprint string) json.RawMessage {
t.Helper()
encoded, err := json.Marshal(map[string]any{
"snapshot_id": "abc",
"agent_identity": map[string]any{
"project": project, "source_app": sourceApp, "expectation_fingerprint": fingerprint,
},
})
if err != nil {
t.Fatal(err)
}
return encoded
}