package scripts_test import ( "bytes" "crypto/sha256" "fmt" "os" "os/exec" "path/filepath" "sort" "strings" "syscall" "testing" "time" ) var releasePlatformAssets = []string{ "dws-darwin-amd64.tar.gz", "dws-darwin-arm64.tar.gz", "dws-linux-amd64.tar.gz", "dws-linux-arm64.tar.gz", "dws-windows-amd64.zip", "dws-windows-arm64.zip", } func writeVersionedReleaseArchive(t *testing.T, dist, asset, version string) { t.Helper() stage := t.TempDir() binary := "dws" if strings.HasSuffix(asset, ".zip") { binary = "dws.exe" } mustWriteFile(t, filepath.Join(stage, binary), []byte("fake release binary\n"+version+"\n"), 0o755) if strings.HasSuffix(asset, ".zip") { mustRun(t, stage, "zip", "-q", filepath.Join(dist, asset), binary) return } mustRun(t, stage, "tar", "-czf", filepath.Join(dist, asset), binary) } func writeReleaseChecksums(t *testing.T, dist string, includeSkills bool) { t.Helper() assets := append([]string{}, releasePlatformAssets...) if includeSkills { assets = append(assets, "dws-skills.zip") } sort.Strings(assets) var lines []string for _, asset := range assets { data, err := os.ReadFile(filepath.Join(dist, asset)) if err != nil { t.Fatalf("ReadFile(%s) error = %v", asset, err) } lines = append(lines, fmt.Sprintf("%x %s", sha256.Sum256(data), asset)) } mustWriteFile(t, filepath.Join(dist, "checksums.txt"), []byte(strings.Join(lines, "\n")+"\n"), 0o644) } func seedVersionedReleaseArtifacts(t *testing.T, dist, version string) { t.Helper() if err := os.MkdirAll(dist, 0o755); err != nil { t.Fatalf("MkdirAll(%s) error = %v", dist, err) } for _, asset := range releasePlatformAssets { writeVersionedReleaseArchive(t, dist, asset, version) } mustWriteFile(t, filepath.Join(dist, "dws-skills.zip"), []byte("fake skills\n"), 0o644) writeReleaseChecksums(t, dist, true) } type releaseTestRepo struct { root string remote string contract string prepare string render string releaseCmd string compat string lib string verify string } func newReleaseTestRepo(t *testing.T) *releaseTestRepo { t.Helper() sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } base := t.TempDir() root := filepath.Join(base, "work") remote := filepath.Join(base, "remote.git") mustRun(t, base, "git", "init", "--bare", remote) mustRun(t, base, "git", "init", "-b", "main", root) mustRun(t, root, "git", "config", "user.name", "Release Test") mustRun(t, root, "git", "config", "user.email", "release-test@example.com") mustWriteFile(t, filepath.Join(root, "CHANGELOG.md"), []byte(releaseChangelog()), 0o644) mustWriteFile(t, filepath.Join(root, "seed.txt"), []byte("initial\n"), 0o644) mustRun(t, root, "git", "add", ".") mustRun(t, root, "git", "commit", "-m", "initial stable") mustRun(t, root, "git", "tag", "-a", "v1.0.0", "-m", "Release v1.0.0") mustRun(t, root, "git", "remote", "add", "origin", remote) mustRun(t, root, "git", "push", "-u", "origin", "main") mustRun(t, root, "git", "push", "origin", "v1.0.0") return &releaseTestRepo{ root: root, remote: remote, contract: filepath.Join(sourceRoot, "scripts", "release", "release-contract.sh"), prepare: filepath.Join(sourceRoot, "scripts", "release", "prepare-changelog.sh"), render: filepath.Join(sourceRoot, "scripts", "release", "render-release-fragments.sh"), releaseCmd: filepath.Join(sourceRoot, "scripts", "release", "release.sh"), compat: filepath.Join(sourceRoot, "scripts", "release", "check-release-compatibility.sh"), lib: filepath.Join(sourceRoot, "scripts", "release", "release-lib.sh"), verify: filepath.Join(sourceRoot, "scripts", "release", "verify-release-artifacts.sh"), } } func seedReleaseCompatibilityRefs(t *testing.T, repo string) (stable, base, candidate string) { t.Helper() mustRun(t, repo, "git", "config", "user.name", "Release Compatibility Test") mustRun(t, repo, "git", "config", "user.email", "release-compatibility-test@example.com") mustWriteFile(t, filepath.Join(repo, "seed.txt"), []byte("stable\n"), 0o644) mustRun(t, repo, "git", "add", "seed.txt") mustRun(t, repo, "git", "commit", "-m", "stable") mustRun(t, repo, "git", "tag", "stable-ref") stable = strings.TrimSpace(mustOutput(t, repo, "git", "rev-parse", "HEAD^{commit}")) mustWriteFile(t, filepath.Join(repo, "seed.txt"), []byte("base\n"), 0o644) mustRun(t, repo, "git", "add", "seed.txt") mustRun(t, repo, "git", "commit", "-m", "base") mustRun(t, repo, "git", "branch", "base-ref") base = strings.TrimSpace(mustOutput(t, repo, "git", "rev-parse", "HEAD^{commit}")) mustWriteFile(t, filepath.Join(repo, "seed.txt"), []byte("candidate\n"), 0o644) mustRun(t, repo, "git", "add", "seed.txt") mustRun(t, repo, "git", "commit", "-m", "candidate") mustRun(t, repo, "git", "branch", "candidate-ref") candidate = strings.TrimSpace(mustOutput(t, repo, "git", "rev-parse", "HEAD^{commit}")) return stable, base, candidate } func TestReleaseCompatibilityCheckRunsCLIAndSchemaWithExactRefs(t *testing.T) { t.Parallel() sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } runner := filepath.Join(sourceRoot, "scripts", "release", "check-release-compatibility.sh") repo := t.TempDir() mustRun(t, repo, "git", "init", "-b", "main") stable, base, candidate := seedReleaseCompatibilityRefs(t, repo) trace := filepath.Join(t.TempDir(), "compatibility.log") checker := func(name string) []byte { return []byte("#!/bin/sh\nset -eu\nprintf '" + name + ":%s\\n' \"$*\" >> \"$TRACE\"\n") } mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-interface-baselines.sh"), checker("cli"), 0o755) mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-schema-compatibility.sh"), checker("schema"), 0o755) cmd := exec.Command( runner, "--repo-root", repo, "--base-ref", "base-ref", "--stable-ref", "stable-ref", "--candidate-ref", "candidate-ref", ) cmd.Env = append(os.Environ(), "TRACE="+trace) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("release compatibility check error = %v\noutput:\n%s", err, output) } got, err := os.ReadFile(trace) if err != nil { t.Fatalf("ReadFile(%s) error = %v", trace, err) } want := fmt.Sprintf("cli:--base-ref %s --stable-ref %s --candidate-ref %s\n", base, stable, candidate) + fmt.Sprintf("schema:--base-ref %s --stable-ref %s --candidate-ref %s\n", base, stable, candidate) if string(got) != want { t.Fatalf("release compatibility calls = %q, want %q", got, want) } } func TestReleaseCompatibilityCheckFreezesRefsBeforeRunningEitherChecker(t *testing.T) { t.Parallel() sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } runner := filepath.Join(sourceRoot, "scripts", "release", "check-release-compatibility.sh") repo := t.TempDir() mustRun(t, repo, "git", "init", "-b", "main") stable, base, candidate := seedReleaseCompatibilityRefs(t, repo) trace := filepath.Join(t.TempDir(), "compatibility.log") resolveAndLog := ` base="$2" stable="$4" candidate="$6" printf '%s:%s:%s:%s\n' "$CHECK_NAME" \ "$(git -C "$REPO_ROOT_FOR_TEST" rev-parse --verify "${base}^{commit}")" \ "$(git -C "$REPO_ROOT_FOR_TEST" rev-parse --verify "${stable}^{commit}")" \ "$(git -C "$REPO_ROOT_FOR_TEST" rev-parse --verify "${candidate}^{commit}")" >> "$TRACE" ` mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-interface-baselines.sh"), []byte("#!/bin/sh\nset -eu\nCHECK_NAME=cli\n"+resolveAndLog+"git -C \"$REPO_ROOT_FOR_TEST\" update-ref refs/heads/base-ref \"$MUTATE_TO\"\n"), 0o755) mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-schema-compatibility.sh"), []byte("#!/bin/sh\nset -eu\nCHECK_NAME=schema\n"+resolveAndLog), 0o755) cmd := exec.Command( runner, "--repo-root", repo, "--base-ref", "base-ref", "--stable-ref", "stable-ref", "--candidate-ref", "candidate-ref", ) cmd.Env = append(os.Environ(), "TRACE="+trace, "REPO_ROOT_FOR_TEST="+repo, "MUTATE_TO="+candidate) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("release compatibility check error = %v\noutput:\n%s", err, output) } got, err := os.ReadFile(trace) if err != nil { t.Fatalf("ReadFile(%s) error = %v", trace, err) } want := fmt.Sprintf("cli:%s:%s:%s\nschema:%s:%s:%s\n", base, stable, candidate, base, stable, candidate) if string(got) != want { t.Fatalf("release compatibility resolved commits = %q, want %q", got, want) } if moved := strings.TrimSpace(mustOutput(t, repo, "git", "rev-parse", "base-ref^{commit}")); moved != candidate { t.Fatalf("mutating checker did not move base-ref: got %s, want %s", moved, candidate) } } func TestReleaseCompatibilityCheckStopsBeforeSchemaWhenCLIFails(t *testing.T) { t.Parallel() sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } runner := filepath.Join(sourceRoot, "scripts", "release", "check-release-compatibility.sh") repo := t.TempDir() mustRun(t, repo, "git", "init", "-b", "main") seedReleaseCompatibilityRefs(t, repo) trace := filepath.Join(t.TempDir(), "compatibility.log") mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-interface-baselines.sh"), []byte("#!/bin/sh\nprintf 'cli\\n' >> \"$TRACE\"\nexit 17\n"), 0o755) mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-schema-compatibility.sh"), []byte("#!/bin/sh\nprintf 'schema\\n' >> \"$TRACE\"\n"), 0o755) cmd := exec.Command( runner, "--repo-root", repo, "--base-ref", "base-ref", "--stable-ref", "stable-ref", "--candidate-ref", "candidate-ref", ) cmd.Env = append(os.Environ(), "TRACE="+trace) output, err := cmd.CombinedOutput() exitErr, ok := err.(*exec.ExitError) if !ok || exitErr.ExitCode() != 17 { t.Fatalf("release compatibility CLI failure = %v, want exit 17\noutput:\n%s", err, output) } if !strings.Contains(string(output), "error: authoritative CLI compatibility failed") { t.Fatalf("release compatibility CLI failure has no boundary message:\n%s", output) } got, err := os.ReadFile(trace) if err != nil { t.Fatalf("ReadFile(%s) error = %v", trace, err) } if string(got) != "cli\n" { t.Fatalf("checks after CLI failure = %q, want only CLI", got) } } func TestReleaseCompatibilityCheckFailsWhenSchemaFails(t *testing.T) { t.Parallel() sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } runner := filepath.Join(sourceRoot, "scripts", "release", "check-release-compatibility.sh") repo := t.TempDir() mustRun(t, repo, "git", "init", "-b", "main") seedReleaseCompatibilityRefs(t, repo) trace := filepath.Join(t.TempDir(), "compatibility.log") mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-interface-baselines.sh"), []byte("#!/bin/sh\nprintf 'cli\\n' >> \"$TRACE\"\n"), 0o755) mustWriteFile(t, filepath.Join(repo, "scripts", "policy", "check-authoritative-schema-compatibility.sh"), []byte("#!/bin/sh\nprintf 'schema\\n' >> \"$TRACE\"\nexit 19\n"), 0o755) cmd := exec.Command( runner, "--repo-root", repo, "--base-ref", "base-ref", "--stable-ref", "stable-ref", "--candidate-ref", "candidate-ref", ) cmd.Env = append(os.Environ(), "TRACE="+trace) output, err := cmd.CombinedOutput() exitErr, ok := err.(*exec.ExitError) if !ok || exitErr.ExitCode() != 19 { t.Fatalf("release compatibility Schema failure = %v, want exit 19\noutput:\n%s", err, output) } if !strings.Contains(string(output), "error: authoritative Schema compatibility failed") { t.Fatalf("release compatibility Schema failure has no boundary message:\n%s", output) } got, err := os.ReadFile(trace) if err != nil { t.Fatalf("ReadFile(%s) error = %v", trace, err) } if string(got) != "cli\nschema\n" { t.Fatalf("checks before Schema failure = %q, want CLI then Schema", got) } } func TestReleaseVersionOrdering(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } lib := filepath.Join(sourceRoot, "scripts", "release", "release-lib.sh") tests := []struct { candidate string baseline string greater bool }{ {candidate: "v1.0.2", baseline: "v1.0.1", greater: true}, {candidate: "v1.1.0-beta.1", baseline: "v1.0.9-beta.9", greater: true}, {candidate: "v1.0.1-beta.2", baseline: "v1.0.1-beta.1", greater: true}, {candidate: "v1.0.1", baseline: "v1.0.1-beta.9", greater: true}, {candidate: "v1.0.1-beta.1", baseline: "v1.0.1-beta.2", greater: false}, {candidate: "v1.0.1-beta.1", baseline: "v1.0.1", greater: false}, {candidate: "v1.0.1", baseline: "v1.0.1", greater: false}, } for _, test := range tests { cmd := exec.Command("sh", "-c", `. "$1"; release_version_is_greater "$2" "$3"`, "sh", lib, test.candidate, test.baseline) err := cmd.Run() if (err == nil) != test.greater { t.Fatalf("release_version_is_greater(%s, %s) error = %v, want greater=%v", test.candidate, test.baseline, err, test.greater) } } } func TestNextReleaseVersion(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } script := filepath.Join(sourceRoot, "scripts", "release", "next-release-version.sh") newRepo := func(t *testing.T, tags ...string) string { t.Helper() root := t.TempDir() mustRun(t, root, "git", "init", "-b", "main") mustRun(t, root, "git", "config", "user.name", "Release Test") mustRun(t, root, "git", "config", "user.email", "release-test@example.com") mustWriteFile(t, filepath.Join(root, "seed.txt"), []byte("release allocator\n"), 0o644) mustRun(t, root, "git", "add", "seed.txt") mustRun(t, root, "git", "commit", "-m", "seed") for _, tag := range tags { mustRun(t, root, "git", "tag", "-a", tag, "-m", "Allocate "+tag) } return root } run := func(t *testing.T, root string, args ...string) (string, error) { t.Helper() commandArgs := append([]string{script, "--repo-root", root}, args...) output, err := exec.Command("sh", commandArgs...).CombinedOutput() return string(output), err } t.Run("continues highest open beta core", func(t *testing.T) { root := newRepo(t, "v1.0.52", "v1.0.53-beta.1", "v1.0.53-beta.2", "v1.0.53-beta.3", "v1.0.53-beta.4", ) output, err := run(t, root, "--channel", "beta") if err != nil { t.Fatalf("next beta error = %v\noutput:\n%s", err, output) } want := "release_version=v1.0.53-beta.5\nfrom_beta=\nchannel=prerelease\nbase=v1.0.52\n" if output != want { t.Fatalf("next beta output:\ngot:\n%s\nwant:\n%s", output, want) } }) t.Run("withdrawn beta remains allocated", func(t *testing.T) { root := newRepo(t, "v1.0.52", "v1.0.53-beta.4", "withdrawn/v1.0.53-beta.5", ) output, err := run(t, root, "--channel", "prerelease") if err != nil { t.Fatalf("next beta after withdrawal error = %v\noutput:\n%s", err, output) } if !strings.Contains(output, "release_version=v1.0.53-beta.6\n") { t.Fatalf("withdrawn beta version was reused:\n%s", output) } }) t.Run("starts requested bumped beta core", func(t *testing.T) { tests := []struct { bump string want string }{ {bump: "patch", want: "v1.2.4-beta.1"}, {bump: "minor", want: "v1.3.0-beta.1"}, {bump: "major", want: "v2.0.0-beta.1"}, } for _, test := range tests { t.Run(test.bump, func(t *testing.T) { root := newRepo(t, "v1.2.3") output, err := run(t, root, "--channel", "beta", "--bump", test.bump) if err != nil { t.Fatalf("new beta line error = %v\noutput:\n%s", err, output) } if !strings.Contains(output, "release_version="+test.want+"\n") { t.Fatalf("new beta line output:\n%s", output) } }) } }) t.Run("promotes latest beta from highest open core", func(t *testing.T) { root := newRepo(t, "v1.0.0", "v1.1.0-beta.3", "v1.2.0-beta.1", "v1.2.0-beta.2", ) output, err := run(t, root, "--channel", "stable") if err != nil { t.Fatalf("stable allocation error = %v\noutput:\n%s", err, output) } want := "release_version=v1.2.0\nfrom_beta=v1.2.0-beta.2\nchannel=stable\nbase=v1.0.0\n" if output != want { t.Fatalf("stable allocation output:\ngot:\n%s\nwant:\n%s", output, want) } }) t.Run("withdrawn stable closes its core", func(t *testing.T) { root := newRepo(t, "v1.0.0", "v1.0.1-beta.1", "withdrawn/v1.0.1", ) output, err := run(t, root, "--channel", "beta") if err != nil { t.Fatalf("beta after withdrawn stable error = %v\noutput:\n%s", err, output) } want := "release_version=v1.0.2-beta.1\nfrom_beta=\nchannel=prerelease\nbase=v1.0.1\n" if output != want { t.Fatalf("withdrawn stable allocation output:\ngot:\n%s\nwant:\n%s", output, want) } }) t.Run("withdrawn latest beta cannot be promoted", func(t *testing.T) { root := newRepo(t, "v1.0.0", "v1.0.1-beta.1", "withdrawn/v1.0.1-beta.2", ) output, err := run(t, root, "--channel", "stable") if err == nil || !strings.Contains(output, "latest beta v1.0.1-beta.2 is withdrawn") { t.Fatalf("withdrawn beta promotion was not rejected: err=%v\noutput:\n%s", err, output) } }) } func TestReleaseLibHelpersPreserveCallerVariables(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } lib := filepath.Join(sourceRoot, "scripts", "release", "release-lib.sh") changelogPath := filepath.Join(t.TempDir(), "CHANGELOG.md") mustWriteFile(t, changelogPath, []byte("## [1.2.3] - 2026-07-16\n\n- Test release.\n"), 0o644) script := ` . "$1" version=caller-version expected=caller-expected actual=caller-actual candidate=caller-candidate baseline=caller-baseline changelog=caller-changelog semver=caller-semver output=caller-output tmp=caller-tmp status=caller-status release_channel_for_version v1.2.3 >/dev/null release_validate_version_channel stable v1.2.3 release_core_tag v1.2.3-beta.1 >/dev/null release_core_is_greater v1.2.4 v1.2.3 release_extract_changelog "$2" 1.2.3 - >/dev/null printf '%s\n' "$version" "$expected" "$actual" "$candidate" "$baseline" \ "$changelog" "$semver" "$output" "$tmp" "$status" ` output, err := exec.Command("sh", "-c", script, "sh", lib, changelogPath).CombinedOutput() if err != nil { t.Fatalf("release helper variable isolation error = %v\noutput:\n%s", err, output) } want := strings.Join([]string{ "caller-version", "caller-expected", "caller-actual", "caller-candidate", "caller-baseline", "caller-changelog", "caller-semver", "caller-output", "caller-tmp", "caller-status", }, "\n") + "\n" if string(output) != want { t.Fatalf("release helpers changed caller variables:\ngot:\n%s\nwant:\n%s", output, want) } } func TestReleaseNpmPackingIgnoresLifecycleScripts(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } packageDir := filepath.Join(t.TempDir(), "package") marker := filepath.Join(t.TempDir(), "prepack-ran") manifest := fmt.Sprintf(`{ "name": "dingtalk-workspace-cli", "version": "1.2.3", "scripts": {"prepack": "touch %s"}, "files": ["README.md"] } `, marker) mustWriteFile(t, filepath.Join(packageDir, "package.json"), []byte(manifest), 0o644) mustWriteFile(t, filepath.Join(packageDir, "README.md"), []byte("test package\n"), 0o644) outputTarball := filepath.Join(t.TempDir(), "package.tgz") cmd := exec.Command("sh", filepath.Join(sourceRoot, "scripts", "release", "pack-npm-package.sh"), packageDir, outputTarball) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("pack-npm-package error = %v\noutput:\n%s", err, output) } if !strings.HasPrefix(strings.TrimSpace(string(output)), "sha512-") { t.Fatalf("pack output is not an integrity value: %s", output) } if _, err := os.Stat(outputTarball); err != nil { t.Fatalf("packed tarball missing: %v", err) } if _, err := os.Stat(marker); !os.IsNotExist(err) { t.Fatalf("npm prepack lifecycle unexpectedly ran: %v", err) } } func TestReleaseNpmStagingRejectsUnexpectedLifecycleScripts(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } root := t.TempDir() source := filepath.Join(root, "source") dist := filepath.Join(root, "dist") mustWriteFile(t, filepath.Join(source, "build", "npm", "install.js"), []byte("\n"), 0o644) mustWriteFile(t, filepath.Join(source, "build", "npm", "bin", "dws.js"), []byte("\n"), 0o755) mustWriteFile(t, filepath.Join(source, "build", "npm", "README.md"), []byte("test\n"), 0o644) mustWriteFile(t, filepath.Join(source, "build", "npm", "package.json.tmpl"), []byte(`{ "name": "dingtalk-workspace-cli", "version": "__VERSION__", "bin": {"dws": "./bin/dws.js"}, "scripts": {"postinstall": "node install.js", "prepublishOnly": "node steal.js"} } `), 0o644) cmd := exec.Command("sh", filepath.Join(sourceRoot, "scripts", "release", "stage-npm-package.sh"), "v1.2.3") cmd.Env = append(os.Environ(), "DWS_PACKAGE_SOURCE_ROOT="+source, "DWS_PACKAGE_DIST_DIR="+dist) output, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), "unexpected npm lifecycle scripts") { t.Fatalf("unexpected lifecycle script was not rejected: err=%v\noutput:\n%s", err, output) } } func TestReleaseGitHubAssetSetIsExact(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() fakeGH := filepath.Join(binDir, "gh") mustWriteFile(t, fakeGH, []byte(`#!/bin/sh set -eu case "${1:-}" in release) [ "${2:-}" = "view" ] || exit 2 [ "${GH_DRAFT:-false}" = "true" ] || exit 1 printf '123\n' ;; api) printf '%s\n' "$*" >> "$GH_LOG" case "$*" in *tag_name*) printf '%s\n' "$GH_RELEASE_TAG" ;; *assets*name*) printf '%s\n' "$GH_ASSETS" ;; *) exit 2 ;; esac ;; *) exit 2 ;; esac `), 0o755) exact := strings.Join([]string{ "dws-windows-arm64.zip", "dws-linux-amd64.tar.gz", "checksums.txt", "dws-skills.zip", "dws-darwin-amd64.tar.gz", "dws-windows-amd64.zip", "dws-linux-arm64.tar.gz", "dws-darwin-arm64.tar.gz", }, "\n") script := filepath.Join(sourceRoot, "scripts", "release", "verify-github-release-assets.sh") run := func(assets string, draft bool, releaseTag, releaseID string) (string, string, error) { draftValue := "false" if draft { draftValue = "true" } logPath := filepath.Join(t.TempDir(), "gh.log") cmd := exec.Command("sh", script, "v1.2.3") cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "GITHUB_REPOSITORY=owner/repo", "GH_ASSETS=" + assets, "GH_DRAFT=" + draftValue, "GH_LOG=" + logPath, "GH_RELEASE_TAG=" + releaseTag, "DWS_GITHUB_RELEASE_ID=" + releaseID, } output, err := cmd.CombinedOutput() log, readErr := os.ReadFile(logPath) if readErr != nil && !os.IsNotExist(readErr) { t.Fatalf("ReadFile(%s) error = %v", logPath, readErr) } return string(output), string(log), err } if output, _, err := run(exact, false, "v1.2.3", ""); err != nil { t.Fatalf("exact GitHub assets rejected: %v\n%s", err, output) } if output, log, err := run(exact, true, "v1.2.3", ""); err != nil { t.Fatalf("exact Draft GitHub assets rejected: %v\n%s", err, output) } else if !strings.Contains(log, "repos/owner/repo/releases/123") || strings.Contains(log, "repos/owner/repo/releases/tags/v1.2.3") { t.Fatalf("Draft verification did not use the release ID endpoint:\n%s", log) } if output, _, err := run(exact+"\nmalware.exe", false, "v1.2.3", ""); err == nil || !strings.Contains(output, "exactly the supported assets") { t.Fatalf("extra GitHub asset was not rejected: err=%v\n%s", err, output) } if output, _, err := run(exact, true, "v9.9.9", ""); err == nil || !strings.Contains(output, "ID/tag mismatch") { t.Fatalf("wrong Draft release ID target was not rejected: err=%v\n%s", err, output) } if output, _, err := run(exact, false, "v1.2.3", "invalid"); err == nil || !strings.Contains(output, "invalid GitHub Release ID") { t.Fatalf("invalid explicit release ID was not rejected: err=%v\n%s", err, output) } } func TestReleaseGitHubAssetsDownloadByExactReleaseID(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() fakeGH := filepath.Join(binDir, "gh") mustWriteFile(t, fakeGH, []byte(`#!/bin/sh set -eu [ "${1:-}" = "api" ] || exit 2 shift accept="" endpoint="" query="" while [ "$#" -gt 0 ]; do case "$1" in -H) accept="${2:-}" shift 2 ;; --jq) query="${2:-}" shift 2 ;; repos/*) endpoint="$1" shift ;; *) shift ;; esac done printf '%s|%s|%s\n' "$accept" "$endpoint" "$query" >> "$GH_LOG" case "$accept" in *application/octet-stream*) case "$endpoint" in repos/owner/repo/releases/assets/*) ;; *) exit 31 ;; esac asset_id="${endpoint##*/}" asset_name="$( awk -F ' ' -v id="$asset_id" ' $1 == id { print $2; found = 1 } END { if (!found) exit 1 } ' "$GH_ASSET_ROWS" )" || exit 32 printf 'asset-bytes:%s:%s\n' "$asset_id" "$asset_name" exit 0 ;; esac [ "$endpoint" = "repos/owner/repo/releases/$GH_EXPECTED_RELEASE_ID" ] || exit 33 case "$query" in '.tag_name') printf '%s\n' "$GH_RELEASE_TAG" ;; '.assets[].name') cut -f 2 "$GH_ASSET_ROWS" ;; '.assets[] | [.id, .name] | @tsv') cat "$GH_ASSET_ROWS" ;; *) exit 34 ;; esac `), 0o755) type releaseAsset struct { id string name string } assets := []releaseAsset{ {id: "1008", name: "dws-windows-arm64.zip"}, {id: "1003", name: "dws-darwin-arm64.tar.gz"}, {id: "1001", name: "checksums.txt"}, {id: "1005", name: "dws-linux-arm64.tar.gz"}, {id: "1002", name: "dws-darwin-amd64.tar.gz"}, {id: "1007", name: "dws-windows-amd64.zip"}, {id: "1004", name: "dws-linux-amd64.tar.gz"}, {id: "1006", name: "dws-skills.zip"}, } rowsFor := func(items []releaseAsset) string { var rows []string for _, asset := range items { rows = append(rows, asset.id+"\t"+asset.name) } return strings.Join(rows, "\n") + "\n" } script := filepath.Join(sourceRoot, "scripts", "release", "download-github-release-assets.sh") type runResult struct { output string log string dest string err error } run := func(items []releaseAsset, releaseTag, releaseID string) runResult { runDir := t.TempDir() rowsPath := filepath.Join(runDir, "assets.tsv") logPath := filepath.Join(runDir, "gh.log") dest := filepath.Join(runDir, "download") mustWriteFile(t, rowsPath, []byte(rowsFor(items)), 0o644) cmd := exec.Command("sh", script, "v1.2.3", dest) cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "GITHUB_REPOSITORY=owner/repo", "DWS_GITHUB_RELEASE_ID=" + releaseID, "GH_ASSET_ROWS=" + rowsPath, "GH_EXPECTED_RELEASE_ID=456", "GH_LOG=" + logPath, "GH_RELEASE_TAG=" + releaseTag, } output, runErr := cmd.CombinedOutput() log, readErr := os.ReadFile(logPath) if readErr != nil && !os.IsNotExist(readErr) { t.Fatalf("ReadFile(%s) error = %v", logPath, readErr) } return runResult{ output: string(output), log: string(log), dest: dest, err: runErr, } } success := run(assets, "v1.2.3", "456") if success.err != nil { t.Fatalf("exact GitHub Release download was rejected: %v\n%s", success.err, success.output) } if strings.Contains(success.log, "releases/tags/") { t.Fatalf("download fell back to a tag endpoint:\n%s", success.log) } if got := strings.Count(success.log, "application/octet-stream"); got != len(assets) { t.Fatalf("asset download count = %d, want %d\nlog:\n%s", got, len(assets), success.log) } for _, asset := range assets { wantEndpoint := "repos/owner/repo/releases/assets/" + asset.id if !strings.Contains(success.log, wantEndpoint) { t.Errorf("asset %s was not downloaded by ID %s\nlog:\n%s", asset.name, asset.id, success.log) } data, readErr := os.ReadFile(filepath.Join(success.dest, asset.name)) if readErr != nil { t.Errorf("ReadFile(%s) error = %v", asset.name, readErr) continue } want := fmt.Sprintf("asset-bytes:%s:%s\n", asset.id, asset.name) if string(data) != want { t.Errorf("%s bytes = %q, want %q", asset.name, data, want) } } extra := append(append([]releaseAsset{}, assets...), releaseAsset{id: "1009", name: "malware.exe"}) if result := run(extra, "v1.2.3", "456"); result.err == nil || !strings.Contains(result.output, "exactly the supported assets") { t.Fatalf("extra GitHub Release asset was not rejected: err=%v\n%s", result.err, result.output) } if result := run(assets[:len(assets)-1], "v1.2.3", "456"); result.err == nil || !strings.Contains(result.output, "exactly the supported assets") { t.Fatalf("missing GitHub Release asset was not rejected: err=%v\n%s", result.err, result.output) } if result := run(assets, "v9.9.9", "456"); result.err == nil || !strings.Contains(result.output, "ID/tag mismatch") { t.Fatalf("wrong GitHub Release tag was not rejected: err=%v\n%s", result.err, result.output) } if result := run(assets, "v1.2.3", "invalid"); result.err == nil || !strings.Contains(result.output, "invalid GitHub Release ID") { t.Fatalf("invalid GitHub Release ID was not rejected: err=%v\n%s", result.err, result.output) } } func TestReleaseDeliveredStableRequiresSuccessfulPublicDelivery(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } r := newReleaseTestRepo(t) commit := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "v1.0.0^{commit}")) binDir := t.TempDir() fakeCurl := filepath.Join(binDir, "curl") mustWriteFile(t, fakeCurl, []byte(`#!/bin/sh set -eu for arg in "$@"; do last="$arg"; done case "$last" in */releases/tags/*) printf '{"tag_name":"v1.0.0","draft":false,"prerelease":false}\n' ;; */actions/workflows/release.yml/runs*) printf '{"workflow_runs":[{"head_sha":"%s","head_branch":"v1.0.0","conclusion":"%s"}]}\n' "$EXPECTED_COMMIT" "$RUN_CONCLUSION" ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-delivered-stable.sh") run := func(conclusion string) (string, error) { cmd := exec.Command("sh", script, "v1.0.0", commit) cmd.Dir = r.root cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "DWS_RELEASE_OFFICIAL_REPOSITORY=owner/repo", "EXPECTED_COMMIT=" + commit, "RUN_CONCLUSION=" + conclusion, } output, err := cmd.CombinedOutput() return string(output), err } if output, err := run("success"); err != nil { t.Fatalf("delivered stable was rejected: %v\n%s", err, output) } if output, err := run("failure"); err == nil || !strings.Contains(output, "did not complete successfully") { t.Fatalf("orphan stable tag was not rejected: err=%v\n%s", err, output) } } func TestReleaseDeliveredStableAcceptsOnlyPinnedSuccessfulRecovery(t *testing.T) { const ( tag = "v1.0.52" commit = "4e59f9aa7ab057da8d5512ae9818fb66d4c6a045" runID = "29380892754" workflowSHA = "434251695c19ebbfe2a2240d2eddce2d56af07b7" ) sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } root := t.TempDir() mustRun(t, root, "git", "init") binDir := t.TempDir() fakeCurl := filepath.Join(binDir, "curl") mustWriteFile(t, fakeCurl, []byte(`#!/bin/sh set -eu for arg in "$@"; do last="$arg"; done case "$last" in */releases/tags/*) printf '{"tag_name":"v1.0.52","draft":false,"prerelease":false}\n' ;; */actions/workflows/release.yml/runs*) printf '{"workflow_runs":[]}\n' ;; */actions/runs/29380892754/jobs*) printf '{"jobs":[{"name":"release","status":"completed","conclusion":"success","head_sha":"%s"},{"name":"verify-darwin-signatures","status":"completed","conclusion":"success","head_sha":"%s"},{"name":"publish-release","status":"completed","conclusion":"%s","head_sha":"%s"}]}\n' "$WORKFLOW_SHA" "$WORKFLOW_SHA" "$PUBLISH_CONCLUSION" "$WORKFLOW_SHA" ;; */actions/runs/29380892754) printf '{"event":"workflow_dispatch","status":"completed","conclusion":"success","head_sha":"%s","head_branch":"codex/recover-v1.0.52","run_attempt":1,"path":".github/workflows/release.yml","repository":{"full_name":"owner/repo"}}\n' "$RUN_HEAD_SHA" ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-delivered-stable.sh") run := func(runHeadSHA, publishConclusion string) (string, error) { cmd := exec.Command("sh", script, tag, commit) cmd.Dir = root cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "DWS_RELEASE_OFFICIAL_REPOSITORY=owner/repo", "WORKFLOW_SHA=" + workflowSHA, "RUN_HEAD_SHA=" + runHeadSHA, "PUBLISH_CONCLUSION=" + publishConclusion, } output, err := cmd.CombinedOutput() return string(output), err } if output, err := run(workflowSHA, "success"); err != nil || !strings.Contains(output, "reviewed recovery run "+runID) { t.Fatalf("pinned stable recovery was rejected: err=%v\noutput:\n%s", err, output) } if output, err := run(strings.Repeat("0", 40), "success"); err == nil || !strings.Contains(output, "does not match the pinned delivery proof") { t.Fatalf("mismatched recovery workflow passed: err=%v\noutput:\n%s", err, output) } if output, err := run(workflowSHA, "failure"); err == nil || !strings.Contains(output, "missing successful job publish-release") { t.Fatalf("failed recovery delivery job passed: err=%v\noutput:\n%s", err, output) } } func TestReleaseWorkflowDeliveryAcceptsOnlySharedProtectedRecovery(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() fakeCurl := filepath.Join(binDir, "curl") mustWriteFile(t, fakeCurl, []byte(`#!/bin/sh set -eu for argument in "$@"; do endpoint="$argument"; done case "$endpoint" in *event=push*) printf '{"workflow_runs":[]}\n' ;; *event=workflow_dispatch*) printf '{"workflow_runs":[{"id":42,"display_title":"Release recovery %s at %s %s-1700000000-42","event":"workflow_dispatch","status":"completed","conclusion":"success","head_branch":"main","head_sha":"%s","path":".github/workflows/release.yml","repository":{"full_name":"owner/repo"}}]}\n' "$TAG" "$RELEASE_COMMIT" "$RELEASE_COMMIT" "$WORKFLOW_SHA" ;; */compare/*...main) printf '{"status":"ahead"}\n' ;; */actions/runs/42/jobs*) printf '{"jobs":[' printf '{"name":"Build signed release artifacts","status":"completed","conclusion":"success","head_sha":"%s"},' "$WORKFLOW_SHA" printf '{"name":"Verify Apple Developer ID signatures","status":"completed","conclusion":"success","head_sha":"%s"},' "$WORKFLOW_SHA" printf '{"name":"Publish immutable GitHub Release","status":"completed","conclusion":"success","head_sha":"%s"}' "$WORKFLOW_SHA" if [ "${MISSING_CHANNELS:-0}" != 1 ]; then printf ',{"name":"Publish npm and mirrors","status":"completed","conclusion":"success","head_sha":"%s"}' "$WORKFLOW_SHA" fi printf ']}\n' ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-release-workflow-delivery.sh") tag := "v1.2.3-beta.1" commit := strings.Repeat("a", 40) workflowSHA := strings.Repeat("b", 40) run := func(missingChannels bool) (string, error) { cmd := exec.Command("sh", script, tag, commit) missing := "0" if missingChannels { missing = "1" } cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "DWS_RELEASE_OFFICIAL_REPOSITORY=owner/repo", "TAG=" + tag, "RELEASE_COMMIT=" + commit, "WORKFLOW_SHA=" + workflowSHA, "MISSING_CHANNELS=" + missing, } output, err := cmd.CombinedOutput() return string(output), err } output, err := run(false) if err != nil || !strings.Contains(output, "protected recovery run 42") { t.Fatalf("protected shared recovery was rejected: err=%v\noutput:\n%s", err, output) } output, err = run(true) if err == nil || !strings.Contains(output, "did not complete the shared release job graph") { t.Fatalf("incomplete recovery job graph passed: err=%v\noutput:\n%s", err, output) } } func TestReleaseWorkflowDeliveryAcceptsOnlyTagBoundCloudRelease(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() fakeCurl := filepath.Join(binDir, "curl") mustWriteFile(t, fakeCurl, []byte(`#!/bin/sh set -eu for argument in "$@"; do endpoint="$argument"; done case "$endpoint" in *event=push*) printf '{"workflow_runs":[]}\n' ;; */git/ref/tags/*) printf '{"object":{"type":"tag","sha":"%s"}}\n' "$TAG_OBJECT" ;; */git/tags/*) python3 - <<'PY' import json import os message = "\n".join([ f"Release {os.environ['TAG']}", "", "Channel: prerelease", "Release-Run: 42", "Release-Run-Attempt: 1", "Requested-By: release-user", "Requested-By-ID: 1234", f"Sealed-Commit: {os.environ['RELEASE_COMMIT']}", f"Workflow-Commit: {os.environ['RELEASE_COMMIT']}", f"Allocation-Fingerprint: {'d' * 64}", ]) print(json.dumps({ "tag": os.environ["TAG"], "message": message, "object": {"type": "commit", "sha": os.environ["RELEASE_COMMIT"]}, })) PY ;; */actions/runs/42/attempts/*/jobs*) python3 - <<'PY' import json import os required = [ "Plan next cloud release", "Seal cloud release tag", "release-contract", "Build signed release artifacts", "Verify Apple Developer ID signatures", "Publish immutable GitHub Release", "Publish npm and mirrors", "Release delivery gate", ] if os.environ.get("MISSING_SEAL") == "1": required.remove("Seal cloud release tag") jobs = [] for name in required: job = { "name": name, "status": "completed", "conclusion": "success", "head_sha": os.environ["RELEASE_COMMIT"], "steps": [], } if name == "Seal cloud release tag": job["steps"] = [{ "name": "Create one immutable annotated release tag", "status": "completed", "conclusion": "success", }] elif name == "Publish immutable GitHub Release": job["steps"] = [{ "name": "Require immutable published GitHub Release", "status": "completed", "conclusion": "success", }] if os.environ.get("PUBLISH_RELEASE_FAILURE") == "1": job["conclusion"] = "failure" elif name == "Publish npm and mirrors": job["conclusion"] = os.environ.get("CHANNEL_CONCLUSION", "success") jobs.append(job) print(json.dumps({"jobs": jobs})) PY ;; */actions/runs/42|*/actions/runs/42/attempts/1) python3 - <<'PY' import json import os print(json.dumps({ "id": 42, "run_attempt": int(os.environ.get("RUN_ATTEMPT", "1")), "repository": {"full_name": "owner/repo"}, "path": ".github/workflows/release.yml", "event": "workflow_dispatch", "status": "completed", "conclusion": os.environ.get("RUN_CONCLUSION", "success"), "head_branch": "main", "head_sha": os.environ["RELEASE_COMMIT"], "actor": { "login": os.environ.get("RUN_ACTOR", "release-user"), "id": int(os.environ.get("RUN_ACTOR_ID", "1234")), }, })) PY ;; *event=workflow_dispatch*) printf '{"workflow_runs":[]}\n' ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-release-workflow-delivery.sh") tag := "v1.2.3-beta.1" commit := strings.Repeat("a", 40) tagObject := strings.Repeat("b", 40) runWithArgs := func(arguments []string, overrides ...string) (string, error) { cmd := exec.Command("sh", script, tag, commit) cmd.Args = append([]string{"sh", script}, arguments...) cmd.Env = append([]string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "DWS_RELEASE_OFFICIAL_REPOSITORY=owner/repo", "TAG=" + tag, "TAG_OBJECT=" + tagObject, "RELEASE_COMMIT=" + commit, }, overrides...) output, err := cmd.CombinedOutput() return string(output), err } run := func(overrides ...string) (string, error) { return runWithArgs([]string{tag, commit}, overrides...) } if output, err := run(); err != nil || !strings.Contains(output, "cloud release run 42") { t.Fatalf("tag-bound cloud delivery was rejected: err=%v\noutput:\n%s", err, output) } if output, err := run("RUN_ATTEMPT=2"); err != nil || !strings.Contains(output, "cloud release run 42") { t.Fatalf("successful rerun of tag-bound cloud delivery was rejected: err=%v\noutput:\n%s", err, output) } if output, err := runWithArgs( []string{"--channel-repair", "oss", tag, commit}, ); err != nil || !strings.Contains(output, "cloud release run 42") { t.Fatalf("OSS repair could not use a successful release with the mirror enabled: err=%v\noutput:\n%s", err, output) } if output, err := run("RUN_ACTOR=renamed-release-user"); err != nil || !strings.Contains(output, "cloud release run 42") { t.Fatalf("cloud delivery broke after a harmless login rename: err=%v\noutput:\n%s", err, output) } if output, err := run("RUN_ACTOR_ID=9999"); err == nil || !strings.Contains(output, "did not deliver") { t.Fatalf("cloud delivery with the wrong stable actor ID passed: err=%v\noutput:\n%s", err, output) } if output, err := run("MISSING_SEAL=1"); err == nil || !strings.Contains(output, "did not deliver") { t.Fatalf("cloud delivery without the seal job passed: err=%v\noutput:\n%s", err, output) } if output, err := runWithArgs( []string{"--npm-repair", tag, commit}, "RUN_CONCLUSION=failure", "CHANNEL_CONCLUSION=failure", ); err != nil || !strings.Contains(output, "npm-repair authority verified") { t.Fatalf("npm repair could not use a sealed immutable release after npm failure: err=%v\noutput:\n%s", err, output) } if output, err := runWithArgs( []string{"--npm-repair", tag, commit}, "RUN_CONCLUSION=failure", "CHANNEL_CONCLUSION=failure", "PUBLISH_RELEASE_FAILURE=1", ); err == nil || strings.Contains(output, "npm-repair authority verified") { t.Fatalf("npm repair accepted a failed immutable GitHub publication: err=%v\noutput:\n%s", err, output) } } func TestReleaseWorkflowDeliveryChannelRepairRequiresLatestAttemptCoreDelivery(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() fakeCurl := filepath.Join(binDir, "curl") mustWriteFile(t, fakeCurl, []byte(`#!/bin/sh set -eu for argument in "$@"; do endpoint="$argument"; done case "$endpoint" in *event=push*) python3 - <<'PY' import json import os run = { "id": 77, "event": "push", "status": "completed", "conclusion": "failure", "head_branch": os.environ["TAG"], "head_sha": os.environ["RELEASE_COMMIT"], "path": os.environ.get("RUN_PATH", ".github/workflows/release.yml"), "repository": {"full_name": os.environ.get("RUN_REPOSITORY", "owner/repo")}, "run_attempt": int(os.environ.get("RUN_ATTEMPT", "2")), } runs = [run] if os.environ.get("DUPLICATE_RUN") == "1": duplicate = dict(run) duplicate["id"] = 78 runs.append(duplicate) print(json.dumps({"workflow_runs": runs})) PY ;; *event=workflow_dispatch*) printf '{"workflow_runs":[]}\n' ;; */actions/runs/77/attempts/2/jobs*) python3 - <<'PY' import json import os commit = os.environ.get("JOB_SHA", os.environ["RELEASE_COMMIT"]) core = [ "release-contract", "Build signed release artifacts", "Verify Apple Developer ID signatures", "Publish immutable GitHub Release", ] jobs = [{ "name": name, "status": "completed", "conclusion": ( os.environ.get("CORE_CONCLUSION", "success") if name == os.environ.get("CORE_JOB", "Build signed release artifacts") else "success" ), "head_sha": commit, "steps": ( [{ "name": "Require immutable published GitHub Release", "status": "completed", "conclusion": os.environ.get("IMMUTABLE_STEP_CONCLUSION", "success"), }] if name == "Publish immutable GitHub Release" else [] ), } for name in core] if os.environ.get("DUPLICATE_CORE") == "1": jobs.append(dict(jobs[1])) required_steps = [ "Download and verify immutable GitHub Release", "Verify immutable npm package without publication credentials", "Inspect npm channel state", "Verify npm channel delivery", ] jobs.append({ "name": "Publish npm and mirrors", "status": "completed", "conclusion": os.environ.get("CHANNEL_JOB_CONCLUSION", "failure"), "head_sha": commit, "steps": [{ "name": name, "status": "completed", "conclusion": ( os.environ.get("CHANNEL_STEP_CONCLUSION", "success") if name == os.environ.get("CHANNEL_STEP", "Verify npm channel delivery") else "success" ), } for name in required_steps] + [{ "name": "Sync release artifacts to China OSS mirror", "status": "completed", "conclusion": os.environ.get("OSS_STEP_CONCLUSION", "failure"), }], }) jobs.extend([ { "name": "Mirror immutable release to Gitee", "status": "completed", "conclusion": os.environ.get("GITEE_CONCLUSION", "skipped"), "head_sha": commit, "steps": [], }, { "name": "Release delivery gate", "status": "completed", "conclusion": os.environ.get("DELIVERY_GATE_CONCLUSION", "failure"), "head_sha": commit, "steps": [], }, ]) if os.environ.get("UNRELATED_FAILURE") == "1": jobs.append({ "name": "Unrelated release job", "status": "completed", "conclusion": "failure", "head_sha": commit, "steps": [], }) print(json.dumps({"jobs": jobs})) PY ;; */actions/runs/77/*/jobs*) echo "channel repair must inspect the exact latest run attempt" >&2 exit 91 ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-release-workflow-delivery.sh") tag := "v1.2.3-beta.1" commit := strings.Repeat("a", 40) run := func(args []string, overrides ...string) (string, error) { cmd := exec.Command("sh", append([]string{script}, args...)...) cmd.Env = append([]string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "DWS_RELEASE_OFFICIAL_REPOSITORY=owner/repo", "TAG=" + tag, "RELEASE_COMMIT=" + commit, }, overrides...) output, err := cmd.CombinedOutput() return string(output), err } repairArgs := func(target string) []string { return []string{"--channel-repair", target, tag, commit} } if output, err := run([]string{tag, commit}); err == nil || !strings.Contains(output, "did not deliver") { t.Fatalf("strict delivery accepted a failed tag run: err=%v\noutput:\n%s", err, output) } if output, err := run(repairArgs("oss")); err != nil || !strings.Contains(output, "failed exact-tag push run 77") { t.Fatalf("safe channel repair delivery was rejected: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "CORE_CONCLUSION=failure", ); err == nil || !strings.Contains(output, "required job 'Build signed release artifacts' did not succeed") { t.Fatalf("failed core release job passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "CHANNEL_STEP_CONCLUSION=failure", ); err == nil || !strings.Contains(output, "required channel step 'Verify npm channel delivery' did not succeed") { t.Fatalf("failed npm delivery proof passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "DUPLICATE_CORE=1", ); err == nil || !strings.Contains(output, "expected exactly one latest-attempt job 'Build signed release artifacts'") { t.Fatalf("duplicate latest-attempt core job passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "JOB_SHA="+strings.Repeat("b", 40), ); err == nil || !strings.Contains(output, "is not bound to "+commit) { t.Fatalf("wrong-sha release jobs passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "UNRELATED_FAILURE=1", ); err == nil || !strings.Contains(output, "unrelated job 'Unrelated release job' failed") { t.Fatalf("unrelated failed job passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "IMMUTABLE_STEP_CONCLUSION=failure", ); err == nil || !strings.Contains(output, "immutable GitHub Release verification did not succeed") { t.Fatalf("failed immutable release verification passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("gitee"), "CHANNEL_JOB_CONCLUSION=success", "OSS_STEP_CONCLUSION=success", "GITEE_CONCLUSION=failure", ); err != nil || !strings.Contains(output, "channel-repair authority verified") { t.Fatalf("single failed Gitee mirror was rejected: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "CHANNEL_JOB_CONCLUSION=success", "OSS_STEP_CONCLUSION=success", "GITEE_CONCLUSION=failure", ); err == nil || !strings.Contains(output, "OSS repair requires") { t.Fatalf("Gitee-only failure was accepted as OSS evidence: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("gitee"), ); err != nil || !strings.Contains(output, "channel-repair authority verified") { t.Fatalf("skipped Gitee backfill was rejected after upstream OSS failure: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "GITEE_CONCLUSION=failure", ); err == nil || !strings.Contains(output, "expected exactly one failed downstream channel job") { t.Fatalf("two failed downstream channels passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "DELIVERY_GATE_CONCLUSION=success", ); err == nil || !strings.Contains(output, "must end in a failed delivery gate") { t.Fatalf("successful terminal gate on a failed run passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "RUN_ATTEMPT=1", ); err == nil || strings.Contains(output, "channel-repair authority verified") { t.Fatalf("non-latest run attempt passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "RUN_REPOSITORY=other/repo", ); err == nil || strings.Contains(output, "channel-repair authority verified") { t.Fatalf("wrong-repository release run passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "RUN_PATH=.github/workflows/other.yml", ); err == nil || strings.Contains(output, "channel-repair authority verified") { t.Fatalf("wrong-workflow release run passed: err=%v\noutput:\n%s", err, output) } if output, err := run( repairArgs("oss"), "DUPLICATE_RUN=1", ); err == nil || strings.Contains(output, "channel-repair authority verified") { t.Fatalf("ambiguous failed release runs passed: err=%v\noutput:\n%s", err, output) } } func releaseChangelog(sections ...string) string { text := "# Changelog\n\n## [Unreleased]\n\n" for _, section := range sections { text += section if !strings.HasSuffix(text, "\n\n") { text += "\n" } } text += "## [1.0.0] - 2026-07-01\n\n### Changed\n\n- Initial release.\n" return text } func betaSection() string { return "## [1.0.1-beta.1] - 2026-07-11\n\n### Changed\n\n- Validate the sealed beta candidate.\n\n" } func stableSection() string { return "## [1.0.1] - 2026-07-11\n\nThis release promotes the sealed `v1.0.1-beta.1` contents to stable.\n\n### Changed\n\n- Publish the validated candidate to the stable channel.\n\n" } func (r *releaseTestRepo) commitAndPush(t *testing.T, message string) { t.Helper() mustRun(t, r.root, "git", "add", ".") mustRun(t, r.root, "git", "commit", "-m", message) mustRun(t, r.root, "git", "push", "origin", "main") } func (r *releaseTestRepo) seedBeta(t *testing.T) { t.Helper() mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) mustWriteFile(t, filepath.Join(r.root, "feature.txt"), []byte("sealed candidate\n"), 0o644) r.commitAndPush(t, "prepare beta") mustRun(t, r.root, "git", "tag", "-a", "v1.0.1-beta.1", "-m", "Release v1.0.1-beta.1", "-m", "Channel: prerelease") mustRun(t, r.root, "git", "push", "origin", "v1.0.1-beta.1") } func runReleaseScript(t *testing.T, workdir, script string, args ...string) (string, error) { t.Helper() cmd := exec.Command("sh", append([]string{script}, args...)...) cmd.Dir = workdir cmd.Env = append(os.Environ(), "DWS_RELEASE_ALLOW_NON_GITHUB_REMOTE=1") if remote, err := exec.Command("git", "-C", workdir, "remote", "get-url", "origin").Output(); err == nil { cmd.Env = append(cmd.Env, "DWS_RELEASE_OFFICIAL_TAGS_URL="+strings.TrimSpace(string(remote))) } output, err := cmd.CombinedOutput() return string(output), err } func TestReleaseContractAcceptsPrereleaseAndWritesNotes(t *testing.T) { r := newReleaseTestRepo(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "prepare beta changelog") notes := filepath.Join(t.TempDir(), "notes.md") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--context", "local", "--remote", "origin", "--notes-output", notes, ) if err != nil { t.Fatalf("release contract error = %v\noutput:\n%s", err, output) } if !strings.Contains(output, "Release contract passed") { t.Fatalf("contract output missing success:\n%s", output) } notesData, err := os.ReadFile(notes) if err != nil { t.Fatalf("ReadFile(notes) error = %v", err) } if !strings.Contains(string(notesData), "Validate the sealed beta candidate") { t.Fatalf("notes did not come from exact changelog section:\n%s", notesData) } } func TestReleaseContractRejectsInvalidVersionChannelPairs(t *testing.T) { r := newReleaseTestRepo(t) tests := []struct { channel string version string }{ {channel: "prerelease", version: "v1.0.1"}, {channel: "stable", version: "v1.0.1-beta.1"}, {channel: "prerelease", version: "v1.0.1-rc.1"}, {channel: "prerelease", version: "v1.0.1-preview"}, {channel: "stable", version: "1.0.1"}, {channel: "stable", version: "v01.0.1"}, {channel: "prerelease", version: "v1.0.1-beta.0"}, {channel: "prerelease", version: "v1.0.1-beta.2"}, } for _, test := range tests { t.Run(test.channel+"_"+strings.ReplaceAll(test.version, ".", "_"), func(t *testing.T) { output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", test.channel, "--version", test.version, ) if err == nil { t.Fatalf("invalid pair unexpectedly passed:\n%s", output) } }) } } func TestReleaseContractTreatsWithdrawnVersionsAsPermanentlyAllocated(t *testing.T) { t.Run("continues after withdrawn beta", func(t *testing.T) { r := newReleaseTestRepo(t) section := "## [1.0.1-beta.3] - 2026-07-11\n\n### Changed\n\n- Replace the withdrawn beta.\n\n" mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(section)), 0o644) r.commitAndPush(t, "prepare replacement beta") mustRun(t, r.root, "git", "tag", "-a", "v1.0.1-beta.1", "-m", "Release v1.0.1-beta.1") mustRun(t, r.root, "git", "tag", "-a", "withdrawn/v1.0.1-beta.2", "-m", "Withdraw v1.0.1-beta.2") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.3", "--remote", "origin", ) if err != nil { t.Fatalf("replacement beta was rejected: err=%v\noutput:\n%s", err, output) } }) t.Run("never reuses exact withdrawn version", func(t *testing.T) { r := newReleaseTestRepo(t) section := "## [1.0.1-beta.1] - 2026-07-11\n\n### Changed\n\n- Must not reuse this version.\n\n" mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(section)), 0o644) r.commitAndPush(t, "prepare withdrawn version") mustRun(t, r.root, "git", "tag", "-a", "withdrawn/v1.0.1-beta.1", "-m", "Withdraw v1.0.1-beta.1") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "can never be reused") { t.Fatalf("withdrawn version was reusable: err=%v\noutput:\n%s", err, output) } }) t.Run("withdrawn beta cannot be promoted", func(t *testing.T) { r := newReleaseTestRepo(t) r.seedBeta(t) mustRun(t, r.root, "git", "tag", "-a", "withdrawn/v1.0.1-beta.1", "-m", "Withdraw v1.0.1-beta.1") mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(stableSection(), betaSection())), 0o644) r.commitAndPush(t, "prepare blocked stable") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "stable", "--version", "v1.0.1", "--from-beta", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "beta baseline was withdrawn") { t.Fatalf("withdrawn beta was promoted: err=%v\noutput:\n%s", err, output) } }) t.Run("withdrawn stable is a permanent version floor", func(t *testing.T) { r := newReleaseTestRepo(t) mustRun(t, r.root, "git", "tag", "-a", "withdrawn/v2.0.0", "-m", "Withdraw v2.0.0") section := "## [1.1.0-beta.1] - 2026-07-11\n\n### Changed\n\n- Must remain above every allocated stable.\n\n" mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(section)), 0o644) r.commitAndPush(t, "prepare invalid lower beta") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.1.0-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "must be greater than latest allocated stable v2.0.0") { t.Fatalf("release below withdrawn stable floor passed: err=%v\noutput:\n%s", err, output) } }) t.Run("tombstone excludes stale ordinary tag from delivered baseline", func(t *testing.T) { r := newReleaseTestRepo(t) mustRun(t, r.root, "git", "tag", "-a", "v1.0.1", "-m", "Release v1.0.1") mustRun(t, r.root, "git", "tag", "-a", "withdrawn/v1.0.1", "-m", "Withdraw v1.0.1") section := "## [1.0.2-beta.1] - 2026-07-11\n\n### Changed\n\n- Continue after the withdrawn stable.\n\n" mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(section)), 0o644) r.commitAndPush(t, "prepare beta after withdrawn stable") metadata := filepath.Join(t.TempDir(), "metadata") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.2-beta.1", "--remote", "origin", "--metadata-output", metadata, ) if err != nil { t.Fatalf("release after stale withdrawn tag failed: err=%v\noutput:\n%s", err, output) } assertFileContains(t, metadata, "previous_stable=v1.0.0") if content, err := os.ReadFile(metadata); err != nil { t.Fatalf("ReadFile(metadata) error = %v", err) } else if strings.Contains(string(content), "previous_stable=v1.0.1") { t.Fatalf("withdrawn stale tag remained the delivered baseline:\n%s", content) } }) } func TestReleaseContractRejectsBadChangelogSections(t *testing.T) { tests := []struct { name string sections string want string }{ {name: "missing", sections: "", want: "exactly one section"}, {name: "empty", sections: "## [1.0.1-beta.1] - 2026-07-11\n\n", want: "must contain release notes"}, {name: "placeholder", sections: "## [1.0.1-beta.1] - 2026-07-11\n\n### Changed\n\n- TODO: write this.\n\n", want: "TODO/TBD"}, {name: "duplicate", sections: betaSection() + betaSection(), want: "exactly one section"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { r := newReleaseTestRepo(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(test.sections)), 0o644) mustWriteFile(t, filepath.Join(r.root, "candidate.txt"), []byte(test.name+"\n"), 0o644) r.commitAndPush(t, "write candidate changelog") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil { t.Fatalf("bad changelog unexpectedly passed:\n%s", output) } if !strings.Contains(output, test.want) { t.Fatalf("output missing %q:\n%s", test.want, output) } }) } } func TestReleaseContractRejectsDirtyOrUnsyncedMain(t *testing.T) { r := newReleaseTestRepo(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "prepare beta") mustWriteFile(t, filepath.Join(r.root, "dirty.txt"), []byte("dirty\n"), 0o644) output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "worktree must be clean") { t.Fatalf("dirty worktree was not blocked: err=%v\noutput:\n%s", err, output) } mustRun(t, r.root, "git", "add", "dirty.txt") mustRun(t, r.root, "git", "commit", "-m", "local commit not pushed") output, err = runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "must be contained in origin/main history") { t.Fatalf("unsynced main was not blocked: err=%v\noutput:\n%s", err, output) } } func TestReleaseContractStablePromotionRequiresBetaAncestry(t *testing.T) { t.Run("sealed", func(t *testing.T) { r := newReleaseTestRepo(t) r.seedBeta(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(stableSection(), betaSection())), 0o644) r.commitAndPush(t, "prepare stable changelog") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "stable", "--version", "v1.0.1", "--from-beta", "v1.0.1-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("sealed stable promotion error = %v\noutput:\n%s", err, output) } }) t.Run("commits after beta", func(t *testing.T) { r := newReleaseTestRepo(t) r.seedBeta(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(stableSection(), betaSection())), 0o644) mustWriteFile(t, filepath.Join(r.root, "followup.txt"), []byte("merged after beta\n"), 0o644) r.commitAndPush(t, "merge follow-up after beta") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "stable", "--version", "v1.0.1", "--from-beta", "v1.0.1-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("stable promotion with commits after beta error = %v\noutput:\n%s", err, output) } }) t.Run("beta outside HEAD history", func(t *testing.T) { r := newReleaseTestRepo(t) mustRun(t, r.root, "git", "checkout", "-b", "sidecar") mustWriteFile(t, filepath.Join(r.root, "sidecar.txt"), []byte("never merged\n"), 0o644) mustRun(t, r.root, "git", "add", ".") mustRun(t, r.root, "git", "commit", "-m", "sidecar beta candidate") mustRun(t, r.root, "git", "tag", "-a", "v1.0.1-beta.1", "-m", "Release v1.0.1-beta.1", "-m", "Channel: prerelease") mustRun(t, r.root, "git", "checkout", "main") mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(stableSection(), betaSection())), 0o644) r.commitAndPush(t, "prepare stable changelog") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "stable", "--version", "v1.0.1", "--from-beta", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "not an ancestor of HEAD") { t.Fatalf("beta outside HEAD history was promoted: err=%v\noutput:\n%s", err, output) } }) t.Run("older sealed commit after main advanced", func(t *testing.T) { r := newReleaseTestRepo(t) r.seedBeta(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(stableSection(), betaSection())), 0o644) r.commitAndPush(t, "prepare stable changelog") sealed := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "HEAD")) mustWriteFile(t, filepath.Join(r.root, "after.txt"), []byte("main advanced\n"), 0o644) r.commitAndPush(t, "advance main after stable candidate") mustRun(t, r.root, "git", "checkout", "--detach", sealed) output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "stable", "--version", "v1.0.1", "--from-beta", "v1.0.1-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("older sealed commit in main history was rejected: %v\noutput:\n%s", err, output) } }) } func TestReleaseContractCIReadsStableBaselineFromAnnotatedTag(t *testing.T) { r := newReleaseTestRepo(t) r.seedBeta(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(stableSection(), betaSection())), 0o644) r.commitAndPush(t, "prepare stable changelog") mustRun(t, r.root, "git", "tag", "-a", "v1.0.1", "-m", "Release v1.0.1", "-m", "Channel: stable", "-m", "From-Beta: v1.0.1-beta.1") mustRun(t, r.root, "git", "push", "origin", "v1.0.1") mustRun(t, r.root, "git", "checkout", "--detach", "v1.0.1") metadata := filepath.Join(t.TempDir(), "metadata") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "stable", "--version", "v1.0.1", "--context", "ci", "--remote", "origin", "--metadata-output", metadata, ) if err != nil { t.Fatalf("CI stable contract error = %v\noutput:\n%s", err, output) } metadataData, err := os.ReadFile(metadata) if err != nil { t.Fatalf("ReadFile(metadata) error = %v", err) } if !strings.Contains(string(metadataData), "from_beta=v1.0.1-beta.1") { t.Fatalf("metadata missing annotated tag baseline:\n%s", metadataData) } } func TestReleaseContractCIRejectsLightweightTag(t *testing.T) { r := newReleaseTestRepo(t) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "prepare beta") mustRun(t, r.root, "git", "tag", "v1.0.1-beta.1") mustRun(t, r.root, "git", "push", "origin", "v1.0.1-beta.1") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--context", "ci", "--remote", "origin", ) if err == nil { t.Fatalf("lightweight release tag unexpectedly passed:\n%s", output) } if !strings.Contains(output, "must be annotated") { t.Fatalf("output missing annotated-tag guidance:\n%s", output) } } func TestReleaseContractCIAllowsMainToAdvanceAfterTagSeal(t *testing.T) { r := newReleaseTestRepo(t) r.seedBeta(t) mustWriteFile(t, filepath.Join(r.root, "after-seal.txt"), []byte("main advanced\n"), 0o644) r.commitAndPush(t, "advance main after beta seal") mustRun(t, r.root, "git", "checkout", "--detach", "v1.0.1-beta.1") output, err := runReleaseScript(t, r.root, r.contract, "--repo-root", r.root, "--channel", "prerelease", "--version", "v1.0.1-beta.1", "--context", "ci", "--remote", "origin", ) if err != nil { t.Fatalf("sealed tag should remain valid after main advances: %v\noutput:\n%s", err, output) } } func TestReleasePrepareChangelogRendersAndArchivesPrereleaseFragments(t *testing.T) { r := newReleaseTestRepo(t) releaseCopyFile(t, r.lib, filepath.Join(r.root, "scripts", "release", "release-lib.sh"), 0o644) releaseCopyFile(t, r.prepare, filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), 0o755) releaseCopyFile(t, r.render, filepath.Join(r.root, "scripts", "release", "render-release-fragments.sh"), 0o755) mustWriteFile(t, filepath.Join(r.root, ".changes", "1234-chat-reply.md"), []byte("---\ncategory: Added\n---\n\n- **Chat reply mentions** (#1234) — supports selected member mentions.\n"), 0o644) mustWriteFile(t, filepath.Join(r.root, ".changes", "1235-chat-fix.md"), []byte("---\ncategory: Fixed\n---\n\n- **Chat reply fallback** (#1235) — keeps replies compatible with older clients.\n"), 0o644) r.commitAndPush(t, "install changelog preparation") cmd := exec.Command("sh", filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), "prerelease", "v1.0.1-beta.1") cmd.Dir = r.root cmd.Env = append(os.Environ(), "DWS_RELEASE_DATE=2026-07-11") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("prepare changelog error = %v\noutput:\n%s", err, output) } changelog, err := os.ReadFile(filepath.Join(r.root, "CHANGELOG.md")) if err != nil { t.Fatalf("ReadFile(CHANGELOG.md) error = %v", err) } content := string(changelog) if !strings.Contains(content, "## [1.0.1-beta.1] - 2026-07-11") || !strings.Contains(content, "### Added") || !strings.Contains(content, "Chat reply mentions") || !strings.Contains(content, "### Fixed") || !strings.Contains(content, "Chat reply fallback") || strings.Contains(content, "TODO") { t.Fatalf("prepared changelog did not render release fragments:\n%s", changelog) } for _, name := range []string{"1234-chat-reply.md", "1235-chat-fix.md"} { if _, err := os.Stat(filepath.Join(r.root, ".changes", name)); !os.IsNotExist(err) { t.Fatalf("unconsumed release fragment %s still present: %v", name, err) } if _, err := os.Stat(filepath.Join(r.root, ".changes", "released", "1.0.1-beta.1", name)); err != nil { t.Fatalf("archived release fragment %s missing: %v", name, err) } } } func TestReleasePrepareChangelogRendersAndArchivesStableFragments(t *testing.T) { r := newReleaseTestRepo(t) releaseCopyFile(t, r.lib, filepath.Join(r.root, "scripts", "release", "release-lib.sh"), 0o644) releaseCopyFile(t, r.prepare, filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), 0o755) releaseCopyFile(t, r.render, filepath.Join(r.root, "scripts", "release", "render-release-fragments.sh"), 0o755) mustWriteFile(t, filepath.Join(r.root, ".changes", "1236-stable-followup.md"), []byte("---\ncategory: Fixed\n---\n\n- **Stable follow-up** (#1236) — includes a change merged after the beta.\n"), 0o644) r.commitAndPush(t, "install stable changelog preparation") cmd := exec.Command( "sh", filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), "stable", "v1.0.1", "--from-beta", "v1.0.1-beta.1", ) cmd.Dir = r.root cmd.Env = append(os.Environ(), "DWS_RELEASE_DATE=2026-07-11") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("prepare stable changelog error = %v\noutput:\n%s", err, output) } changelog, err := os.ReadFile(filepath.Join(r.root, "CHANGELOG.md")) if err != nil { t.Fatalf("ReadFile(CHANGELOG.md) error = %v", err) } content := string(changelog) if !strings.Contains(content, "## [1.0.1] - 2026-07-11") || !strings.Contains(content, "promotes the sealed `v1.0.1-beta.1` contents to stable") || !strings.Contains(content, "### Changes since `v1.0.1-beta.1`") || !strings.Contains(content, "### Fixed") || !strings.Contains(content, "Stable follow-up") { t.Fatalf("prepared stable changelog did not render post-beta fragments:\n%s", changelog) } name := "1236-stable-followup.md" if _, err := os.Stat(filepath.Join(r.root, ".changes", name)); !os.IsNotExist(err) { t.Fatalf("unconsumed stable release fragment %s still present: %v", name, err) } if _, err := os.Stat(filepath.Join(r.root, ".changes", "released", "1.0.1", name)); err != nil { t.Fatalf("archived stable release fragment %s missing: %v", name, err) } } func TestReleasePrepareChangelogStableWithoutFragmentsKeepsTemplate(t *testing.T) { r := newReleaseTestRepo(t) releaseCopyFile(t, r.lib, filepath.Join(r.root, "scripts", "release", "release-lib.sh"), 0o644) releaseCopyFile(t, r.prepare, filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), 0o755) releaseCopyFile(t, r.render, filepath.Join(r.root, "scripts", "release", "render-release-fragments.sh"), 0o755) r.commitAndPush(t, "install stable changelog preparation") cmd := exec.Command( "sh", filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), "stable", "v1.0.1", "--from-beta", "v1.0.1-beta.1", ) cmd.Dir = r.root cmd.Env = append(os.Environ(), "DWS_RELEASE_DATE=2026-07-11") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("prepare stable changelog without fragments error = %v\noutput:\n%s", err, output) } changelog, err := os.ReadFile(filepath.Join(r.root, "CHANGELOG.md")) if err != nil { t.Fatalf("ReadFile(CHANGELOG.md) error = %v", err) } content := string(changelog) if !strings.Contains(content, "## [1.0.1] - 2026-07-11") || !strings.Contains(content, "TODO: summarize the complete user-visible release") || strings.Contains(content, "### Changes since") { t.Fatalf("stable changelog without fragments changed template behavior:\n%s", changelog) } if _, err := os.Stat(filepath.Join(r.root, ".changes", "released", "1.0.1")); !os.IsNotExist(err) { t.Fatalf("stable preparation without fragments created an archive: %v", err) } } func TestReleasePrepareChangelogRejectsInvalidReleaseFragment(t *testing.T) { r := newReleaseTestRepo(t) releaseCopyFile(t, r.lib, filepath.Join(r.root, "scripts", "release", "release-lib.sh"), 0o644) releaseCopyFile(t, r.prepare, filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), 0o755) releaseCopyFile(t, r.render, filepath.Join(r.root, "scripts", "release", "render-release-fragments.sh"), 0o755) mustWriteFile(t, filepath.Join(r.root, ".changes", "invalid.md"), []byte("---\ncategory: Added\n---\n\n- TODO: write this later.\n"), 0o644) r.commitAndPush(t, "add invalid release fragment") cmd := exec.Command("sh", filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), "prerelease", "v1.0.1-beta.1") cmd.Dir = r.root cmd.Env = append(os.Environ(), "DWS_RELEASE_DATE=2026-07-11") if output, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(output), "must not contain TODO/TBD") { t.Fatalf("invalid release fragment was accepted: err=%v\noutput:\n%s", err, output) } } const releaseValidFragment = "---\ncategory: Added\n---\n\n- **Chat reply mentions** (#1234) — supports selected member mentions.\n" // The renderer used to skip anything `find -type f -name '*.md'` did not match, // so a symlinked or illegally named fragment was dropped from the notes without // a word. Every unexpected top-level entry must fail the release instead. func TestReleasePrepareChangelogRejectsUnsafeReleaseFragmentEntries(t *testing.T) { tests := []struct { name string seed func(t *testing.T, changesDir string) wantMsg string }{ { name: "symlinked fragment", seed: func(t *testing.T, changesDir string) { mustWriteFile(t, filepath.Join(changesDir, "1234-chat.md"), []byte(releaseValidFragment), 0o644) if err := os.Symlink("1234-chat.md", filepath.Join(changesDir, "5678-alias.md")); err != nil { t.Fatalf("Symlink release fragment: %v", err) } }, wantMsg: "not a symbolic link", }, { name: "fragment name with a space", seed: func(t *testing.T, changesDir string) { mustWriteFile(t, filepath.Join(changesDir, "chat reply.md"), []byte(releaseValidFragment), 0o644) }, wantMsg: "invalid release fragment filename", }, { name: "uppercase fragment name", seed: func(t *testing.T, changesDir string) { mustWriteFile(t, filepath.Join(changesDir, "Chat.md"), []byte(releaseValidFragment), 0o644) }, wantMsg: "invalid release fragment filename", }, { name: "non-markdown entry", seed: func(t *testing.T, changesDir string) { mustWriteFile(t, filepath.Join(changesDir, "notes.txt"), []byte(releaseValidFragment), 0o644) }, wantMsg: "invalid release fragment filename", }, { // The PR gate is what keeps a nested directory out of `.changes/`; // this covers the release-side backstop that reports it instead of // rendering notes from an unexpected tree shape. name: "nested fragment directory", seed: func(t *testing.T, changesDir string) { mustWriteFile(t, filepath.Join(changesDir, "foo", "bar.md"), []byte(releaseValidFragment), 0o644) }, wantMsg: "unexpected directory in release fragments directory", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { r := newReleaseTestRepo(t) releaseCopyFile(t, r.lib, filepath.Join(r.root, "scripts", "release", "release-lib.sh"), 0o644) releaseCopyFile(t, r.prepare, filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), 0o755) releaseCopyFile(t, r.render, filepath.Join(r.root, "scripts", "release", "render-release-fragments.sh"), 0o755) test.seed(t, filepath.Join(r.root, ".changes")) r.commitAndPush(t, "add unsafe release fragment entry") cmd := exec.Command("sh", filepath.Join(r.root, "scripts", "release", "prepare-changelog.sh"), "prerelease", "v1.0.1-beta.1") cmd.Dir = r.root cmd.Env = append(os.Environ(), "DWS_RELEASE_DATE=2026-07-11") output, err := cmd.CombinedOutput() if err == nil { t.Fatalf("unsafe release fragment entry was accepted:\n%s", output) } if !strings.Contains(string(output), test.wantMsg) { t.Fatalf("renderer output missing %q:\n%s", test.wantMsg, output) } }) } } func TestReleaseMirrorUsesChannelSpecificPointer(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } script := filepath.Join(sourceRoot, "scripts", "release", "sync-to-oss.sh") for _, test := range []struct { name string version string channel string want string doNotWant string installer bool }{ {name: "prerelease", version: "v1.2.3-beta.1", channel: "prerelease", want: "/beta.txt", doNotWant: "/latest.txt", installer: false}, {name: "stable", version: "v1.2.3", channel: "stable", want: "/latest.txt", doNotWant: "/beta.txt", installer: true}, } { t.Run(test.name, func(t *testing.T) { root := t.TempDir() dist := filepath.Join(root, "dist") seedVersionedReleaseArtifacts(t, dist, test.version) logPath := filepath.Join(root, "ossutil.log") argsLogPath := filepath.Join(root, "ossutil-args.log") fakeOSSUtil := filepath.Join(root, "ossutil") mustWriteFile(t, fakeOSSUtil, []byte("#!/bin/sh\nset -eu\nprintf '%s\\n' \"$@\" >> \"$OSSUTIL_ARGS_LOG\"\nprevious=\npenultimate=\nfor arg in \"$@\"; do penultimate=\"$previous\"; previous=\"$arg\"; done\nlast=\"$previous\"\ncase \"$penultimate\" in oss://*) echo 'ErrorCode=NoSuchKey' >&2; exit 1 ;; esac\nprintf '%s\\n' \"$last\" >> \"$OSSUTIL_LOG\"\n"), 0o755) cmd := exec.Command("bash", script) cmd.Dir = sourceRoot cmd.Env = append(os.Environ(), "DIST_DIR="+dist, "VERSION="+test.version, "DWS_RELEASE_CHANNEL="+test.channel, "OSS_ACCESS_KEY_ID=test-key", "OSS_ACCESS_KEY_SECRET=test-secret", "OSS_ENDPOINT=https://oss.example.com", "OSS_REGION=cn-test", "OSS_BUCKET=test-bucket", "OSS_PREFIX=dws", "OSSUTIL="+fakeOSSUtil, "OSSUTIL_LOG="+logPath, "OSSUTIL_ARGS_LOG="+argsLogPath, ) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("sync-to-oss error = %v\noutput:\n%s", err, output) } logData, err := os.ReadFile(logPath) if err != nil { t.Fatalf("ReadFile(ossutil log) error = %v", err) } if !strings.Contains(string(logData), test.want) { t.Fatalf("mirror log missing %q:\n%s", test.want, logData) } if strings.Contains(string(logData), test.doNotWant) { t.Fatalf("mirror log unexpectedly contains %q:\n%s", test.doNotWant, logData) } hasInstaller := strings.Contains(string(logData), "/dws/install.sh") if hasInstaller != test.installer { t.Fatalf("installer upload = %v, want %v:\n%s", hasInstaller, test.installer, logData) } argsData, err := os.ReadFile(argsLogPath) if err != nil { t.Fatalf("ReadFile(ossutil args log) error = %v", err) } for _, forbidden := range []string{"test-key", "test-secret", "--access-key-id", "--access-key-secret"} { if strings.Contains(string(argsData), forbidden) { t.Fatalf("ossutil argv exposed %q:\n%s", forbidden, argsData) } } }) } } func TestReleaseMirrorFailsClosedWhenPointerCannotBeRead(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } script := filepath.Join(sourceRoot, "scripts", "release", "sync-to-oss.sh") for _, test := range []struct { name string readAction string want string }{ {name: "transport error", readAction: "echo 'connection timed out' >&2; exit 7", want: "Could not read OSS beta.txt"}, {name: "empty pointer", readAction: ": > \"$last\"; exit 0", want: "read successfully but is empty"}, } { t.Run(test.name, func(t *testing.T) { root := t.TempDir() dist := filepath.Join(root, "dist") seedVersionedReleaseArtifacts(t, dist, "v1.2.3-beta.1") logPath := filepath.Join(root, "ossutil.log") fakeOSSUtil := filepath.Join(root, "ossutil") scriptText := "#!/bin/sh\nset -eu\nprevious=\npenultimate=\nfor arg in \"$@\"; do penultimate=\"$previous\"; previous=\"$arg\"; done\nlast=\"$previous\"\ncase \"$penultimate\" in oss://*) " + test.readAction + " ;; esac\nprintf '%s\\n' \"$last\" >> \"$OSSUTIL_LOG\"\n" mustWriteFile(t, fakeOSSUtil, []byte(scriptText), 0o755) cmd := exec.Command("bash", script) cmd.Dir = sourceRoot cmd.Env = append(os.Environ(), "DIST_DIR="+dist, "VERSION=v1.2.3-beta.1", "DWS_RELEASE_CHANNEL=prerelease", "OSS_ACCESS_KEY_ID=test-key", "OSS_ACCESS_KEY_SECRET=test-secret", "OSS_ENDPOINT=https://oss.example.com", "OSS_REGION=cn-test", "OSS_BUCKET=test-bucket", "OSS_PREFIX=dws", "OSSUTIL="+fakeOSSUtil, "OSSUTIL_LOG="+logPath, ) output, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), test.want) { t.Fatalf("pointer failure was not closed: err=%v, want=%q\noutput:\n%s", err, test.want, output) } if logData, readErr := os.ReadFile(logPath); readErr == nil && strings.Contains(string(logData), "/beta.txt") { t.Fatalf("failed pointer read still published beta pointer:\n%s", logData) } }) } } func TestReleaseMirrorRepairsHistoricalAssetsWithoutMovingNewerPointer(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } root := t.TempDir() dist := filepath.Join(root, "dist") seedVersionedReleaseArtifacts(t, dist, "v1.2.3-beta.1") logPath := filepath.Join(root, "ossutil.log") fakeOSSUtil := filepath.Join(root, "ossutil") mustWriteFile(t, fakeOSSUtil, []byte("#!/bin/sh\nset -eu\nprevious=\npenultimate=\nfor arg in \"$@\"; do penultimate=\"$previous\"; previous=\"$arg\"; done\nlast=\"$previous\"\ncase \"$penultimate\" in oss://*) printf 'v1.2.4-beta.1\\n' > \"$last\"; exit 0 ;; esac\nprintf '%s\\n' \"$last\" >> \"$OSSUTIL_LOG\"\n"), 0o755) cmd := exec.Command("bash", filepath.Join(sourceRoot, "scripts", "release", "sync-to-oss.sh")) cmd.Dir = sourceRoot cmd.Env = append(os.Environ(), "DIST_DIR="+dist, "VERSION=v1.2.3-beta.1", "DWS_RELEASE_CHANNEL=prerelease", "OSS_ACCESS_KEY_ID=test-key", "OSS_ACCESS_KEY_SECRET=test-secret", "OSS_ENDPOINT=https://oss.example.com", "OSS_REGION=cn-test", "OSS_BUCKET=test-bucket", "OSS_PREFIX=dws", "OSSUTIL="+fakeOSSUtil, "OSSUTIL_LOG="+logPath, ) output, err := cmd.CombinedOutput() if err != nil || !strings.Contains(string(output), "assets will be repaired without moving it") { t.Fatalf("historical OSS repair failed: err=%v\noutput:\n%s", err, output) } logData, err := os.ReadFile(logPath) if err != nil { t.Fatalf("ReadFile(ossutil log) error = %v", err) } if strings.Contains(string(logData), "/beta.txt") { t.Fatalf("historical repair moved beta pointer:\n%s", logData) } if !strings.Contains(string(logData), "/download/v1.2.3-beta.1/") { t.Fatalf("historical repair did not upload target assets:\n%s", logData) } } func TestReleaseRequiredMirrorsFailWithoutCredentials(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } for _, test := range []struct { name string script string require string want string }{ {name: "oss", script: "sync-to-oss.sh", require: "DWS_REQUIRE_OSS=1", want: "OSS mirror sync is required"}, {name: "gitee", script: "sync-to-gitee.sh", require: "DWS_REQUIRE_GITEE=1", want: "Gitee mirror sync is enabled"}, } { t.Run(test.name, func(t *testing.T) { cmd := exec.Command("bash", filepath.Join(sourceRoot, "scripts", "release", test.script)) cmd.Dir = sourceRoot cmd.Env = []string{"PATH=" + os.Getenv("PATH"), "HOME=" + t.TempDir(), test.require} output, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), test.want) { t.Fatalf("required mirror did not fail closed: err=%v, want=%q\noutput:\n%s", err, test.want, output) } }) } } func TestReleaseArtifactVerificationRequiresEveryChecksum(t *testing.T) { r := newReleaseTestRepo(t) dist := t.TempDir() seedVersionedReleaseArtifacts(t, dist, "v1.2.3") cmd := exec.Command("sh", r.verify, "v1.2.3") cmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+dist) if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("artifact verification error = %v\noutput:\n%s", err, output) } writeReleaseChecksums(t, dist, false) cmd = exec.Command("sh", r.verify, "v1.2.3") cmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+dist) output, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), "dws-skills.zip exactly once") { t.Fatalf("missing checksum was not blocked: err=%v\noutput:\n%s", err, output) } writeReleaseChecksums(t, dist, true) mustWriteFile(t, filepath.Join(dist, "dws-linux-riscv64.tar.gz"), []byte("unexpected\n"), 0o644) cmd = exec.Command("sh", r.verify, "v1.2.3") cmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+dist) if output, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(output), "public release assets") { t.Fatalf("extra public archive was not rejected: err=%v\noutput:\n%s", err, output) } if err := os.Remove(filepath.Join(dist, "dws-linux-riscv64.tar.gz")); err != nil { t.Fatalf("Remove(extra archive) error = %v", err) } writeVersionedReleaseArchive(t, dist, "dws-windows-arm64.zip", "v1.2.2") writeReleaseChecksums(t, dist, true) cmd = exec.Command("sh", r.verify, "v1.2.3") cmd.Env = append(os.Environ(), "DWS_PACKAGE_DIST_DIR="+dist) if output, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(output), "dws-windows-arm64.zip binary") { t.Fatalf("mixed-version archive was not rejected: err=%v\noutput:\n%s", err, output) } } func TestReleaseCommandValidatesThenPushesAnnotatedTag(t *testing.T) { r := newReleaseTestRepo(t) installReleaseCommandFixture(t, r) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "install release automation") output, err := runReleaseScript(t, r.root, filepath.Join(r.root, "scripts", "release", "release.sh"), "prerelease", "v1.0.1-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("release validation error = %v\noutput:\n%s", err, output) } if !strings.Contains(output, "No tag was created") { t.Fatalf("validation output missing dry-run result:\n%s", output) } if mustOutput(t, r.root, "git", "tag", "--list", "v1.0.1-beta.1") != "" { t.Fatal("validation-only release created a tag") } output, err = runReleaseScript(t, r.root, filepath.Join(r.root, "scripts", "release", "release.sh"), "prerelease", "v1.0.1-beta.1", "--remote", "origin", "--publish", "--yes", ) if err != nil { t.Fatalf("release publish error = %v\noutput:\n%s", err, output) } if got := strings.TrimSpace(mustOutput(t, r.root, "git", "cat-file", "-t", "v1.0.1-beta.1")); got != "tag" { t.Fatalf("release tag type = %q, want annotated tag", got) } if got := mustOutput(t, r.root, "git", "ls-remote", "--tags", "origin", "refs/tags/v1.0.1-beta.1"); !strings.Contains(got, "refs/tags/v1.0.1-beta.1") { t.Fatalf("remote release tag missing:\n%s", got) } } func TestReleaseCommandReusesExactPreflightProof(t *testing.T) { r := newReleaseTestRepo(t) installReleaseCommandFixture(t, r) mustWriteFile(t, filepath.Join(r.root, ".gitignore"), []byte("/dws\n"), 0o644) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) mustWriteFile(t, filepath.Join(r.root, "Makefile"), []byte(`test: @rm -f dws @printf 'test\n' >> "$$(git rev-parse --git-path release-phases)" build: @printf '#!/bin/sh\nexit 0\n' > dws @chmod +x dws @printf 'build\n' >> "$$(git rev-parse --git-path release-phases)" policy: @test -x dws @printf 'policy\n' >> "$$(git rev-parse --git-path release-phases)" package: @printf 'package\n' >> "$$(git rev-parse --git-path release-phases)" `), 0o644) r.commitAndPush(t, "install proof-aware release automation") command := filepath.Join(r.root, "scripts", "release", "release.sh") output, err := runReleaseScript(t, r.root, command, "prerelease", "v1.0.1-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("release validation error = %v\noutput:\n%s", err, output) } if !strings.Contains(output, "Publish within six hours") { t.Fatalf("validation output missing reusable-proof guidance:\n%s", output) } phasePath := filepath.Join(r.root, ".git", "release-phases") phaseData, err := os.ReadFile(phasePath) if err != nil { t.Fatalf("ReadFile(release phases) error = %v", err) } wantPhases := "test\nbuild\npolicy\npackage\n" if string(phaseData) != wantPhases { t.Fatalf("release phase order = %q, want %q", phaseData, wantPhases) } proofPath := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "--git-path", "dws-release-preflight/v1.0.1-beta.1.proof")) if !filepath.IsAbs(proofPath) { proofPath = filepath.Join(r.root, proofPath) } proofBefore, err := os.ReadFile(proofPath) if err != nil { t.Fatalf("ReadFile(preflight proof) error = %v", err) } hook := filepath.Join(r.remote, "hooks", "pre-receive") mustWriteFile(t, hook, []byte("#!/bin/sh\nset -eu\nwhile read -r old new ref; do\n case \"$ref\" in refs/tags/*) exit 1 ;; esac\ndone\nexit 0\n"), 0o755) failedOutput, failedErr := runReleaseScript(t, r.root, command, "prerelease", "v1.0.1-beta.1", "--remote", "origin", "--publish", "--yes", ) if failedErr == nil || !strings.Contains(failedOutput, "new local tag was removed") { t.Fatalf("rejected proof-based publish did not fail safely: err=%v\noutput:\n%s", failedErr, failedOutput) } proofAfterFailure, err := os.ReadFile(proofPath) if err != nil { t.Fatalf("ReadFile(preflight proof after failure) error = %v", err) } if string(proofAfterFailure) != string(proofBefore) { t.Fatalf("failed publish refreshed reusable proof:\nbefore:\n%s\nafter:\n%s", proofBefore, proofAfterFailure) } if err := os.Remove(hook); err != nil { t.Fatalf("Remove(rejecting hook) error = %v", err) } output, err = runReleaseScript(t, r.root, command, "prerelease", "v1.0.1-beta.1", "--remote", "origin", "--publish", "--yes", ) if err != nil { t.Fatalf("release publish error = %v\noutput:\n%s", err, output) } if !strings.Contains(output, "Reusing exact preflight proof") { t.Fatalf("publish did not reuse the exact preflight proof:\n%s", output) } phaseData, err = os.ReadFile(phasePath) if err != nil { t.Fatalf("ReadFile(release phases after publish) error = %v", err) } if string(phaseData) != wantPhases { t.Fatalf("publish reran expensive phases: %q", phaseData) } } func TestReleaseGovernanceCIDispatchBindsExactRunIdentity(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() stateDir := t.TempDir() fakeGH := filepath.Join(binDir, "gh") mustWriteFile(t, fakeGH, []byte(`#!/bin/sh set -eu command="$1" shift case "$command" in workflow) test "$1" = run shift printf '%s\n' "$@" > "$GH_STATE/workflow-args" for argument in "$@"; do case "$argument" in governance_preflight_nonce=*) printf 'Release governance preflight %s\n' "${argument#governance_preflight_nonce=}" > "$GH_STATE/title" ;; governance_preflight_commit=*) printf '%s\n' "${argument#governance_preflight_commit=}" > "$GH_STATE/commit" ;; esac done ;; api) for argument in "$@"; do case "$argument" in repos/*/actions/*) endpoint="$argument" ;; esac done case "$endpoint" in */actions/workflows/release.yml/runs*) printf '42\n' ;; */actions/runs/42) title="$(cat "$GH_STATE/title")" commit="$(cat "$GH_STATE/commit")" repository=owner/repo if [ "${GH_BAD_STATE:-0}" = 1 ]; then repository=attacker/repo; fi printf '%s\tworkflow_dispatch\tcompleted\tsuccess\tmain\t%s\t.github/workflows/release.yml\t%s\n' "$title" "$commit" "$repository" ;; *) exit 1 ;; esac ;; run) test "$1" = watch ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-release-governance-ci.sh") commit := strings.Repeat("a", 40) run := func(badState bool) (string, error) { cmd := exec.Command("sh", script, "owner/repo", commit) cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "GH_STATE=" + stateDir, fmt.Sprintf("GH_BAD_STATE=%d", map[bool]int{false: 0, true: 1}[badState]), } output, err := cmd.CombinedOutput() return string(output), err } output, err := run(false) if err != nil || !strings.Contains(output, "Release governance preflight passed") { t.Fatalf("governance CI preflight error = %v\noutput:\n%s", err, output) } args, err := os.ReadFile(filepath.Join(stateDir, "workflow-args")) if err != nil { t.Fatalf("ReadFile(workflow args) error = %v", err) } for _, required := range []string{ "--ref\nmain\n", "governance_preflight_commit=" + commit, "governance_preflight_nonce=" + commit + "-", } { if !strings.Contains(string(args), required) { t.Errorf("workflow dispatch args are missing %q:\n%s", required, args) } } output, err = run(true) if err == nil || !strings.Contains(output, "run identity mismatch") { t.Fatalf("mismatched governance run identity passed: err=%v\noutput:\n%s", err, output) } } func TestVerifyHomebrewPRTokenRequiresMinimalWorkingIdentity(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } binDir := t.TempDir() fakeGH := filepath.Join(binDir, "gh") mustWriteFile(t, fakeGH, []byte(`#!/bin/sh set -eu test "${GH_TOKEN:-}" = valid-token command="$1" shift test "$command" = api endpoint="" for argument in "$@"; do case "$argument" in repos/*|user) endpoint="$argument" ;; esac done case "$endpoint" in repos/owner/repo) printf 'HTTP/2.0 200 OK\nX-OAuth-Scopes: %s\n\nowner/repo\t%s\tmain\n' \ "${GH_SCOPES-public_repo}" "${GH_CAN_PUSH:-true}" ;; repos/owner/repo/pulls?state=open\&per_page=1) ;; user) printf '%s\n' release-bot ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-homebrew-pr-token.sh") run := func(token, scopes, canPush string) (string, error) { cmd := exec.Command("sh", script, "owner/repo") cmd.Env = []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "HOMEBREW_PR_TOKEN=" + token, "GH_SCOPES=" + scopes, "GH_CAN_PUSH=" + canPush, } output, err := cmd.CombinedOutput() return string(output), err } output, err := run("valid-token", "public_repo", "true") if err != nil || !strings.Contains(output, "Homebrew PR token capability passed for owner/repo as release-bot (classic-public_repo)") { t.Fatalf("valid Homebrew PR token rejected: err=%v\noutput:\n%s", err, output) } output, err = run("valid-token", "repo", "true") if err == nil || !strings.Contains(output, "must have only public_repo scope") { t.Fatalf("overprivileged Homebrew PR token accepted: err=%v\noutput:\n%s", err, output) } output, err = run("valid-token", "", "true") if err != nil || !strings.Contains(output, "(fine-grained)") { t.Fatalf("fine-grained Homebrew PR token rejected: err=%v\noutput:\n%s", err, output) } output, err = run("valid-token", "public_repo", "false") if err == nil || !strings.Contains(output, "does not have push permission") { t.Fatalf("non-pushing Homebrew identity accepted: err=%v\noutput:\n%s", err, output) } output, err = run("", "public_repo", "true") if err == nil || !strings.Contains(output, "HOMEBREW_PR_TOKEN is required") { t.Fatalf("missing Homebrew token accepted: err=%v\noutput:\n%s", err, output) } } func TestVerifyHomebrewPRTokenCanaryCreatesAndCleans(t *testing.T) { sourceRoot, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("Abs(repo root) error = %v", err) } r := newReleaseTestRepo(t) binDir := t.TempDir() stateDir := t.TempDir() realGit, err := exec.LookPath("git") if err != nil { t.Fatalf("LookPath(git) error = %v", err) } fakeGit := filepath.Join(binDir, "git") mustWriteFile(t, fakeGit, []byte(`#!/bin/sh set -eu network=false for argument in "$@"; do case "$argument" in clone|push|ls-remote) network=true ;; esac done if [ "$network" = true ]; then printf '%s\n' "$*" >> "$GH_STATE/git-network-args" case " $* " in *" https://github.com/owner/repo.git "*) ;; *) exit 91 ;; esac test "${GIT_CONFIG_NOSYSTEM:-}" = 1 test "${GIT_CONFIG_GLOBAL:-}" = /dev/null test "${GIT_CONFIG_COUNT:-}" = 0 test -x "${GIT_ASKPASS:-}" test "$("$GIT_ASKPASS" Username)" = x-access-token test "$("$GIT_ASKPASS" Password)" = valid-token case " $* " in *" HEAD:refs/heads/"*) if [ "${BLOCK_PUSH:-false}" = true ]; then "$REAL_GIT" \ -c "url.$TEST_REMOTE.insteadOf=https://github.com/owner/repo.git" \ "$@" : > "$GH_STATE/push-complete-${GITHUB_RUN_ID:-unknown}" sleep 2 exit 0 fi ;; esac case " $* " in *" :refs/heads/"*) if [ "${FAIL_DELETE:-false}" = true ]; then exit 92 fi ;; esac exec "$REAL_GIT" \ -c "url.$TEST_REMOTE.insteadOf=https://github.com/owner/repo.git" \ "$@" fi exec "$REAL_GIT" "$@" `), 0o755) fakeGH := filepath.Join(binDir, "gh") mustWriteFile(t, fakeGH, []byte(`#!/bin/sh set -eu test "${GH_TOKEN:-}" = valid-token command="$1" shift case "$command" in api) endpoint="" for argument in "$@"; do case "$argument" in repos/*|user) endpoint="$argument" ;; esac done case "$endpoint" in repos/owner/repo) printf 'HTTP/2.0 200 OK\nX-OAuth-Scopes: public_repo\n\nowner/repo\ttrue\tmain\n' ;; repos/owner/repo/pulls?state=open\&per_page=1) ;; repos/owner/repo/pulls?state=open\&head=owner:*\&base=main\&per_page=2) if [ -f "$GH_STATE/pr-open-${GITHUB_RUN_ID:-unknown}" ]; then printf '%s\n' 42 fi ;; repos/owner/repo/pulls/42) if [ "${FAIL_CLOSE:-false}" = true ]; then exit 93 fi rm -f "$GH_STATE/pr-open-${GITHUB_RUN_ID:-unknown}" printf '%s\n' closed > "$GH_STATE/pr-closed-${GITHUB_RUN_ID:-unknown}" ;; user) printf '%s\n' release-bot ;; *) exit 1 ;; esac ;; pr) test "$1" = create shift printf '%s\n' "$@" > "$GH_STATE/pr-args" : > "$GH_STATE/pr-create-started-${GITHUB_RUN_ID:-unknown}" if [ "${FAIL_PR_CREATE:-false}" = true ]; then exit 94 fi : > "$GH_STATE/pr-open-${GITHUB_RUN_ID:-unknown}" if [ "${BLOCK_PR_CREATE:-false}" = true ]; then sleep 2 fi printf '%s\n' 'https://github.com/owner/repo/pull/42' ;; *) exit 1 ;; esac `), 0o755) script := filepath.Join(sourceRoot, "scripts", "release", "verify-homebrew-pr-token.sh") baseEnv := []string{ "PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"), "HOME=" + t.TempDir(), "GH_STATE=" + stateDir, "HOMEBREW_PR_TOKEN=valid-token", "GITHUB_RUN_ATTEMPT=2", "REAL_GIT=" + realGit, "TEST_REMOTE=" + r.remote, } runCanary := func(runID string, extraEnv ...string) ([]byte, error) { cmd := exec.Command("sh", script, "owner/repo", "--canary") cmd.Dir = r.root cmd.Env = append(append([]string{}, baseEnv...), "GITHUB_RUN_ID="+runID) cmd.Env = append(cmd.Env, extraEnv...) return cmd.CombinedOutput() } remoteHasBranch := func(branch string) bool { cmd := exec.Command( "git", "--git-dir", r.remote, "show-ref", "--verify", "refs/heads/"+branch, ) return cmd.Run() == nil } output, err := runCanary("12345") if err != nil || !strings.Contains(string(output), "Homebrew PR token capability passed") { t.Fatalf("Homebrew token canary error = %v\noutput:\n%s", err, output) } prArgs, err := os.ReadFile(filepath.Join(stateDir, "pr-args")) if err != nil { t.Fatalf("ReadFile(PR args) error = %v", err) } for _, required := range []string{ "--draft", "--base\nmain", "--head\nautomation/homebrew-token-canary-12345-2", } { if !strings.Contains(string(prArgs), required) { t.Errorf("Homebrew token canary PR args are missing %q:\n%s", required, prArgs) } } if _, err := os.Stat(filepath.Join(stateDir, "pr-closed-12345")); err != nil { t.Fatalf("Homebrew token canary did not close its PR: %v", err) } gitNetworkArgs, err := os.ReadFile(filepath.Join(stateDir, "git-network-args")) if err != nil { t.Fatalf("ReadFile(git network args) error = %v", err) } for _, required := range []string{ "clone", "https://github.com/owner/repo.git", "HEAD:refs/heads/automation/homebrew-token-canary-12345-2", "--force-with-lease=refs/heads/automation/homebrew-token-canary-12345-2:", ":refs/heads/automation/homebrew-token-canary-12345-2", } { if !strings.Contains(string(gitNetworkArgs), required) { t.Errorf("Homebrew token canary Git operations are missing %q:\n%s", required, gitNetworkArgs) } } if remoteHasBranch("automation/homebrew-token-canary-12345-2") { t.Fatal("Homebrew token canary left its remote branch behind") } output, err = runCanary("20001", "FAIL_PR_CREATE=true") if err == nil || !strings.Contains(string(output), "cannot create a canary pull request") { t.Fatalf("PR creation failure was not fail-closed: err=%v\noutput:\n%s", err, output) } if remoteHasBranch("automation/homebrew-token-canary-20001-2") { t.Fatal("PR creation failure left its canary branch behind") } output, err = runCanary("20002", "FAIL_CLOSE=true") if err == nil || !strings.Contains(string(output), "could not close canary PR") || !strings.Contains(string(output), "could not clean up Homebrew token canary PR") { t.Fatalf("PR cleanup failure was not fail-closed: err=%v\noutput:\n%s", err, output) } if remoteHasBranch("automation/homebrew-token-canary-20002-2") { t.Fatal("PR cleanup failure left its canary branch behind") } if _, err := os.Stat(filepath.Join(stateDir, "pr-open-20002")); err != nil { t.Fatalf("PR cleanup failure did not leave an observable open canary PR: %v", err) } output, err = runCanary("20003", "FAIL_DELETE=true") if err == nil || !strings.Contains(string(output), "could not delete canary branch") || !strings.Contains(string(output), "could not clean up Homebrew token canary branch") { t.Fatalf("branch cleanup failure was not fail-closed: err=%v\noutput:\n%s", err, output) } if !remoteHasBranch("automation/homebrew-token-canary-20003-2") { t.Fatal("branch deletion failure was hidden instead of leaving an observable failed canary") } termCmd := exec.Command("sh", script, "owner/repo", "--canary") termCmd.Dir = r.root termCmd.Env = append(append([]string{}, baseEnv...), "GITHUB_RUN_ID=20004", "BLOCK_PR_CREATE=true", ) var termStdout bytes.Buffer var termStderr bytes.Buffer termCmd.Stdout = &termStdout termCmd.Stderr = &termStderr if err := termCmd.Start(); err != nil { t.Fatalf("Start(TERM canary) error = %v", err) } marker := filepath.Join(stateDir, "pr-create-started-20004") deadline := time.Now().Add(5 * time.Second) for { if _, err := os.Stat(marker); err == nil { break } if time.Now().After(deadline) { _ = termCmd.Process.Kill() _ = termCmd.Wait() t.Fatalf("TERM canary did not reach PR creation:\n%s%s", termStdout.String(), termStderr.String()) } time.Sleep(20 * time.Millisecond) } if err := termCmd.Process.Signal(syscall.SIGTERM); err != nil { t.Fatalf("Signal(TERM canary) error = %v", err) } err = termCmd.Wait() exitErr, ok := err.(*exec.ExitError) if !ok || exitErr.ExitCode() != 143 { t.Fatalf("TERM canary exit = %v, want 143\nstdout:\n%s\nstderr:\n%s", err, termStdout.String(), termStderr.String()) } if remoteHasBranch("automation/homebrew-token-canary-20004-2") { t.Fatal("TERM canary left its remote branch behind") } if _, err := os.Stat(filepath.Join(stateDir, "pr-closed-20004")); err != nil { t.Fatalf("TERM canary did not close the PR created during the signal race: %v", err) } if _, err := os.Stat(filepath.Join(stateDir, "pr-open-20004")); !os.IsNotExist(err) { t.Fatalf("TERM canary left an open PR marker: %v", err) } pushTermCmd := exec.Command("sh", script, "owner/repo", "--canary") pushTermCmd.Dir = r.root pushTermCmd.Env = append(append([]string{}, baseEnv...), "GITHUB_RUN_ID=20005", "BLOCK_PUSH=true", ) var pushTermStdout bytes.Buffer var pushTermStderr bytes.Buffer pushTermCmd.Stdout = &pushTermStdout pushTermCmd.Stderr = &pushTermStderr if err := pushTermCmd.Start(); err != nil { t.Fatalf("Start(push TERM canary) error = %v", err) } pushMarker := filepath.Join(stateDir, "push-complete-20005") deadline = time.Now().Add(5 * time.Second) for { if _, err := os.Stat(pushMarker); err == nil { break } if time.Now().After(deadline) { _ = pushTermCmd.Process.Kill() _ = pushTermCmd.Wait() t.Fatalf("push TERM canary did not complete its remote push:\n%s%s", pushTermStdout.String(), pushTermStderr.String()) } time.Sleep(20 * time.Millisecond) } if err := pushTermCmd.Process.Signal(syscall.SIGTERM); err != nil { t.Fatalf("Signal(push TERM canary) error = %v", err) } err = pushTermCmd.Wait() exitErr, ok = err.(*exec.ExitError) if !ok || exitErr.ExitCode() != 143 { t.Fatalf("push TERM canary exit = %v, want 143\nstdout:\n%s\nstderr:\n%s", err, pushTermStdout.String(), pushTermStderr.String()) } if remoteHasBranch("automation/homebrew-token-canary-20005-2") { t.Fatal("push TERM canary left its remote branch behind") } } func TestReleaseCommandCleansLocalTagWhenPushFails(t *testing.T) { r := newReleaseTestRepo(t) installReleaseCommandFixture(t, r) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "install release automation") hook := filepath.Join(r.remote, "hooks", "pre-receive") mustWriteFile(t, hook, []byte("#!/bin/sh\nset -eu\nwhile read -r old new ref; do\n case \"$ref\" in refs/tags/*) exit 1 ;; esac\ndone\nexit 0\n"), 0o755) output, err := runReleaseScript(t, r.root, filepath.Join(r.root, "scripts", "release", "release.sh"), "prerelease", "v1.0.1-beta.1", "--remote", "origin", "--publish", "--yes", ) if err == nil { t.Fatalf("rejected tag push unexpectedly passed:\n%s", output) } if !strings.Contains(output, "new local tag was removed") { t.Fatalf("push failure output missing cleanup result:\n%s", output) } if mustOutput(t, r.root, "git", "tag", "--list", "v1.0.1-beta.1") != "" { t.Fatal("failed push left the local release tag behind") } } func TestReleaseCommandRejectsDifferentFetchAndPushRepositories(t *testing.T) { r := newReleaseTestRepo(t) installReleaseCommandFixture(t, r) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "install release automation") otherRemote := filepath.Join(t.TempDir(), "other.git") mustRun(t, r.root, "git", "init", "--bare", otherRemote) mustRun(t, r.root, "git", "remote", "set-url", "--push", "origin", otherRemote) output, err := runReleaseScript(t, r.root, filepath.Join(r.root, "scripts", "release", "release.sh"), "prerelease", "v1.0.1-beta.1", "--remote", "origin", ) if err == nil || !strings.Contains(output, "fetch and push URLs target different repositories") { t.Fatalf("split release authority was not rejected: err=%v\noutput:\n%s", err, output) } } func TestReleaseCommandRunsCLIAndSchemaCompatibilityInInitialPreflight(t *testing.T) { r := newReleaseTestRepo(t) installReleaseCommandFixture(t, r) mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(betaSection())), 0o644) r.commitAndPush(t, "install release compatibility fixture") output, err := runReleaseScript(t, r.root, filepath.Join(r.root, "scripts", "release", "release.sh"), "prerelease", "v1.0.1-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("release validation error = %v\noutput:\n%s", err, output) } head := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "HEAD^{commit}")) stable := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "v1.0.0^{commit}")) for _, want := range []string{ fmt.Sprintf("cli-compatibility --base-ref %s --stable-ref %s --candidate-ref %s", head, stable, head), fmt.Sprintf("schema-compatibility --base-ref %s --stable-ref %s --candidate-ref %s", head, stable, head), } { if !strings.Contains(output, want) { t.Errorf("initial release preflight is missing %q\noutput:\n%s", want, output) } } } func TestReleaseCommandRechecksAdvancedStableAuthority(t *testing.T) { r := newReleaseTestRepo(t) installReleaseCommandFixture(t, r) section := "## [1.0.2-beta.1] - 2026-07-11\n\n### Changed\n\n- Validate a candidate after stable authority advances.\n\n" mustWriteFile(t, filepath.Join(r.root, "CHANGELOG.md"), []byte(releaseChangelog(section)), 0o644) mustWriteFile(t, filepath.Join(r.root, "Makefile"), []byte("test:\n\t@:\nbuild:\n\t@:\npolicy:\n\t@:\npackage:\n\t@git tag -a v1.0.1 -m 'Release v1.0.1'\n\t@git push origin refs/tags/v1.0.1\n"), 0o644) r.commitAndPush(t, "install advancing release fixture") output, err := runReleaseScript(t, r.root, filepath.Join(r.root, "scripts", "release", "release.sh"), "prerelease", "v1.0.2-beta.1", "--remote", "origin", ) if err != nil { t.Fatalf("release validation error = %v\noutput:\n%s", err, output) } head := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "HEAD^{commit}")) stable := strings.TrimSpace(mustOutput(t, r.root, "git", "rev-parse", "v1.0.1^{commit}")) for _, want := range []string{ "Stable authority advanced from v1.0.0 to v1.0.1", fmt.Sprintf("cli-compatibility --base-ref %s --stable-ref %s --candidate-ref %s", head, stable, head), fmt.Sprintf("schema-compatibility --base-ref %s --stable-ref %s --candidate-ref %s", head, stable, head), } { if !strings.Contains(output, want) { t.Errorf("advanced stable authority recheck is missing %q\noutput:\n%s", want, output) } } } func installReleaseCommandFixture(t *testing.T, r *releaseTestRepo) { t.Helper() for _, source := range []string{r.lib, r.contract, r.releaseCmd, r.compat} { releaseCopyFile(t, source, filepath.Join(r.root, "scripts", "release", filepath.Base(source)), 0o755) } mustWriteFile(t, filepath.Join(r.root, "scripts", "release", "verify-package-managers.sh"), []byte("#!/bin/sh\nset -eu\nexit 0\n"), 0o755) mustWriteFile(t, filepath.Join(r.root, "scripts", "release", "verify-release-artifacts.sh"), []byte("#!/bin/sh\nset -eu\nexit 0\n"), 0o755) mustWriteFile(t, filepath.Join(r.root, "scripts", "policy", "check-authoritative-interface-baselines.sh"), []byte("#!/bin/sh\nset -eu\nprintf 'cli-compatibility %s\\n' \"$*\"\n"), 0o755) mustWriteFile(t, filepath.Join(r.root, "scripts", "policy", "check-authoritative-schema-compatibility.sh"), []byte("#!/bin/sh\nset -eu\nprintf 'schema-compatibility %s\\n' \"$*\"\n"), 0o755) mustWriteFile(t, filepath.Join(r.root, "Makefile"), []byte("test:\n\t@:\nbuild:\n\t@:\npolicy:\n\t@:\npackage:\n\t@:\n"), 0o644) } func releaseCopyFile(t *testing.T, source, target string, mode os.FileMode) { t.Helper() data, err := os.ReadFile(source) if err != nil { t.Fatalf("ReadFile(%s) error = %v", source, err) } mustWriteFile(t, target, data, mode) }