Files
go-sip/internal/ai/mq_config.go
T

75 lines
2.9 KiB
Go

package ai
import (
"encoding/json"
"errors"
"fmt"
"time"
"git.ipao.vip/rogee/go-sip/internal/contract"
)
// ValidateConfigResponse binds an incoming configuration to the original,
// persisted request. It does not infer authorization from a published version.
func ValidateConfigResponse(raw []byte, request contract.ServiceMessage, now time.Time) (Snapshot, error) {
if request.MessageType != "ai.config.request" {
return Snapshot{}, errors.New("expected original AI configuration request")
}
response, err := contract.DecodeService(raw)
if err != nil {
return Snapshot{}, err
}
if response.MessageType != "ai.config.result" || response.CorrelationID != request.MessageID || response.DispatcherID != request.DispatcherID || response.TenantID != request.TenantID || response.TenantKey != request.TenantKey {
return Snapshot{}, errors.New("AI configuration response binding mismatch")
}
deadline, err := time.Parse(time.RFC3339Nano, request.NotAfter)
if err != nil {
return Snapshot{}, fmt.Errorf("AI request deadline: %w", err)
}
issued, err := time.Parse(time.RFC3339Nano, request.IssuedAt)
if err != nil {
return Snapshot{}, err
}
responseTime, err := time.Parse(time.RFC3339Nano, response.IssuedAt)
if err != nil {
return Snapshot{}, err
}
if !now.Before(deadline) || now.Before(issued) || responseTime.Before(issued) || responseTime.After(now) {
return Snapshot{}, errors.New("AI configuration response is outside the request window")
}
if response.Status != "ok" {
return Snapshot{}, fmt.Errorf("AI configuration request rejected: %s", response.ReasonCode)
}
var requested struct {
AgentVersionID string `json:"agent_version_id"`
}
if err := json.Unmarshal(request.Payload, &requested); err != nil {
return Snapshot{}, err
}
var payload struct {
Snapshot struct {
AgentVersionID string `json:"agent_version_id"`
Status string `json:"status"`
TenantID string `json:"tenant_id"`
Immutable bool `json:"immutable"`
ContentSHA256 string `json:"content_sha256"`
Config json.RawMessage `json:"config"`
} `json:"snapshot"`
}
if err := json.Unmarshal(response.Payload, &payload); err != nil {
return Snapshot{}, err
}
if (payload.Snapshot.Status != "published" && payload.Snapshot.Status != "reused") || payload.Snapshot.AgentVersionID != requested.AgentVersionID || payload.Snapshot.TenantID != request.TenantID || !payload.Snapshot.Immutable {
return Snapshot{}, errors.New("AI snapshot publication, version, tenant or immutability mismatch")
}
snapshot, err := Validate(payload.Snapshot.Config)
if err != nil {
return Snapshot{}, err
}
if snapshot.AgentVersionID != requested.AgentVersionID || snapshot.Digest != payload.Snapshot.ContentSHA256 {
return Snapshot{}, errors.New("AI configuration version or canonical digest mismatch")
}
snapshot.TenantKey = request.TenantKey
return snapshot, nil
}