chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package safariconformance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRealSafariCompleteMatrixPasses(t *testing.T) {
|
||||
t.Parallel()
|
||||
capture := safariFixture("MACOS_SAFARI")
|
||||
report := Evaluate(capture, capture.CandidateVersion, capture.BuildDigest, time.Now())
|
||||
if report.Result != "PASS" || report.EvidenceScope != "REAL_SAFARI" || report.ExternalValidationRequired || report.ResultCount != 24 {
|
||||
t.Fatalf("unexpected report: %+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealSafariMissingWorkflowFails(t *testing.T) {
|
||||
t.Parallel()
|
||||
capture := safariFixture("MACOS_SAFARI")
|
||||
capture.Runs[1].Results = capture.Runs[1].Results[:len(capture.Runs[1].Results)-1]
|
||||
report := Evaluate(capture, capture.CandidateVersion, capture.BuildDigest, time.Now())
|
||||
if report.Result != "FAIL" || !report.ExternalValidationRequired {
|
||||
t.Fatalf("expected failed report: %+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonSafariToolchainCannotCompleteExternalValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
capture := safariFixture("NON_SAFARI_TOOLCHAIN")
|
||||
capture.Runs = capture.Runs[:1]
|
||||
capture.Runs[0].BrowserName = "Chrome"
|
||||
report := Evaluate(capture, capture.CandidateVersion, capture.BuildDigest, time.Now())
|
||||
if report.Result != "PASS" || report.EvidenceScope != "NON_SAFARI_TOOLCHAIN" || !report.ExternalValidationRequired {
|
||||
t.Fatalf("unexpected toolchain report: %+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCaptureRejectsUnknownFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
content, err := json.Marshal(safariFixture("NON_SAFARI_TOOLCHAIN"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content = []byte(strings.TrimSuffix(string(content), "}") + `,"unexpected":true}`)
|
||||
if _, err = ParseCapture(content); err == nil {
|
||||
t.Fatal("expected unknown field rejection")
|
||||
}
|
||||
}
|
||||
|
||||
func safariFixture(source string) Capture {
|
||||
startedAt := time.Date(2026, 8, 25, 10, 0, 0, 0, time.UTC)
|
||||
collectedAt := startedAt.Add(3 * time.Hour)
|
||||
environment := "STAGING"
|
||||
if source == "NON_SAFARI_TOOLCHAIN" {
|
||||
environment = "LOCAL"
|
||||
}
|
||||
runs := make([]BrowserRun, 0, 2)
|
||||
for rankPosition, rank := range RequiredReleaseRanks {
|
||||
results := make([]WorkflowResult, 0, len(RequiredWorkflows)*len(RequiredViewports))
|
||||
for _, workflow := range RequiredWorkflows {
|
||||
for _, viewport := range RequiredViewports {
|
||||
results = append(results, WorkflowResult{Workflow: workflow, Viewport: viewport, Status: "PASS", EvidenceRef: "evidence:" + rank + ":" + workflow + ":" + viewport})
|
||||
}
|
||||
}
|
||||
runs = append(runs, BrowserRun{
|
||||
ReleaseRank: rank, BrowserName: "Safari", BrowserVersion: []string{"26.0", "18.6"}[rankPosition], MacOSVersion: "macOS-test", MachineRef: "mac-runner-a",
|
||||
ExecutionMode: "SAFARIDRIVER", StartedAt: startedAt.Add(time.Duration(rankPosition) * time.Hour), EndedAt: startedAt.Add(time.Duration(rankPosition+1) * time.Hour), Results: results,
|
||||
})
|
||||
}
|
||||
return Capture{
|
||||
SchemaVersion: CaptureSchema, EvidenceSource: source, CandidateVersion: "0.63.0-rc.1", BuildDigest: "sha256:" + strings.Repeat("a", 64), Environment: environment,
|
||||
ReleaseReferenceRef: "apple-release-baseline:2026-08-25", ChangeTicketRef: "change-20260825", ExecutedByRef: "qa-executor", AttestedByRef: "qa-reviewer",
|
||||
AttestedAt: collectedAt.Add(-30 * time.Minute), CollectedAt: collectedAt, Runs: runs,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user