2026-08-26 06:41:07 +08:00
package diagnostics
import (
"context"
"encoding/json"
2026-08-26 22:04:38 +08:00
"errors"
"fmt"
2026-08-26 06:41:07 +08:00
"regexp"
"sort"
"strings"
"time"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/buildinfo"
"data-terminal-cloud/internal/idempotency"
"data-terminal-cloud/internal/identity"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
var (
acceptanceCandidatePattern = regexp . MustCompile ( `^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$` )
acceptanceDigestPattern = regexp . MustCompile ( `^sha256:[a-f0-9]{64}$` )
acceptanceEnvironments = map [ string ] bool { "LAB" : true , "STAGING" : true , "PRODUCTION" : true }
acceptanceRunTypes = map [ string ] bool { "REAL_DEVICE_PROVIDER" : true , "PILOT" : true , "PRODUCTION_RELEASE" : true }
acceptanceDependencies = map [ string ] string { "oidc_jwks" : "api" , "ark_tls" : "worker" , "volc_rtc_tls" : "worker" , "content_safety_tls" : "worker" }
)
type AcceptanceSnapshotInput struct {
CandidateVersion string `json:"candidate_version"`
BuildDigest string `json:"build_digest"`
Environment string `json:"environment"`
RunType string `json:"run_type"`
Scope string `json:"scope"`
RequiredDependencies [] string `json:"required_dependencies"`
Reason string `json:"reason"`
}
type AcceptanceDatabaseState struct {
MigrationVersion int64 `json:"migration_version"`
Dirty bool `json:"dirty"`
}
type AcceptanceDependencyState struct {
ServiceName string `json:"service_name"`
Dependency string `json:"dependency"`
Status string `json:"status"`
CheckedAt * time . Time `json:"checked_at,omitempty"`
LastSuccessAt * time . Time `json:"last_success_at,omitempty"`
LastFailureAt * time . Time `json:"last_failure_at,omitempty"`
LastErrorCode string `json:"last_error_code,omitempty"`
LatencyMS int64 `json:"latency_ms"`
ProbeIntervalMS int64 `json:"probe_interval_ms"`
ConsecutiveFailures int `json:"consecutive_failures"`
Fresh bool `json:"fresh"`
GateSatisfied bool `json:"gate_satisfied"`
}
type AcceptanceOutboxState struct {
Pending int64 `json:"pending"`
Processing int64 `json:"processing"`
Dead int64 `json:"dead"`
}
type AcceptanceAlertState struct {
CriticalOpen int64 `json:"critical_open"`
HighOpen int64 `json:"high_open"`
Blocking int64 `json:"blocking"`
BlockingItems [] AcceptanceBlockingAlert `json:"blocking_items"`
BlockingItemsTruncated bool `json:"blocking_items_truncated"`
}
2026-08-26 22:04:38 +08:00
type AcceptancePlatformHealthState struct {
State string `json:"state"`
Status string `json:"status,omitempty"`
ObservedAt * time . Time `json:"observed_at,omitempty"`
ReceivedAt * time . Time `json:"received_at,omitempty"`
CheckCount int `json:"check_count"`
WarningCount int `json:"warning_count"`
CriticalCount int `json:"critical_count"`
GateSatisfied bool `json:"gate_satisfied"`
}
2026-08-26 06:41:07 +08:00
type AcceptanceBlockingAlert struct {
ID string `json:"id"`
RuleCode string `json:"rule_code"`
Severity string `json:"severity"`
Status string `json:"status"`
ResourceType string `json:"resource_type,omitempty"`
ResourceID string `json:"resource_id,omitempty"`
ResourceState string `json:"resource_state"`
FirstDetectedAt time . Time `json:"first_detected_at"`
LastDetectedAt time . Time `json:"last_detected_at"`
}
type AcceptanceGate struct {
Code string `json:"code"`
Satisfied bool `json:"satisfied"`
Summary string `json:"summary"`
}
type AcceptanceSnapshot struct {
2026-08-26 22:04:38 +08:00
ID string `json:"id"`
GeneratedAt time . Time `json:"generated_at"`
TenantID string `json:"tenant_id"`
CandidateVersion string `json:"candidate_version"`
BuildDigest string `json:"build_digest"`
Environment string `json:"environment"`
RunType string `json:"run_type"`
Scope string `json:"scope"`
RedactionPolicy string `json:"redaction_policy"`
RuntimeBuild buildinfo . Info `json:"runtime_build"`
Database AcceptanceDatabaseState `json:"database"`
IntegrationIssues IntegrationIssueSummary `json:"integration_issues"`
ExternalDependencies [] AcceptanceDependencyState `json:"external_dependencies"`
Outbox AcceptanceOutboxState `json:"outbox"`
Alerts AcceptanceAlertState `json:"alerts"`
PlatformHealth AcceptancePlatformHealthState `json:"platform_health"`
Gates [] AcceptanceGate `json:"gates"`
OverallGateSatisfied bool `json:"overall_gate_satisfied"`
Disclaimer string `json:"disclaimer"`
2026-08-26 06:41:07 +08:00
}
func ( s * Service ) ExportAcceptanceSnapshot ( ctx context . Context , principal identity . Principal , tenantPublicID , key , requestID string , input AcceptanceSnapshotInput ) ( AcceptanceSnapshot , bool , error ) {
normalizeAcceptanceSnapshotInput ( & input )
if ! validAcceptanceSnapshotInput ( input ) {
return AcceptanceSnapshot {}, false , ErrInvalid
}
a , err := s . resolveActor ( ctx , principal , tenantPublicID , "ops_operator" , "ops_admin" )
if err != nil {
return AcceptanceSnapshot {}, false , err
}
requestBody , _ := json . Marshal ( input )
result , err := s . idem . Execute ( ctx , idempotency . Request {
TenantID : & a . tenantID , ActorKey : "ops:" + a . userPublicID ,
Scope : "POST /api/v1/ops/diagnostics/acceptance-snapshots" , Key : key ,
RequestHash : idempotency . Hash ( requestBody ), ExpiresAfter : 24 * time . Hour ,
}, func ( operationContext context . Context , tx pgx . Tx ) ( idempotency . Result , error ) {
snapshot , snapshotErr := buildAcceptanceSnapshot ( operationContext , tx , tenantPublicID , a . tenantID , input )
if snapshotErr != nil {
return idempotency . Result {}, snapshotErr
}
if auditErr := s . audit . Record ( operationContext , tx , audit . Event {
TenantID : & a . tenantID , ActorType : "user" , ActorPublicID : & a . userPublicID ,
Action : "ops.diagnostics.acceptance_snapshot.export" , ResourceType : "acceptance_snapshot" , ResourceID : & snapshot . ID ,
RequestID : requestID , Purpose : input . Reason , Result : "success" ,
Detail : map [ string ] any {
"candidate_version" : input . CandidateVersion , "build_digest" : input . BuildDigest ,
"environment" : input . Environment , "run_type" : input . RunType , "scope" : input . Scope ,
"required_dependencies" : input . RequiredDependencies , "overall_gate_satisfied" : snapshot . OverallGateSatisfied ,
"redaction_policy" : snapshot . RedactionPolicy ,
},
}); auditErr != nil {
return idempotency . Result {}, auditErr
}
encoded , _ := json . Marshal ( snapshot )
return idempotency . Result { StatusCode : 201 , Body : encoded , ResourceID : & snapshot . ID }, nil
})
if err != nil {
return AcceptanceSnapshot {}, false , err
}
var snapshot AcceptanceSnapshot
if err = json . Unmarshal ( result . Body , & snapshot ); err != nil {
return AcceptanceSnapshot {}, false , err
}
return snapshot , ! result . Replayed , nil
}
func normalizeAcceptanceSnapshotInput ( input * AcceptanceSnapshotInput ) {
input . CandidateVersion = strings . TrimSpace ( input . CandidateVersion )
input . BuildDigest = strings . ToLower ( strings . TrimSpace ( input . BuildDigest ))
input . Environment = strings . ToUpper ( strings . TrimSpace ( input . Environment ))
input . RunType = strings . ToUpper ( strings . TrimSpace ( input . RunType ))
input . Scope = strings . TrimSpace ( input . Scope )
input . Reason = strings . TrimSpace ( input . Reason )
for index := range input . RequiredDependencies {
input . RequiredDependencies [ index ] = strings . ToLower ( strings . TrimSpace ( input . RequiredDependencies [ index ]))
}
sort . Strings ( input . RequiredDependencies )
}
func validAcceptanceSnapshotInput ( input AcceptanceSnapshotInput ) bool {
if ! acceptanceCandidatePattern . MatchString ( input . CandidateVersion ) || ! acceptanceDigestPattern . MatchString ( input . BuildDigest ) ||
! acceptanceEnvironments [ input . Environment ] || ! acceptanceRunTypes [ input . RunType ] ||
input . Scope == "" || len ([] rune ( input . Scope )) > 500 || input . Reason == "" || len ([] rune ( input . Reason )) > 255 ||
len ( input . RequiredDependencies ) == 0 || len ( input . RequiredDependencies ) > len ( acceptanceDependencies ) {
return false
}
seen := map [ string ] bool {}
for _ , dependency := range input . RequiredDependencies {
if _ , allowed := acceptanceDependencies [ dependency ]; ! allowed || seen [ dependency ] {
return false
}
seen [ dependency ] = true
}
return true
}
func buildAcceptanceSnapshot ( ctx context . Context , tx pgx . Tx , tenantPublicID , tenantInternalID string , input AcceptanceSnapshotInput ) ( AcceptanceSnapshot , error ) {
now := time . Now (). UTC ()
snapshot := AcceptanceSnapshot {
ID : uuid . NewString (), GeneratedAt : now , TenantID : tenantPublicID ,
CandidateVersion : input . CandidateVersion , BuildDigest : input . BuildDigest , Environment : input . Environment ,
RunType : input . RunType , Scope : input . Scope , RedactionPolicy : "ops-acceptance-minimal-v1" ,
RuntimeBuild : buildinfo . Current (), Alerts : AcceptanceAlertState { BlockingItems : [] AcceptanceBlockingAlert {}},
Disclaimer : "本快照仅证明生成时刻的技术预检状态,不替代真实设备、真实供应商、灰度观察或发布审批结论。" ,
}
if err := tx . QueryRow ( ctx , `SELECT version,dirty FROM schema_migrations ORDER BY version DESC LIMIT 1` ). Scan ( & snapshot . Database . MigrationVersion , & snapshot . Database . Dirty ); err != nil {
return AcceptanceSnapshot {}, err
}
if err := tx . QueryRow ( ctx , `
SELECT count(*),
count(*) FILTER (WHERE status <> 'CLOSED'),
count(*) FILTER (WHERE severity='CRITICAL' AND status <> 'CLOSED'),
count(*) FILTER (WHERE severity='HIGH' AND status <> 'CLOSED'),
count(*) FILTER (WHERE severity IN ('CRITICAL','HIGH') AND status <> 'CLOSED')
FROM integration_issue WHERE tenant_id=$1::uuid` , tenantInternalID ). Scan (
& snapshot . IntegrationIssues . Total , & snapshot . IntegrationIssues . Open , & snapshot . IntegrationIssues . CriticalOpen ,
& snapshot . IntegrationIssues . HighOpen , & snapshot . IntegrationIssues . ReleaseBlocking ,
); err != nil {
return AcceptanceSnapshot {}, err
}
snapshot . IntegrationIssues . ReleaseGateSatisfied = snapshot . IntegrationIssues . ReleaseBlocking == 0
for _ , dependency := range input . RequiredDependencies {
state := AcceptanceDependencyState { ServiceName : acceptanceDependencies [ dependency ], Dependency : dependency , Status : "MISSING" }
var checkedAt time . Time
err := tx . QueryRow ( ctx , `SELECT status,checked_at,last_success_at,last_failure_at,COALESCE(last_error_code,''),latency_ms,probe_interval_ms,consecutive_failures FROM external_dependency_probe_state WHERE service_name=$1 AND dependency=$2` , state . ServiceName , dependency ). Scan (
& state . Status , & checkedAt , & state . LastSuccessAt , & state . LastFailureAt , & state . LastErrorCode ,
& state . LatencyMS , & state . ProbeIntervalMS , & state . ConsecutiveFailures ,
)
if err != nil && err != pgx . ErrNoRows {
return AcceptanceSnapshot {}, err
}
if err == nil {
state . CheckedAt = & checkedAt
state . Fresh = ! checkedAt . After ( now ) && now . Sub ( checkedAt ) <= 3 * time . Duration ( state . ProbeIntervalMS ) * time . Millisecond
state . GateSatisfied = state . Status == "AVAILABLE" && state . Fresh
}
snapshot . ExternalDependencies = append ( snapshot . ExternalDependencies , state )
}
if err := tx . QueryRow ( ctx , `SELECT
count(*) FILTER (WHERE status='pending'),
count(*) FILTER (WHERE status='processing'),
count(*) FILTER (WHERE status='dead')
FROM outbox_event WHERE tenant_id=$1::uuid OR tenant_id IS NULL` , tenantInternalID ). Scan (
& snapshot . Outbox . Pending , & snapshot . Outbox . Processing , & snapshot . Outbox . Dead ,
); err != nil {
return AcceptanceSnapshot {}, err
}
rows , err := tx . Query ( ctx , `SELECT public_id::text,rule_code,severity,status,COALESCE(resource_type,''),COALESCE(resource_public_id::text,''),
CASE
WHEN resource_public_id IS NULL THEN 'AGGREGATE'
WHEN resource_type='conversation' THEN CASE WHEN EXISTS(SELECT 1 FROM conversation c WHERE c.public_id=alert_incident.resource_public_id) THEN 'PRESENT' ELSE 'MISSING' END
WHEN resource_type='privacy_request' THEN CASE WHEN EXISTS(SELECT 1 FROM privacy_request pr WHERE pr.public_id=alert_incident.resource_public_id) THEN 'PRESENT' ELSE 'MISSING' END
WHEN resource_type='upload_batch' THEN CASE WHEN EXISTS(SELECT 1 FROM upload_batch ub WHERE ub.public_id=alert_incident.resource_public_id) THEN 'PRESENT' ELSE 'MISSING' END
WHEN resource_type='firmware_rollout' THEN CASE WHEN EXISTS(SELECT 1 FROM firmware_rollout fr WHERE fr.public_id=alert_incident.resource_public_id) THEN 'PRESENT' ELSE 'MISSING' END
WHEN resource_type='ops_resource_reference' THEN CASE WHEN EXISTS(SELECT 1 FROM ops_resource_reference rr WHERE rr.public_id=alert_incident.resource_public_id) THEN 'PRESENT' ELSE 'MISSING' END
ELSE 'UNKNOWN'
END,first_detected_at,last_detected_at
FROM alert_incident
WHERE tenant_id=$1::uuid AND status<>'RESOLVED' AND severity IN ('CRITICAL','HIGH')
ORDER BY CASE severity WHEN 'CRITICAL' THEN 0 ELSE 1 END,first_detected_at,public_id
LIMIT 100` , tenantInternalID )
if err != nil {
return AcceptanceSnapshot {}, err
}
for rows . Next () {
var item AcceptanceBlockingAlert
if err = rows . Scan ( & item . ID , & item . RuleCode , & item . Severity , & item . Status , & item . ResourceType , & item . ResourceID , & item . ResourceState , & item . FirstDetectedAt , & item . LastDetectedAt ); err != nil {
rows . Close ()
return AcceptanceSnapshot {}, err
}
snapshot . Alerts . BlockingItems = append ( snapshot . Alerts . BlockingItems , item )
}
if err = rows . Err (); err != nil {
rows . Close ()
return AcceptanceSnapshot {}, err
}
rows . Close ()
snapshot . Alerts . BlockingItemsTruncated = snapshot . Alerts . Blocking > int64 ( len ( snapshot . Alerts . BlockingItems ))
if err := tx . QueryRow ( ctx , `SELECT
count(*) FILTER (WHERE severity='CRITICAL'),
count(*) FILTER (WHERE severity='HIGH'),
count(*)
FROM alert_incident WHERE tenant_id=$1::uuid AND status<>'RESOLVED' AND severity IN ('CRITICAL','HIGH')` , tenantInternalID ). Scan (
& snapshot . Alerts . CriticalOpen , & snapshot . Alerts . HighOpen , & snapshot . Alerts . Blocking ,
); err != nil {
return AcceptanceSnapshot {}, err
}
2026-08-26 22:04:38 +08:00
platformHealth , err := loadAcceptancePlatformHealth ( ctx , tx , now )
if err != nil {
return AcceptanceSnapshot {}, err
}
snapshot . PlatformHealth = platformHealth
2026-08-26 06:41:07 +08:00
runtimeSatisfied := input . Environment == "LAB" || ( snapshot . RuntimeBuild . Version == input . CandidateVersion && snapshot . RuntimeBuild . Commit != "unknown" && snapshot . RuntimeBuild . BuildTime != "unknown" )
dependenciesSatisfied := true
for _ , dependency := range snapshot . ExternalDependencies {
dependenciesSatisfied = dependenciesSatisfied && dependency . GateSatisfied
}
outboxSatisfied := snapshot . Outbox . Pending == 0 && snapshot . Outbox . Processing == 0 && snapshot . Outbox . Dead == 0
alertsSatisfied := snapshot . Alerts . Blocking == 0
snapshot . Gates = [] AcceptanceGate {
{ Code : "candidate_build_identified" , Satisfied : runtimeSatisfied , Summary : acceptanceRuntimeSummary ( input . Environment )},
{ Code : "database_migration_clean" , Satisfied : ! snapshot . Database . Dirty , Summary : "数据库迁移必须处于非脏状态" },
{ Code : "required_dependencies_available" , Satisfied : dependenciesSatisfied , Summary : "所有必需外部依赖探针必须可用且未过期" },
{ Code : "integration_issues_clear" , Satisfied : snapshot . IntegrationIssues . ReleaseGateSatisfied , Summary : "不得存在未关闭的高或严重级联调问题" },
{ Code : "outbox_clear" , Satisfied : outboxSatisfied , Summary : "租户及平台级待处理、处理中和死信任务必须清零" },
{ Code : "critical_alerts_clear" , Satisfied : alertsSatisfied , Summary : "不得存在未解决的高或严重级告警" },
2026-08-26 22:04:38 +08:00
{ Code : "platform_health_current" , Satisfied : snapshot . PlatformHealth . GateSatisfied , Summary : acceptancePlatformHealthSummary ( snapshot . PlatformHealth )},
2026-08-26 06:41:07 +08:00
}
snapshot . OverallGateSatisfied = true
for _ , gate := range snapshot . Gates {
snapshot . OverallGateSatisfied = snapshot . OverallGateSatisfied && gate . Satisfied
}
return snapshot , nil
}
2026-08-26 22:04:38 +08:00
func loadAcceptancePlatformHealth ( ctx context . Context , tx pgx . Tx , now time . Time ) ( AcceptancePlatformHealthState , error ) {
state := AcceptancePlatformHealthState { State : "MISSING" }
var observedAt , receivedAt time . Time
var checksJSON [] byte
err := tx . QueryRow ( ctx , `
SELECT status,observed_at,received_at,checks
FROM platform_health_snapshot ORDER BY observed_at DESC,id DESC LIMIT 1` ). Scan (
& state . Status , & observedAt , & receivedAt , & checksJSON ,
)
if errors . Is ( err , pgx . ErrNoRows ) {
return state , nil
}
if err != nil {
return AcceptancePlatformHealthState {}, err
}
var checks [] PlatformHealthCheck
if err = json . Unmarshal ( checksJSON , & checks ); err != nil {
return AcceptancePlatformHealthState {}, err
}
return evaluateAcceptancePlatformHealth ( state . Status , observedAt , receivedAt , checks , now ), nil
}
func evaluateAcceptancePlatformHealth ( status string , observedAt , receivedAt time . Time , checks [] PlatformHealthCheck , now time . Time ) AcceptancePlatformHealthState {
state := AcceptancePlatformHealthState {
State : "CURRENT" , Status : status , ObservedAt : & observedAt , ReceivedAt : & receivedAt , CheckCount : len ( checks ),
}
for _ , check := range checks {
switch check . Status {
case "warning" :
state . WarningCount ++
case "critical" :
state . CriticalCount ++
}
}
if observedAt . Before ( now . Add ( - platformHealthMaxAge )) || observedAt . After ( now . Add ( platformHealthFuture )) {
state . State = "STALE"
}
state . GateSatisfied = state . State == "CURRENT" && state . Status == "healthy" && state . WarningCount == 0 && state . CriticalCount == 0
return state
}
func acceptancePlatformHealthSummary ( state AcceptancePlatformHealthState ) string {
switch state . State {
case "MISSING" :
return "尚未收到云端主机健康快照"
case "STALE" :
return "云端主机健康快照必须在15分钟内更新"
}
if state . Status != "healthy" || state . WarningCount > 0 || state . CriticalCount > 0 {
return fmt . Sprintf ( "云端主机存在%d项预警和%d项严重异常" , state . WarningCount , state . CriticalCount )
}
return fmt . Sprintf ( "云端主机健康快照当前有效,共%d项检查" , state . CheckCount )
}
2026-08-26 06:41:07 +08:00
func acceptanceRuntimeSummary ( environment string ) string {
if environment == "LAB" {
return "实验室环境允许开发构建,但候选版本与镜像摘要必须明确"
}
return "预发布和生产环境的运行版本必须等于候选版本,并包含提交与构建时间"
}