refactor(rpc): remove unused static Cell artifact path

This commit is contained in:
2026-09-30 13:34:11 +08:00
parent 802f7f8893
commit 2ca1f7113a
6 changed files with 10 additions and 378 deletions
@@ -118,6 +118,7 @@
- Agent 旧失败事实分支:旧 Mock 上传失败事实通过已退役的 `ReportExecutionEvent` RPC 回报,现删除该代码及专属测试。现行录音失败、未知 PUT、重启恢复和最终结果仍由 `recording_delivery*` 隔离测试覆盖;不复活额外通话事件或把未知上传当作成功。
- Agent 旧业务 RPC 处理器:先以服务结构测试复现旧 `GetBootstrap` 等十个方法仍存在,再删除旧执行、许可、控制、查询、事件和上传处理器及仅依赖旧服务面的专属测试;原混合测试保留会话代际、证书指纹、状态和真实 gRPC 激活。另将旧执行日志写入失败保护迁至现行获批执行测试:日志目录不可写时两次相同请求均不得接受或发起呼叫,修复目录后只发起一次,结果未知时拒绝重拨。`go test ./...`、`go test -race ./...`、`go vet ./...`、`go build ./...`、Proto 来源/hash、当前合同及临时 RabbitMQ/HTTPS/双向 TLS 隔离链路均通过;旧执行日志结构、未使用的 Proto 消息与其他引用仍须继续清理,未接触现存业务数据或真实外部服务。
- Agent 旧执行日志根因:新增「首次激活→同一路径重启」测试,复现现行入口曾在初次启动自动写出废弃的 `.executions` 文件,下一次启动又将它识别为旧未交付状态并拒绝服务。移除旧日志写入、回放状态和闲置执行配置,只保留当前 `.approved` 日志、会话代际和旧文件存在时拒绝启动的保护;回归确认首次启动与重启不会产生新旧执行日志。已有 `.executions` 一律保留并失败关闭,不自动清理或猜测其业务内容;当前测试只使用临时目录。
- Agent 旧静态制品入口:现行命令从未提供 `StaticArtifactRaw/Expected`,旧激活分支及只服务于旧 `static-cell-artifact-v0.2` Schema 的手写解析器无法证明 Asterisk 实际加载。结构测试先复现残留,再移除旧 RPC 参数、解析器与专属测试;保留当前隔离 Mock 的 `LoadedSIP` revision 回报及 Dispatcher SIP 版本准入校验。此变更不等于真实 Agent/Asterisk 已加载或管理平台已审批,历史 Schema/来源事实另行辨析。
## 验收台账
-147
View File
@@ -1,147 +0,0 @@
package contract
import (
"encoding/json"
"fmt"
)
// StaticCellArtifact is the management-approved, immutable Cell/SIP hand-off
// artifact. Its project-local JSON shape is static-cell-artifact-v0.2.schema.json; this
// type only provides a typed boundary after schema validation.
type StaticCellArtifact struct {
ArtifactID string `json:"artifact_id"`
SourceRelease string `json:"source_release"`
SourceDigest string `json:"source_digest"`
ApprovalReference string `json:"approval_reference"`
CellID string `json:"cell_id"`
Revision uint64 `json:"revision"`
ConfigSHA256 string `json:"config_sha256"`
Mode string `json:"mode"`
AllowedTargets []string `json:"allowed_targets"`
Trunks []StaticTrunk `json:"trunks"`
ARI *StaticARI `json:"ari,omitempty"`
MediaProfiles map[string]StaticMediaProfile `json:"media_profiles,omitempty"`
Media *StaticMedia `json:"media,omitempty"`
Recording *StaticRecording `json:"recording,omitempty"`
LoadEvidence *StaticLoadEvidence `json:"load_evidence"`
}
type StaticTrunk struct {
TrunkID string `json:"trunk_id"`
ProviderID string `json:"provider_id"`
Codec string `json:"codec"`
CallerProfileIDs []string `json:"caller_profile_ids"`
DialPrefix string `json:"dial_prefix"`
Enabled bool `json:"enabled"`
SIPEndpointRef string `json:"sip_endpoint_ref"`
CredentialRef *string `json:"credential_ref"`
MediaProfileID string `json:"media_profile_id,omitempty"`
}
type StaticARI struct {
BaseURL string `json:"base_url"`
WebsocketURL string `json:"websocket_url"`
Application string `json:"application"`
CredentialRef string `json:"credential_ref"`
}
type StaticMedia struct {
BindAddress string `json:"bind_address"`
Port int `json:"port"`
Format string `json:"format"`
SampleRateHz int `json:"sample_rate_hz"`
Channels int `json:"channels"`
PayloadType int `json:"payload_type"`
}
type StaticMediaProfile struct {
Format string `json:"format"`
SampleRateHz int `json:"sample_rate_hz"`
Channels int `json:"channels"`
PayloadType int `json:"payload_type"`
}
type StaticRecording struct {
Enabled bool `json:"enabled"`
Format string `json:"format"`
Directory string `json:"directory"`
MaxBytes int64 `json:"max_bytes"`
}
type StaticLoadEvidence struct {
AsteriskConfigSHA256 string `json:"asterisk_config_sha256"`
LoadedAt string `json:"loaded_at"`
Status string `json:"status"`
}
// StaticArtifactExpectation contains deployment-local binding constraints.
// Empty string/slice values leave the corresponding optional check disabled;
// the source contract remains mandatory and is always validated first.
type StaticArtifactExpectation struct {
CellID string
Mode string
SourceRelease string
SourceDigest string
ConfigSHA256 string
MinimumRevision uint64
RequiredTrunkIDs []string
}
// ValidateStaticArtifact validates the versioned artifact schema and then
// applies the local Cell hand-off bindings. It deliberately does not claim
// that Asterisk has loaded the artifact: load_evidence.status is explicitly
// "not-yet-loaded" in the contract until an independent load check exists.
func ValidateStaticArtifact(raw []byte, expected StaticArtifactExpectation) (StaticCellArtifact, error) {
if err := validateLocalSchema("static-cell-artifact-v0.2.schema.json", raw); err != nil {
return StaticCellArtifact{}, err
}
var artifact StaticCellArtifact
if err := json.Unmarshal(raw, &artifact); err != nil {
return StaticCellArtifact{}, fmt.Errorf("decode static Cell artifact: %w", err)
}
if expected.CellID != "" && artifact.CellID != expected.CellID {
return StaticCellArtifact{}, fmt.Errorf("static artifact cell binding mismatch: got %q, want %q", artifact.CellID, expected.CellID)
}
if expected.Mode != "" && artifact.Mode != expected.Mode {
return StaticCellArtifact{}, fmt.Errorf("static artifact mode mismatch: got %q, want %q", artifact.Mode, expected.Mode)
}
if expected.SourceRelease != "" && artifact.SourceRelease != expected.SourceRelease {
return StaticCellArtifact{}, fmt.Errorf("static artifact source release mismatch: got %q, want %q", artifact.SourceRelease, expected.SourceRelease)
}
if expected.SourceDigest != "" && artifact.SourceDigest != expected.SourceDigest {
return StaticCellArtifact{}, fmt.Errorf("static artifact source digest mismatch: got %q, want %q", artifact.SourceDigest, expected.SourceDigest)
}
if expected.ConfigSHA256 != "" && artifact.ConfigSHA256 != expected.ConfigSHA256 {
return StaticCellArtifact{}, fmt.Errorf("static artifact config digest mismatch: got %q, want %q", artifact.ConfigSHA256, expected.ConfigSHA256)
}
if expected.MinimumRevision != 0 && artifact.Revision < expected.MinimumRevision {
return StaticCellArtifact{}, fmt.Errorf("static artifact revision %d is older than required %d", artifact.Revision, expected.MinimumRevision)
}
requiredTrunks := make(map[string]struct{}, len(expected.RequiredTrunkIDs))
for _, trunkID := range expected.RequiredTrunkIDs {
requiredTrunks[trunkID] = struct{}{}
}
seenTrunks := make(map[string]struct{}, len(artifact.Trunks))
for _, trunk := range artifact.Trunks {
if _, duplicate := seenTrunks[trunk.TrunkID]; duplicate {
return StaticCellArtifact{}, fmt.Errorf("static artifact contains duplicate trunk_id %q", trunk.TrunkID)
}
seenTrunks[trunk.TrunkID] = struct{}{}
if _, required := requiredTrunks[trunk.TrunkID]; required && !trunk.Enabled {
return StaticCellArtifact{}, fmt.Errorf("required static artifact trunk %q is disabled", trunk.TrunkID)
}
if trunk.MediaProfileID != "" {
if _, ok := artifact.MediaProfiles[trunk.MediaProfileID]; !ok {
return StaticCellArtifact{}, fmt.Errorf("static artifact trunk %q references unknown media profile %q", trunk.TrunkID, trunk.MediaProfileID)
}
}
}
for trunkID := range requiredTrunks {
if _, present := seenTrunks[trunkID]; !present {
return StaticCellArtifact{}, fmt.Errorf("required static artifact trunk %q is missing", trunkID)
}
}
return artifact, nil
}
-141
View File
@@ -1,141 +0,0 @@
package contract
import (
"encoding/json"
"testing"
"git.ipao.vip/rogee/go-sip/contracts"
)
func TestValidateStaticArtifactBindsCellAndTrunks(t *testing.T) {
raw, err := contracts.Files.ReadFile("local/v0.3/examples/static-cell-artifact-v0.2.json")
if err != nil {
t.Fatal(err)
}
artifact, err := ValidateStaticArtifact(raw, StaticArtifactExpectation{
CellID: "cell-a",
Mode: "mock",
SourceRelease: "management-snapshot-1",
SourceDigest: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
ConfigSHA256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
MinimumRevision: 1,
RequiredTrunkIDs: []string{"trunk-mock"},
})
if err != nil {
t.Fatalf("valid artifact rejected: %v", err)
}
if artifact.CellID != "cell-a" || artifact.Revision != 1 || len(artifact.Trunks) != 1 {
t.Fatalf("unexpected artifact: %+v", artifact)
}
}
func TestValidateRealStaticArtifact(t *testing.T) {
raw, err := contracts.Files.ReadFile("local/v0.3/examples/static-cell-artifact-real-v2.json")
if err != nil {
t.Fatal(err)
}
artifact, err := ValidateStaticArtifact(raw, StaticArtifactExpectation{
CellID: "cell-single",
Mode: "real",
SourceRelease: "asterisk-22.10.1-native-v1",
SourceDigest: "68006a1a8efed288be4ca4a2ae3cb9554a31d733eac08eaacf4c646c95faf74d",
ConfigSHA256: "89d2686d0d1ca60159c3c6bd725dc9e6f511cbdb56bf6ce7b65ca7d4dc3f2d60",
RequiredTrunkIDs: []string{"provider-second"},
})
if err != nil {
t.Fatalf("valid real artifact rejected: %v", err)
}
if artifact.ARI == nil || artifact.Media == nil || artifact.Recording == nil {
t.Fatalf("real artifact lost runtime sections: %+v", artifact)
}
if artifact.Media.Format != "alaw" || artifact.Media.SampleRateHz != 8000 || artifact.Media.PayloadType != 8 || artifact.Recording.Format != "wav" {
t.Fatalf("unexpected real media/recording contract: %+v %+v", artifact.Media, artifact.Recording)
}
}
func TestValidateStaticArtifactRejectsBindingViolations(t *testing.T) {
raw, err := contracts.Files.ReadFile("local/v0.3/examples/static-cell-artifact-v0.2.json")
if err != nil {
t.Fatal(err)
}
base := func() StaticArtifactExpectation {
return StaticArtifactExpectation{
CellID: "cell-a",
Mode: "mock",
SourceRelease: "management-snapshot-1",
SourceDigest: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
ConfigSHA256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
}
}
tests := []struct {
name string
expected StaticArtifactExpectation
mutate func(*StaticCellArtifact)
}{
{name: "wrong cell", expected: func() StaticArtifactExpectation { e := base(); e.CellID = "cell-b"; return e }()},
{name: "wrong source digest", expected: func() StaticArtifactExpectation {
e := base()
e.SourceDigest = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
return e
}()},
{name: "old revision", expected: func() StaticArtifactExpectation { e := base(); e.MinimumRevision = 2; return e }()},
{name: "missing required trunk", expected: func() StaticArtifactExpectation {
e := base()
e.RequiredTrunkIDs = []string{"trunk-required"}
return e
}()},
{name: "disabled required trunk", expected: func() StaticArtifactExpectation { e := base(); e.RequiredTrunkIDs = []string{"trunk-mock"}; return e }(), mutate: func(a *StaticCellArtifact) { a.Trunks[0].Enabled = false }},
{name: "duplicate trunk", expected: base(), mutate: func(a *StaticCellArtifact) { a.Trunks = append(a.Trunks, a.Trunks[0]) }},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
candidate := raw
if test.mutate != nil {
var artifact StaticCellArtifact
if err := json.Unmarshal(raw, &artifact); err != nil {
t.Fatal(err)
}
test.mutate(&artifact)
candidate, err = json.Marshal(artifact)
if err != nil {
t.Fatal(err)
}
}
if _, err := ValidateStaticArtifact(candidate, test.expected); err == nil {
t.Fatal("expected static artifact validation to fail")
}
})
}
}
func TestValidateStaticArtifactRejectsRemovedEgressPoolField(t *testing.T) {
old, err := contracts.Files.ReadFile("upstream/v1/examples/static-cell-artifact.json")
if err != nil {
t.Fatal(err)
}
if _, err := ValidateStaticArtifact(old, StaticArtifactExpectation{}); err == nil {
t.Fatal("legacy egress-pool field unexpectedly accepted")
}
}
func TestValidateStaticArtifactAlwaysChecksLocalSchema(t *testing.T) {
raw, err := contracts.Files.ReadFile("local/v0.3/examples/static-cell-artifact-v0.2.json")
if err != nil {
t.Fatal(err)
}
var value map[string]any
if err := json.Unmarshal(raw, &value); err != nil {
t.Fatal(err)
}
value["unexpected"] = true
candidate, err := json.Marshal(value)
if err != nil {
t.Fatal(err)
}
if _, err := ValidateStaticArtifact(candidate, StaticArtifactExpectation{}); err == nil {
t.Fatal("expected schema validation failure")
}
}
-23
View File
@@ -13,7 +13,6 @@ import (
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
"git.ipao.vip/rogee/go-sip/internal/agent"
"git.ipao.vip/rogee/go-sip/internal/contract"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
@@ -27,8 +26,6 @@ import (
type ServerOptions struct {
Mode string
Status *agentpb.AgentStatus
StaticArtifactRaw []byte
StaticArtifactExpected contract.StaticArtifactExpectation
Now func() time.Time
RequirePeerCertificate bool
ApprovedDispatcherID string
@@ -51,9 +48,6 @@ type Server struct {
mode string
now func() time.Time
status *agentpb.AgentStatus
staticArtifact contract.StaticCellArtifact
staticArtifactEnabled bool
staticArtifactError error
requirePeerCertificate bool
approvedDispatcherID string
peerAgentIDs map[string]string
@@ -84,19 +78,10 @@ func NewServer(options ServerOptions) *Server {
if statusValue.AdmissionState == agentpb.AdmissionState_ADMISSION_STATE_UNSPECIFIED {
statusValue.AdmissionState = agentpb.AdmissionState_ADMISSION_STATE_CLOSED
}
var staticArtifact contract.StaticCellArtifact
var staticArtifactError error
staticArtifactEnabled := len(options.StaticArtifactRaw) != 0
if staticArtifactEnabled {
staticArtifact, staticArtifactError = contract.ValidateStaticArtifact(options.StaticArtifactRaw, options.StaticArtifactExpected)
}
server := &Server{
mode: mode,
now: now,
status: statusValue,
staticArtifact: staticArtifact,
staticArtifactEnabled: staticArtifactEnabled,
staticArtifactError: staticArtifactError,
requirePeerCertificate: options.RequirePeerCertificate,
approvedDispatcherID: options.ApprovedDispatcherID,
peerAgentIDs: cloneStringMap(options.PeerAgentIDs),
@@ -350,14 +335,6 @@ func (s *Server) ActivateAgent(ctx context.Context, req *agentpb.ActivateAgentRe
if s.approvedDispatcherID != "" && req.Binding.DispatcherId != s.approvedDispatcherID {
return nil, status.Error(codes.PermissionDenied, "activated Dispatcher identity is not authorized")
}
if s.staticArtifactEnabled {
if s.staticArtifactError != nil {
return nil, status.Errorf(codes.FailedPrecondition, "static Cell artifact is invalid: %v", s.staticArtifactError)
}
if req.Binding.CellId != s.staticArtifact.CellID {
return nil, status.Error(codes.FailedPrecondition, "activation Cell does not match static artifact")
}
}
binding := proto.Clone(req.Binding).(*agentpb.AgentBinding)
if binding.ExpectedBootId == "" {
binding.ExpectedBootId = req.Meta.BootId
+9
View File
@@ -22,6 +22,15 @@ func TestAgentControlServiceOnlyExposesApprovedMethods(t *testing.T) {
}
}
func TestAgentServerUsesLoadedSIPInsteadOfRetiredStaticArtifact(t *testing.T) {
options := reflect.TypeOf(ServerOptions{})
for _, name := range []string{"StaticArtifactRaw", "StaticArtifactExpected"} {
if _, exists := options.FieldByName(name); exists {
t.Fatalf("retired static artifact input %s remains on the Agent server", name)
}
}
}
func TestAgentServerHasNoRetiredBusinessMethods(t *testing.T) {
server := reflect.TypeOf(&Server{})
for _, name := range []string{
-67
View File
@@ -1,67 +0,0 @@
package rpc
import (
"context"
"testing"
"time"
"git.ipao.vip/rogee/go-sip/contracts"
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
"git.ipao.vip/rogee/go-sip/internal/contract"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestActivationValidatesStaticCellArtifact(t *testing.T) {
raw, err := contracts.Files.ReadFile("local/v0.3/examples/static-cell-artifact-v0.2.json")
if err != nil {
t.Fatal(err)
}
server := NewServer(ServerOptions{
Now: func() time.Time { return time.Unix(100, 0) },
StaticArtifactRaw: raw,
StaticArtifactExpected: contract.StaticArtifactExpectation{
CellID: "cell-a",
Mode: "mock",
SourceRelease: "management-snapshot-1",
SourceDigest: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
ConfigSHA256: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
RequiredTrunkIDs: []string{"trunk-mock"},
},
})
meta := testMeta("activate-static", "", 0)
meta.CellId = "cell-a"
response, err := server.ActivateAgent(context.Background(), &agentpb.ActivateAgentRequest{
Meta: meta,
Binding: &agentpb.AgentBinding{AgentId: "agent-1", CellId: "cell-a", ExpectedBootId: "boot-1", DispatcherEpoch: "epoch-1", SessionGeneration: 1},
ActivationOperationId: "activate-static",
})
if err != nil {
t.Fatal(err)
}
if response.State != agentpb.ActivationState_ACTIVATION_STATE_ACTIVE {
t.Fatalf("activation state=%s", response.State)
}
}
func TestActivationRejectsInvalidStaticCellArtifact(t *testing.T) {
raw, err := contracts.Files.ReadFile("local/v0.3/examples/static-cell-artifact-v0.2.json")
if err != nil {
t.Fatal(err)
}
server := NewServer(ServerOptions{
Now: func() time.Time { return time.Unix(100, 0) },
StaticArtifactRaw: raw,
StaticArtifactExpected: contract.StaticArtifactExpectation{CellID: "cell-b", Mode: "mock"},
})
meta := testMeta("activate-invalid-static", "", 0)
meta.CellId = "cell-a"
_, err = server.ActivateAgent(context.Background(), &agentpb.ActivateAgentRequest{
Meta: meta,
Binding: &agentpb.AgentBinding{AgentId: "agent-1", CellId: "cell-a", ExpectedBootId: "boot-1", DispatcherEpoch: "epoch-1", SessionGeneration: 1},
ActivationOperationId: "activate-invalid-static",
})
if err == nil || status.Code(err) != codes.FailedPrecondition {
t.Fatalf("invalid artifact error=%v code=%s", err, status.Code(err))
}
}