feat(swarm): adopt frozen agent_swarm contract v1 (read-side) (#45/#46)
按 agent_swarm#14/#15 冻结契约做 HM 读侧适配(客户端 #28 消费): - 注册 6 类新事件(event-schema v1):swarm.completed/failed/stopped、approval.approved/rejected、 handoff.created(categories + requiredFields 两表;必填先最小集,待 agent_swarm PR #28 §4 精校)。 - AgentCallbackEvent 增 Sequence(per-swarm 严格递增序号),回调入库透传 envelope.sequence; 事件视图暴露 sequence 供客户端去重/排序。游标仍用稳定 id(next_after)避免 sequence 未全量 上线时回归。 - 脱敏键补 credential_ref/signing_secret_ref(envelope 按设计透传 azkv:// 引用,客户端视图剔除)。 - artifact.created → 扁平视图 {uri,checksum,task_id,size_bytes?,created_at}(无 secret_ref; size 未知省略不伪造)。 - 状态展示映射 §4.1:display_status(blocked→degraded;不臆造 preparing/verifying)。 测试:状态映射、artifact 视图(脱敏 + size 省略)、6 类事件注册;controller+model 全回归通过, go build/vet 干净。文档 §5.2 更新。 未含(下一 PR):stop 真实运行时接入(写路径,复用 agentRuntimeClientConfigForMode("swarm"))。 Affects: Manager only(只读查询契约适配)。AgentCallbackEvent 加列(AutoMigrate);无计费改动。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/heicode/manager/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -44,3 +45,49 @@ func TestSanitizeSwarmPayload_EmptyAndPlain(t *testing.T) {
|
||||
out := sanitizeSwarmPayload(`{"route":"chat","n":3}`)
|
||||
require.Equal(t, "chat", out["route"])
|
||||
}
|
||||
|
||||
// #45/§4.1: 状态展示映射 —— blocked→degraded,其余直通(不臆造 preparing/verifying)。
|
||||
func TestSwarmDisplayStatus(t *testing.T) {
|
||||
require.Equal(t, "degraded", swarmDisplayStatus("blocked"))
|
||||
require.Equal(t, "degraded", swarmDisplayStatus("BLOCKED"))
|
||||
require.Equal(t, "running", swarmDisplayStatus("running"))
|
||||
require.Equal(t, "completed", swarmDisplayStatus("completed"))
|
||||
require.Equal(t, "waiting_approval", swarmDisplayStatus("waiting_approval"))
|
||||
}
|
||||
|
||||
// #15: artifact.created → 扁平视图 {uri,checksum,task_id,created_at}(size 缺省省略);绝不泄 secret_ref。
|
||||
func TestSwarmArtifactView(t *testing.T) {
|
||||
e := model.AgentCallbackEvent{
|
||||
EventID: "evt-a1", Sequence: 5, TaskID: "t9", OccurredAt: "2026-06-10T00:00:00Z",
|
||||
PayloadJSON: `{"uri":"azblob://bucket/x.zip","checksum":"sha256:abc","task_id":"t9","secret_ref":"azkv://kv/secrets/s"}`,
|
||||
}
|
||||
v := swarmArtifactView(e)
|
||||
require.Equal(t, "azblob://bucket/x.zip", v["uri"])
|
||||
require.Equal(t, "sha256:abc", v["checksum"])
|
||||
require.Equal(t, "t9", v["task_id"])
|
||||
require.Equal(t, "2026-06-10T00:00:00Z", v["created_at"]) // payload 无 created_at → 回退 occurred_at
|
||||
_, hasSize := v["size_bytes"]
|
||||
require.False(t, hasSize, "size 未知不应出现")
|
||||
b, _ := json.Marshal(v)
|
||||
require.NotContains(t, string(b), "secret_ref")
|
||||
require.NotContains(t, string(b), "azkv://")
|
||||
|
||||
// 带 size_bytes 时保留
|
||||
e2 := model.AgentCallbackEvent{EventID: "evt-a2", PayloadJSON: `{"uri":"u","checksum":"c","size_bytes":1234,"created_at":"2026-06-10T01:00:00Z"}`}
|
||||
v2 := swarmArtifactView(e2)
|
||||
require.EqualValues(t, 1234, v2["size_bytes"])
|
||||
require.Equal(t, "2026-06-10T01:00:00Z", v2["created_at"])
|
||||
}
|
||||
|
||||
// #15: 6 类新事件已注册(类别 + 必填字段两张表)。
|
||||
func TestSwarmFrozenEventTypesRegistered(t *testing.T) {
|
||||
for _, et := range []string{
|
||||
"swarm.completed", "swarm.failed", "swarm.stopped",
|
||||
"approval.approved", "approval.rejected", "handoff.created",
|
||||
} {
|
||||
_, inCat := agentCallbackEventCategories[et]
|
||||
_, inReq := agentCallbackEventRequiredFields[et]
|
||||
require.True(t, inCat, "event_type %s 应在 categories 注册", et)
|
||||
require.True(t, inReq, "event_type %s 应在 requiredFields 注册", et)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user