feat: gate releases on platform health

This commit is contained in:
Codex Local
2026-08-26 22:04:38 +08:00
parent 47ef596d3b
commit e4f7701439
12 changed files with 189 additions and 33 deletions
+92 -18
View File
@@ -3,6 +3,8 @@ package diagnostics
import (
"context"
"encoding/json"
"errors"
"fmt"
"regexp"
"sort"
"strings"
@@ -69,6 +71,17 @@ type AcceptanceAlertState struct {
BlockingItemsTruncated bool `json:"blocking_items_truncated"`
}
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"`
}
type AcceptanceBlockingAlert struct {
ID string `json:"id"`
RuleCode string `json:"rule_code"`
@@ -88,24 +101,25 @@ type AcceptanceGate struct {
}
type AcceptanceSnapshot struct {
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"`
Gates []AcceptanceGate `json:"gates"`
OverallGateSatisfied bool `json:"overall_gate_satisfied"`
Disclaimer string `json:"disclaimer"`
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"`
}
func (s *Service) ExportAcceptanceSnapshot(ctx context.Context, principal identity.Principal, tenantPublicID, key, requestID string, input AcceptanceSnapshotInput) (AcceptanceSnapshot, bool, error) {
@@ -275,6 +289,11 @@ func buildAcceptanceSnapshot(ctx context.Context, tx pgx.Tx, tenantPublicID, ten
); err != nil {
return AcceptanceSnapshot{}, err
}
platformHealth, err := loadAcceptancePlatformHealth(ctx, tx, now)
if err != nil {
return AcceptanceSnapshot{}, err
}
snapshot.PlatformHealth = platformHealth
runtimeSatisfied := input.Environment == "LAB" || (snapshot.RuntimeBuild.Version == input.CandidateVersion && snapshot.RuntimeBuild.Commit != "unknown" && snapshot.RuntimeBuild.BuildTime != "unknown")
dependenciesSatisfied := true
@@ -290,6 +309,7 @@ func buildAcceptanceSnapshot(ctx context.Context, tx pgx.Tx, tenantPublicID, ten
{Code: "integration_issues_clear", Satisfied: snapshot.IntegrationIssues.ReleaseGateSatisfied, Summary: "不得存在未关闭的高或严重级联调问题"},
{Code: "outbox_clear", Satisfied: outboxSatisfied, Summary: "租户及平台级待处理、处理中和死信任务必须清零"},
{Code: "critical_alerts_clear", Satisfied: alertsSatisfied, Summary: "不得存在未解决的高或严重级告警"},
{Code: "platform_health_current", Satisfied: snapshot.PlatformHealth.GateSatisfied, Summary: acceptancePlatformHealthSummary(snapshot.PlatformHealth)},
}
snapshot.OverallGateSatisfied = true
for _, gate := range snapshot.Gates {
@@ -298,6 +318,60 @@ func buildAcceptanceSnapshot(ctx context.Context, tx pgx.Tx, tenantPublicID, ten
return snapshot, nil
}
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)
}
func acceptanceRuntimeSummary(environment string) string {
if environment == "LAB" {
return "实验室环境允许开发构建,但候选版本与镜像摘要必须明确"