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 则只跑一次。
74 lines
3.0 KiB
Go
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
|
|
}
|