feat: require production infrastructure evidence
This commit is contained in:
@@ -82,6 +82,15 @@ type AcceptancePlatformHealthState struct {
|
||||
GateSatisfied bool `json:"gate_satisfied"`
|
||||
}
|
||||
|
||||
type AcceptanceProductionInfrastructureState struct {
|
||||
Applicable bool `json:"applicable"`
|
||||
DeploymentProfile string `json:"deployment_profile"`
|
||||
OffsiteBackupReady bool `json:"offsite_backup_ready"`
|
||||
ExternalAlertReceiverReady bool `json:"external_alert_receiver_ready"`
|
||||
MockServicesDisabled bool `json:"mock_services_disabled"`
|
||||
GateSatisfied bool `json:"gate_satisfied"`
|
||||
}
|
||||
|
||||
type AcceptanceBlockingAlert struct {
|
||||
ID string `json:"id"`
|
||||
RuleCode string `json:"rule_code"`
|
||||
@@ -101,25 +110,26 @@ 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"`
|
||||
PlatformHealth AcceptancePlatformHealthState `json:"platform_health"`
|
||||
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"`
|
||||
ProductionInfrastructure AcceptanceProductionInfrastructureState `json:"production_infrastructure"`
|
||||
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) {
|
||||
@@ -289,11 +299,12 @@ func buildAcceptanceSnapshot(ctx context.Context, tx pgx.Tx, tenantPublicID, ten
|
||||
); err != nil {
|
||||
return AcceptanceSnapshot{}, err
|
||||
}
|
||||
platformHealth, err := loadAcceptancePlatformHealth(ctx, tx, now)
|
||||
platformHealth, productionInfrastructure, err := loadAcceptancePlatformHealth(ctx, tx, now, input.Environment)
|
||||
if err != nil {
|
||||
return AcceptanceSnapshot{}, err
|
||||
}
|
||||
snapshot.PlatformHealth = platformHealth
|
||||
snapshot.ProductionInfrastructure = productionInfrastructure
|
||||
|
||||
runtimeSatisfied := input.Environment == "LAB" || (snapshot.RuntimeBuild.Version == input.CandidateVersion && snapshot.RuntimeBuild.Commit != "unknown" && snapshot.RuntimeBuild.BuildTime != "unknown")
|
||||
dependenciesSatisfied := true
|
||||
@@ -310,6 +321,7 @@ func buildAcceptanceSnapshot(ctx context.Context, tx pgx.Tx, tenantPublicID, ten
|
||||
{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)},
|
||||
{Code: "production_infrastructure_ready", Satisfied: snapshot.ProductionInfrastructure.GateSatisfied, Summary: acceptanceProductionInfrastructureSummary(snapshot.ProductionInfrastructure)},
|
||||
}
|
||||
snapshot.OverallGateSatisfied = true
|
||||
for _, gate := range snapshot.Gates {
|
||||
@@ -318,7 +330,7 @@ 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) {
|
||||
func loadAcceptancePlatformHealth(ctx context.Context, tx pgx.Tx, now time.Time, environment string) (AcceptancePlatformHealthState, AcceptanceProductionInfrastructureState, error) {
|
||||
state := AcceptancePlatformHealthState{State: "MISSING"}
|
||||
var observedAt, receivedAt time.Time
|
||||
var checksJSON []byte
|
||||
@@ -328,16 +340,16 @@ func loadAcceptancePlatformHealth(ctx context.Context, tx pgx.Tx, now time.Time)
|
||||
&state.Status, &observedAt, &receivedAt, &checksJSON,
|
||||
)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return state, nil
|
||||
return state, evaluateAcceptanceProductionInfrastructure(environment, nil), nil
|
||||
}
|
||||
if err != nil {
|
||||
return AcceptancePlatformHealthState{}, err
|
||||
return AcceptancePlatformHealthState{}, AcceptanceProductionInfrastructureState{}, err
|
||||
}
|
||||
var checks []PlatformHealthCheck
|
||||
if err = json.Unmarshal(checksJSON, &checks); err != nil {
|
||||
return AcceptancePlatformHealthState{}, err
|
||||
return AcceptancePlatformHealthState{}, AcceptanceProductionInfrastructureState{}, err
|
||||
}
|
||||
return evaluateAcceptancePlatformHealth(state.Status, observedAt, receivedAt, checks, now), nil
|
||||
return evaluateAcceptancePlatformHealth(state.Status, observedAt, receivedAt, checks, now), evaluateAcceptanceProductionInfrastructure(environment, checks), nil
|
||||
}
|
||||
|
||||
func evaluateAcceptancePlatformHealth(status string, observedAt, receivedAt time.Time, checks []PlatformHealthCheck, now time.Time) AcceptancePlatformHealthState {
|
||||
@@ -372,6 +384,53 @@ func acceptancePlatformHealthSummary(state AcceptancePlatformHealthState) string
|
||||
return fmt.Sprintf("云端主机健康快照当前有效,共%d项检查", state.CheckCount)
|
||||
}
|
||||
|
||||
func evaluateAcceptanceProductionInfrastructure(environment string, checks []PlatformHealthCheck) AcceptanceProductionInfrastructureState {
|
||||
state := AcceptanceProductionInfrastructureState{Applicable: environment == "PRODUCTION", DeploymentProfile: "MISSING"}
|
||||
if !state.Applicable {
|
||||
state.GateSatisfied = true
|
||||
return state
|
||||
}
|
||||
profileReady := false
|
||||
for _, check := range checks {
|
||||
switch check.Name {
|
||||
case "deployment_profile":
|
||||
state.DeploymentProfile = check.Value
|
||||
profileReady = check.Status == "healthy" && check.Value == "production"
|
||||
case "offsite_backup":
|
||||
state.OffsiteBackupReady = check.Status == "healthy" && strings.HasPrefix(check.Value, "age_seconds=")
|
||||
case "external_alert_receiver":
|
||||
state.ExternalAlertReceiverReady = check.Status == "healthy" && check.Value == "configured"
|
||||
case "mock_services":
|
||||
state.MockServicesDisabled = check.Status == "healthy" && check.Value == "disabled"
|
||||
}
|
||||
}
|
||||
state.GateSatisfied = profileReady && state.OffsiteBackupReady && state.ExternalAlertReceiverReady && state.MockServicesDisabled
|
||||
return state
|
||||
}
|
||||
|
||||
func acceptanceProductionInfrastructureSummary(state AcceptanceProductionInfrastructureState) string {
|
||||
if !state.Applicable {
|
||||
return "非生产候选不适用生产基础设施强制门禁"
|
||||
}
|
||||
missing := make([]string, 0, 4)
|
||||
if state.DeploymentProfile != "production" {
|
||||
missing = append(missing, "生产部署模式")
|
||||
}
|
||||
if !state.OffsiteBackupReady {
|
||||
missing = append(missing, "已验证异地备份")
|
||||
}
|
||||
if !state.ExternalAlertReceiverReady {
|
||||
missing = append(missing, "真实外部告警接收器")
|
||||
}
|
||||
if !state.MockServicesDisabled {
|
||||
missing = append(missing, "关闭模拟服务")
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return "生产基础设施尚缺:" + strings.Join(missing, "、")
|
||||
}
|
||||
return "生产部署模式、异地备份、外部告警和模拟服务退出均已确认"
|
||||
}
|
||||
|
||||
func acceptanceRuntimeSummary(environment string) string {
|
||||
if environment == "LAB" {
|
||||
return "实验室环境允许开发构建,但候选版本与镜像摘要必须明确"
|
||||
|
||||
Reference in New Issue
Block a user