package safariconformance import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "io" "regexp" "slices" "strings" "time" ) const ( CaptureSchema = "safari-compatibility-capture-v1" ReportSchema = "safari-compatibility-conformance-report-v1" ) var digestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) var RequiredReleaseRanks = []string{"CURRENT", "PREVIOUS"} var RequiredViewports = []string{"DESKTOP_1440X1000", "TABLET_1024X900"} var RequiredWorkflows = []string{"AUTH_SESSION", "FAMILY_WORKSPACE", "OPERATIONS_WORKSPACES", "REALTIME_NAVIGATION", "MEDIA_FILE_CONTROLS", "CONTROLLED_ACTIONS"} type Capture struct { SchemaVersion string `json:"schema_version"` EvidenceSource string `json:"evidence_source"` CandidateVersion string `json:"candidate_version"` BuildDigest string `json:"build_digest"` Environment string `json:"environment"` ReleaseReferenceRef string `json:"release_reference_ref"` ChangeTicketRef string `json:"change_ticket_ref"` ExecutedByRef string `json:"executed_by_ref"` AttestedByRef string `json:"attested_by_ref"` AttestedAt time.Time `json:"attested_at"` CollectedAt time.Time `json:"collected_at"` Runs []BrowserRun `json:"runs"` } type BrowserRun struct { ReleaseRank string `json:"release_rank"` BrowserName string `json:"browser_name"` BrowserVersion string `json:"browser_version"` MacOSVersion string `json:"macos_version"` MachineRef string `json:"machine_ref"` ExecutionMode string `json:"execution_mode"` StartedAt time.Time `json:"started_at"` EndedAt time.Time `json:"ended_at"` Results []WorkflowResult `json:"results"` } type WorkflowResult struct { Workflow string `json:"workflow"` Viewport string `json:"viewport"` Status string `json:"status"` ConsoleErrorCount int `json:"console_error_count"` UnexpectedFailedRequestCount int `json:"unexpected_failed_request_count"` HorizontalOverflowCount int `json:"horizontal_overflow_count"` EvidenceRef string `json:"evidence_ref"` } type Check struct { Code string `json:"code"` Status string `json:"status"` Detail string `json:"detail"` } type Report struct { SchemaVersion string `json:"schema_version"` Result string `json:"result"` GeneratedAt time.Time `json:"generated_at"` CandidateVersion string `json:"candidate_version"` BuildDigest string `json:"build_digest"` EvidenceScope string `json:"evidence_scope"` ExternalValidationRequired bool `json:"external_validation_required"` Environment string `json:"environment"` BrowserVersions map[string]string `json:"browser_versions"` RunCount int `json:"run_count"` ResultCount int `json:"result_count"` CompletedReleaseRanks []string `json:"completed_release_ranks"` CompletedWorkflows map[string]int `json:"completed_workflows"` CompletedViewports map[string]int `json:"completed_viewports"` InputSHA256 map[string]string `json:"input_sha256"` Checks []Check `json:"checks"` Disclaimer string `json:"disclaimer"` } func ParseCapture(content []byte) (Capture, error) { var capture Capture if err := decodeStrict(content, &capture); err != nil { return Capture{}, fmt.Errorf("decode Safari compatibility capture: %w", err) } return capture, nil } func ParseReport(content []byte) (Report, error) { var report Report if err := decodeStrict(content, &report); err != nil { return Report{}, fmt.Errorf("decode Safari compatibility report: %w", err) } return report, nil } func Evaluate(capture Capture, candidateVersion, buildDigest string, generatedAt time.Time) Report { candidateVersion = strings.TrimSpace(candidateVersion) buildDigest = strings.ToLower(strings.TrimSpace(buildDigest)) report := Report{ SchemaVersion: ReportSchema, Result: "PASS", GeneratedAt: generatedAt.UTC(), CandidateVersion: candidateVersion, BuildDigest: buildDigest, EvidenceScope: "NON_SAFARI_TOOLCHAIN", ExternalValidationRequired: true, Environment: capture.Environment, BrowserVersions: map[string]string{}, RunCount: len(capture.Runs), CompletedReleaseRanks: []string{}, CompletedWorkflows: map[string]int{}, CompletedViewports: map[string]int{}, InputSHA256: map[string]string{}, Checks: []Check{}, Disclaimer: "该报告只核对所提供的候选、浏览器版本、视口、关键流程结果和证据引用;非macOS真Safari结果不能替代Safari兼容验收,也不等于业务发布批准。", } if capture.EvidenceSource == "MACOS_SAFARI" { report.EvidenceScope = "REAL_SAFARI" } add := func(code string, ok bool, detail string) { status := "PASS" if !ok { status, report.Result = "FAIL", "FAIL" } report.Checks = append(report.Checks, Check{Code: code, Status: status, Detail: detail}) } add("CAPTURE_SCHEMA", capture.SchemaVersion == CaptureSchema, "兼容证据必须使用"+CaptureSchema) add("EVIDENCE_SOURCE", capture.EvidenceSource == "MACOS_SAFARI" || capture.EvidenceSource == "NON_SAFARI_TOOLCHAIN", "证据来源必须明确为MACOS_SAFARI或NON_SAFARI_TOOLCHAIN") add("CANDIDATE_IDENTITY", candidateVersion != "" && digestPattern.MatchString(buildDigest), "候选版本和sha256构建摘要必须完整") add("CAPTURE_CANDIDATE_BINDING", capture.CandidateVersion == candidateVersion && strings.ToLower(capture.BuildDigest) == buildDigest, "浏览器采集必须绑定同一候选版本和构建摘要") real := capture.EvidenceSource == "MACOS_SAFARI" environmentOK := capture.ExecutedByRef != "" && capture.AttestedByRef != "" && !capture.AttestedAt.IsZero() && !capture.CollectedAt.IsZero() && !capture.AttestedAt.After(capture.CollectedAt) if real { environmentOK = environmentOK && capture.ExecutedByRef != capture.AttestedByRef && (capture.Environment == "STAGING" || capture.Environment == "PRODUCTION") && capture.ReleaseReferenceRef != "" && capture.ChangeTicketRef != "" } else { environmentOK = environmentOK && capture.Environment == "LOCAL" } add("ENVIRONMENT_IDENTITY", environmentOK, "真实Safari证据必须绑定目标环境、版本发布依据、变更工单、执行人和独立复核人") releaseOK, viewportOK, workflowOK, outcomeOK, refsOK, timelineOK := len(capture.Runs) > 0, true, true, true, true, true seenRanks, seenVersions := map[string]bool{}, map[string]bool{} for _, run := range capture.Runs { if !slices.Contains(RequiredReleaseRanks, run.ReleaseRank) || seenRanks[run.ReleaseRank] || run.BrowserVersion == "" { releaseOK = false } seenRanks[run.ReleaseRank] = true if seenVersions[run.BrowserVersion] { releaseOK = false } seenVersions[run.BrowserVersion] = true report.BrowserVersions[run.ReleaseRank] = run.BrowserVersion timelineOK = timelineOK && !run.StartedAt.IsZero() && !run.EndedAt.IsZero() && !run.EndedAt.Before(run.StartedAt) && !run.EndedAt.After(capture.CollectedAt) if real && (run.BrowserName != "Safari" || run.MacOSVersion == "" || run.MachineRef == "" || !slices.Contains([]string{"SAFARIDRIVER", "MANUAL_OBSERVED"}, run.ExecutionMode)) { releaseOK = false } seenMatrix := map[string]bool{} for _, result := range run.Results { key := result.Workflow + "/" + result.Viewport if seenMatrix[key] || !slices.Contains(RequiredWorkflows, result.Workflow) || !slices.Contains(RequiredViewports, result.Viewport) { workflowOK = false } seenMatrix[key] = true report.ResultCount++ if result.Status != "PASS" || result.ConsoleErrorCount != 0 || result.UnexpectedFailedRequestCount != 0 || result.HorizontalOverflowCount != 0 { outcomeOK = false } if strings.TrimSpace(result.EvidenceRef) == "" { refsOK = false } report.CompletedWorkflows[result.Workflow]++ report.CompletedViewports[result.Viewport]++ } if real { for _, workflow := range RequiredWorkflows { for _, viewport := range RequiredViewports { if !seenMatrix[workflow+"/"+viewport] { workflowOK = false } } } } } for _, rank := range RequiredReleaseRanks { if seenRanks[rank] { report.CompletedReleaseRanks = append(report.CompletedReleaseRanks, rank) } else if real { releaseOK = false } } add("SAFARI_RELEASE_COVERAGE", releaseOK, "真实验收必须覆盖发布依据中CURRENT和PREVIOUS两个不同Safari版本") add("VIEWPORT_COVERAGE", viewportOK && (!real || report.ResultCount == len(RequiredReleaseRanks)*len(RequiredWorkflows)*len(RequiredViewports)), "每个Safari版本必须覆盖桌面与平板响应式视口") add("WORKFLOW_COVERAGE", workflowOK, "每个版本和视口必须覆盖认证、家庭、运营、实时导航、媒体文件和受控操作六条关键流程") add("FUNCTIONAL_OUTCOME", outcomeOK, "所有关键流程必须通过且控制台错误、非预期失败请求和横向溢出均为0") add("EVIDENCE_REFERENCES", refsOK, "每个关键流程结果必须保存截图、视频、日志或人工记录引用") add("EXECUTION_TIMELINE", timelineOK, "每次浏览器执行时间必须有效且不晚于采集时间") if real && environmentOK && releaseOK && workflowOK && outcomeOK && refsOK && timelineOK && report.Result == "PASS" { report.ExternalValidationRequired = false } return report } func SHA256(content []byte) string { digest := sha256.Sum256(content) return hex.EncodeToString(digest[:]) } func decodeStrict(content []byte, target any) error { decoder := json.NewDecoder(bytes.NewReader(content)) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { return err } var trailing any if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) { return errors.New("trailing JSON data") } return nil }