Merge remote-tracking branch 'origin/main' into codex/todo-skill-optimization-run1

This commit is contained in:
瑞达 committed 2026-08-25 19:16:14 +08:00
commit d3efec96ce
273 files changed
+34034 -3145

No files matched your search

+76
View File
@@ -351,6 +351,79 @@ This release promotes the sealed `+"`v1.0.1-beta.1`"+` contents to stable.
}
}
func TestReleaseFragmentPolicyAcceptsOnlyUntaggedCanonicalBetaAmendments(t *testing.T) {
newAmendmentRepo := func(t *testing.T) (*changelogGateRepo, string) {
t.Helper()
repo := newChangelogGateRepo(t)
sealBase := repo.sealFragmentInto(t, "1.0.1-beta.1")
if output, err := repo.runFragmentPolicy(t, sealBase, "HEAD"); err != nil {
t.Fatalf("release fragment policy rejected initial beta seal: %v\noutput:\n%s", err, output)
}
changelogGateWrite(t, repo.root, ".changes/1235-sheet.md", "---\ncategory: Added\n---\n\n- Sheet accepts local float image files.\n", 0o644)
repo.commit(t, "merge post-seal beta fragment")
return repo, strings.TrimSpace(changelogGateGit(t, repo.root, "rev-parse", "HEAD"))
}
stageCanonicalAmendment := func(t *testing.T, repo *changelogGateRepo) {
t.Helper()
changelogGateWrite(t, repo.root, "CHANGELOG.md", `# Changelog
## [Unreleased]
## [1.0.1-beta.1] - 2026-07-17
### Added
- Chat reply mentions.
- Sheet accepts local float image files.
## [1.0.0] - 2026-07-01
### Added
- Initial release.
`, 0o644)
archiveDir := filepath.Join(repo.root, ".changes", "released", "1.0.1-beta.1")
if err := os.Rename(filepath.Join(repo.root, ".changes", "1235-sheet.md"), filepath.Join(archiveDir, "1235-sheet.md")); err != nil {
t.Fatalf("Rename beta amendment fragment: %v", err)
}
repo.commit(t, "amend untagged beta release notes")
}
t.Run("accepts exact pre-tag merge", func(t *testing.T) {
repo, amendmentBase := newAmendmentRepo(t)
stageCanonicalAmendment(t, repo)
if output, err := repo.runFragmentPolicy(t, amendmentBase, "HEAD"); err != nil {
t.Fatalf("release fragment policy rejected canonical beta amendment: %v\noutput:\n%s", err, output)
}
})
t.Run("rejects tagged beta", func(t *testing.T) {
repo, amendmentBase := newAmendmentRepo(t)
stageCanonicalAmendment(t, repo)
changelogGateGit(t, repo.root, "tag", "v1.0.1-beta.1")
output, err := repo.runFragmentPolicy(t, amendmentBase, "HEAD")
if err == nil || !strings.Contains(output, "forbidden after tag v1.0.1-beta.1 exists") {
t.Fatalf("tagged beta amendment passed: err=%v\noutput:\n%s", err, output)
}
})
t.Run("rejects rewritten sealed prose", func(t *testing.T) {
repo, amendmentBase := newAmendmentRepo(t)
stageCanonicalAmendment(t, repo)
changelogGateWrite(t, repo.root, "CHANGELOG.md", strings.Replace(changelogGateSealedRelease, "Chat reply mentions.", "Rewritten sealed note.", 1), 0o644)
repo.commit(t, "rewrite sealed beta prose")
output, err := repo.runFragmentPolicy(t, amendmentBase, "HEAD")
if err == nil || !strings.Contains(output, "does not exactly match") {
t.Fatalf("rewritten beta amendment passed: err=%v\noutput:\n%s", err, output)
}
})
}
func TestReleaseFragmentPolicyRejectsInvalidActiveFragmentAndWrongArchiveVersion(t *testing.T) {
t.Run("invalid active fragment", func(t *testing.T) {
repo := newChangelogGateRepo(t)
@@ -1106,6 +1179,9 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
t.Fatal("Code Admission workflow missing Policy job boundaries")
}
policyJob := admission[policyStart:policyEnd]
if !strings.Contains(policyJob, "timeout-minutes: 15") {
t.Error("Policy job must retain enough headroom for full Schema policy validation")
}
requirePolicyEnv := func(step, nextStep string) {
t.Helper()
start := strings.Index(policyJob, " - name: "+step+"\n")
@@ -0,0 +1,145 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package scripts_test
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"gopkg.in/yaml.v3"
)
const stableMergedPRIdentityHelper = `function isStableMergedPRIdentity(
currentPull,
pullNumber,
headSha,
mergeCommitSha,
) {
return (
currentPull?.number === pullNumber &&
currentPull.state === 'closed' &&
currentPull.merged === true &&
typeof currentPull.merged_at === 'string' &&
currentPull.merged_at.length > 0 &&
currentPull.base?.ref === 'main' &&
currentPull.head?.sha === headSha &&
currentPull.merge_commit_sha === mergeCommitSha
);
}`
func TestCoverageBaselineRepairUsesStableMergedPRIdentity(t *testing.T) {
t.Parallel()
node, err := exec.LookPath("node")
if err != nil {
t.Skip("node is required to verify merged-PR repair identity semantics")
}
root, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("resolve repository root: %v", err)
}
workflowPath := filepath.Join(
root,
".github",
"workflows",
"coverage-baseline-repair.yml",
)
data, err := os.ReadFile(workflowPath)
if err != nil {
t.Fatalf("read %s: %v", workflowPath, err)
}
workflow := string(data)
var parsedWorkflow any
if err := yaml.Unmarshal(data, &parsedWorkflow); err != nil {
t.Fatalf("parse %s: %v", workflowPath, err)
}
var scripts []string
collectGitHubScripts(parsedWorkflow, &scripts)
helperCount := 0
checkCount := 0
for _, script := range scripts {
helperCount += strings.Count(script, stableMergedPRIdentityHelper)
checkCount += strings.Count(script, "!isStableMergedPRIdentity(")
}
if helperCount != 2 {
t.Fatalf("stable merged-PR identity helper occurrences = %d, want dispatcher and producer", helperCount)
}
if checkCount != 2 {
t.Fatalf("stable merged-PR identity checks = %d, want dispatcher and producer", checkCount)
}
for _, forbidden := range []string{
"currentPull.base.sha",
"const baseSha =",
"base_sha",
} {
if strings.Contains(workflow, forbidden) {
t.Errorf("merged-PR repair must not bind mutable Ref SHA via %q", forbidden)
}
}
for _, want := range []string{
"const baseRef = eventPull?.base?.ref;",
"baseRef !== 'main'",
"const headSha = eventPull?.head?.sha;",
"const headSha = payload.head_sha;",
"head_sha: headSha",
"currentPull.head?.sha === headSha",
"currentPull.merge_commit_sha === mergeCommitSha",
"await requireMainContainment(mergeCommitSha)",
"await requireMainContainment(targetSha)",
} {
if !strings.Contains(workflow, want) {
t.Errorf("merged-PR repair missing stable identity guard %q", want)
}
}
verification := stableMergedPRIdentityHelper + `
const pullNumber = 1077;
const headSha = '1'.repeat(40);
const mergeCommitSha = '2'.repeat(40);
const validPull = {
number: pullNumber,
state: 'closed',
merged: true,
merged_at: '2026-08-25T04:55:30Z',
base: {ref: 'main', sha: '3'.repeat(40)},
head: {sha: headSha},
merge_commit_sha: mergeCommitSha,
};
const cases = [
['base ref advanced after event', {...validPull, base: {...validPull.base, sha: '4'.repeat(40)}}, true],
['base sha omitted by projection', {...validPull, base: {ref: 'main'}}, true],
['wrong pull number', {...validPull, number: pullNumber + 1}, false],
['open pull request', {...validPull, state: 'open'}, false],
['not merged', {...validPull, merged: false}, false],
['missing merged at', {...validPull, merged_at: null}, false],
['empty merged at', {...validPull, merged_at: ''}, false],
['non-string merged at', {...validPull, merged_at: 1}, false],
['wrong base ref', {...validPull, base: {...validPull.base, ref: 'release'}}, false],
['missing base ref', {...validPull, base: {}}, false],
['missing head', {...validPull, head: null}, false],
['wrong head sha', {...validPull, head: {sha: '5'.repeat(40)}}, false],
['wrong merge sha', {...validPull, merge_commit_sha: '6'.repeat(40)}, false],
['null pull', null, false],
];
for (const [name, currentPull, want] of cases) {
const got = isStableMergedPRIdentity(
currentPull,
pullNumber,
headSha,
mergeCommitSha,
);
if (got !== want) {
throw new Error(name + ': got ' + got + ', want ' + want);
}
}
`
command := exec.Command(node, "-e", verification)
if output, runErr := command.CombinedOutput(); runErr != nil {
t.Fatalf("stable merged-PR identity verification failed: %v\n%s", runErr, output)
}
}
+521 -4
View File
@@ -135,15 +135,32 @@ func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) {
t.Fatalf("ReadFile(ci.yml) error = %v", err)
}
admission := string(data)
for _, want := range []string{
"group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && format('pr-{0}-{1}-{2}', github.event.pull_request.number, github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('push-{0}', github.sha) }}",
"cancel-in-progress: true",
} {
if !strings.Contains(admission, want) {
t.Errorf("CI workflow missing exact-revision producer contract %q", want)
}
}
for _, forbidden := range []string{
"github.event.pull_request.number || github.ref",
"github.event.pull_request.number || github.sha",
} {
if strings.Contains(admission, forbidden) {
t.Errorf("CI producers must not share the coarse concurrency identity %q", forbidden)
}
}
currentStart := strings.Index(admission, "\n coverage-current:\n")
fullStart := strings.Index(admission, "\n coverage-current-full:\n")
supportingStart := strings.Index(admission, "\n coverage-supporting:\n")
baselineStart := strings.Index(admission, "\n coverage-baseline:\n")
metadataStart := strings.Index(admission, "\n coverage-main-metadata:\n")
gateStart := strings.Index(admission, "\n coverage:\n")
policyStart := strings.Index(admission, "\n policy:\n")
if currentStart < 0 || fullStart <= currentStart || supportingStart <= fullStart ||
baselineStart <= supportingStart || gateStart <= baselineStart || policyStart <= gateStart {
baselineStart <= supportingStart || metadataStart <= baselineStart || gateStart <= metadataStart || policyStart <= gateStart {
t.Fatal("CI workflow missing ordered coverage job boundaries")
}
@@ -175,7 +192,10 @@ func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) {
}
}
baselineJob := admission[baselineStart:gateStart]
baselineJob := admission[baselineStart:metadataStart]
if !strings.Contains(baselineJob, "timeout-minutes: 30") {
t.Error("coverage-baseline must retain enough headroom for an authoritative cold-cache fallback")
}
cachePath := "coverage-cache.txt"
baselineKey := "dws-coverage-full-v2-${{ env.COVERAGE_BASE_REF }}-go${{ steps.setup-go.outputs.go-version }}"
for _, want := range []string{
@@ -201,8 +221,50 @@ func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) {
t.Error("coverage baseline cache must stay exact-key; prefix restore-keys can resurrect a wrong-commit baseline")
}
metadataJob := admission[metadataStart:gateStart]
for _, want := range []string{
"github.event_name == 'push'",
"needs.lint.outputs.changelog_only == 'true' || needs.lint.outputs.docs_only == 'true'",
"timeout-minutes: 30",
"fetch-depth: 0",
"PUSH_BEFORE_SHA: ${{ github.event.before }}",
"PUSH_AFTER_SHA: ${{ github.event.after }}",
"git merge-base --is-ancestor \"$PUSH_BEFORE_SHA\" \"$PUSH_AFTER_SHA\"",
"git diff --name-only --no-renames -z",
`^\.changes/[a-z0-9][a-z0-9._-]*\.md$`,
`^\.changes/released/[0-9]+\.[0-9]+\.[0-9]+(-beta\.[1-9][0-9]*)?/[a-z0-9][a-z0-9._-]*\.md$`,
"Refusing coverage-cache promotion for unreviewed change-fragment path",
"Refusing coverage-cache promotion for executable path",
"COVERAGE_SOURCE_REF=$PUSH_BEFORE_SHA",
"id: metadata-current-cache",
"id: metadata-source-cache",
"key: dws-coverage-full-v2-${{ env.COVERAGE_SOURCE_REF }}-go${{ steps.setup-go-metadata.outputs.go-version }}",
"go test -count=1 -p 1",
"./ ./cmd/... ./internal/... ./skills/...",
"key: dws-coverage-full-v2-${{ github.sha }}-go${{ steps.setup-go-metadata.outputs.go-version }}",
"id: metadata-target-cache-verification",
"lookup-only: true",
"fail-on-cache-miss: true",
"EXACT_CACHE_HIT: ${{ steps.metadata-target-cache-verification.outputs.cache-hit }}",
`run: test "$EXACT_CACHE_HIT" = true`,
} {
if !strings.Contains(metadataJob, want) {
t.Errorf("metadata-only main cache producer missing contract %q", want)
}
}
if strings.Contains(metadataJob, "restore-keys") {
t.Error("metadata-only main cache promotion must use exact source and target keys")
}
if strings.Count(metadataJob, "path: "+cachePath) != 4 {
t.Error("metadata-only producer must restore target/source, save, and verify through the shared cache version path")
}
gateJob := admission[gateStart:policyStart]
for _, want := range []string{
"- coverage-main-metadata",
"MAIN_METADATA_RESULT: ${{ needs.coverage-main-metadata.result }}",
"main_metadata_expected=success",
`"main metadata cache:$MAIN_METADATA_RESULT:$main_metadata_expected"`,
"pattern: coverage-current-*",
"merge-multiple: true",
"for shard in app cli generators helpers remaining; do",
@@ -212,6 +274,12 @@ func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) {
"cp coverage.txt coverage-cache.txt",
"path: " + cachePath,
"key: dws-coverage-full-v2-${{ github.sha }}-go${{ steps.setup-go.outputs.go-version }}",
"name: Verify push coverage cache exists",
"id: push-cache-verification",
"lookup-only: true",
"fail-on-cache-miss: true",
"EXACT_CACHE_HIT: ${{ steps.push-cache-verification.outputs.cache-hit }}",
`run: test "$EXACT_CACHE_HIT" = true`,
`"current shards:$CURRENT_FULL_RESULT:$current_full_expected"`,
} {
if !strings.Contains(gateJob, want) {
@@ -219,7 +287,456 @@ func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) {
}
}
if strings.Count(gateJob, "path: "+cachePath) != 1 {
t.Error("green main push must save the candidate profile through the same cache path/version as baseline restore")
if strings.Count(gateJob, "path: "+cachePath) != 2 {
t.Error("green main push must save and verify the candidate profile through the same cache path/version as baseline restore")
}
}
func TestFormulaCoverageBaselinePromotionContract(t *testing.T) {
root, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("Abs(repo root) error = %v", err)
}
promotionData, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "coverage-baseline-promotion.yml"))
if err != nil {
t.Fatalf("ReadFile(coverage-baseline-promotion.yml) error = %v", err)
}
promotion := string(promotionData)
for _, want := range []string{
"repository_dispatch:",
"types: [coverage-baseline-promote]",
"checks: write",
"contents: read",
"group: coverage-baseline-promotion-${{ github.event.client_payload.target_sha }}",
"cancel-in-progress: false",
"queue: max",
"github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli'",
"timeout-minutes: 30",
"context.payload.client_payload?.target_sha",
"context.payload.client_payload?.source_run_id",
"context.payload.client_payload?.check_run_id",
"targetCommit.parents.length !== 1",
"targetCommit.author?.login !== 'github-actions[bot]'",
"targetCommit.committer?.login !== 'github-actions[bot]'",
"files.length !== 1",
"Formula/dingtalk-workspace-cli.rb",
"Formula/dingtalk-workspace-cli-beta.rb",
"[skip ci",
"await requireSuccessfulAdmission(parentSha, 'Formula parent')",
"await requireSuccessfulAdmission(targetSha, 'Formula target')",
"for (let attempt = 1; attempt <= 6; attempt += 1)",
"setTimeout(resolve, 5000)",
"run.app?.slug !== 'github-actions'",
"run.conclusion !== 'success'",
"basehead: `${targetSha}...${branch.commit.sha}`",
"['ahead', 'identical'].includes(containment.status)",
"promotionCheck.name !== 'Coverage Baseline Cache'",
"promotionCheck.external_id !== promotionExternalId",
"promotionCheck.status !== 'queued'",
"core.setOutput('check_run_id', String(checkRunId))",
"name: Mark Formula cache promotion in progress",
"status: 'in_progress'",
"persist-credentials: false",
"ref: ${{ steps.validate-target.outputs.target_sha }}",
"test \"$(git rev-parse HEAD^)\" = \"$PARENT_SHA\"",
"id: target-cache",
"id: parent-cache",
"key: dws-coverage-full-v2-${{ steps.validate-target.outputs.parent_sha }}-go${{ steps.setup-go.outputs.go-version }}",
"go test -count=1 -p 1",
"./ ./cmd/... ./internal/... ./skills/...",
"key: dws-coverage-full-v2-${{ steps.validate-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}",
"lookup-only: true",
"fail-on-cache-miss: true",
"id: formula-target-cache-verification",
"EXACT_CACHE_HIT: ${{ steps.formula-target-cache-verification.outputs.cache-hit }}",
`run: test "$EXACT_CACHE_HIT" = true`,
"name: Complete Formula cache promotion acknowledgement",
"PROMOTION_JOB_STATUS: ${{ job.status }}",
"conclusion: succeeded ? 'success' : 'failure'",
} {
if !strings.Contains(promotion, want) {
t.Errorf("Formula baseline promotion missing contract %q", want)
}
}
for _, want := range []string{
"actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683",
"actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff",
"actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830",
} {
if !strings.Contains(promotion, want) {
t.Errorf("Formula baseline promotion must pin trusted action %q", want)
}
}
for _, forbidden := range []string{
"pull_request:",
"pull_request_target:",
"push:",
"workflow_dispatch:",
"restore-keys",
"HOMEBREW_PR_TOKEN",
"RELEASE_GOVERNANCE_TOKEN",
"contents: write",
} {
if strings.Contains(promotion, forbidden) {
t.Errorf("Formula baseline promotion must not contain %q", forbidden)
}
}
validateIndex := strings.Index(promotion, "name: Validate Formula-only main target")
checkoutIndex := strings.Index(promotion, "name: Check out validated Formula-only target")
if validateIndex < 0 || checkoutIndex <= validateIndex {
t.Error("Formula target must be fully validated before it is checked out or executed")
}
ackReadIndex := strings.Index(promotion, "const {data: promotionCheck} = await github.rest.checks.get")
ackOutputIndex := strings.Index(promotion, "core.setOutput('check_run_id', String(checkRunId))")
targetReadIndex := strings.Index(promotion, "const {data: targetCommit} = await github.rest.repos.getCommit")
if ackReadIndex < 0 || ackOutputIndex <= ackReadIndex || targetReadIndex <= ackOutputIndex {
t.Error("Formula acknowledgement must be identity-checked and bound to the finalizer before target validation")
}
if strings.Count(promotion, "path: coverage-cache.txt") != 4 {
t.Error("Formula promotion must restore target/source, save, and verify through the shared cache version path")
}
releaseData, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "release.yml"))
if err != nil {
t.Fatalf("ReadFile(release.yml) error = %v", err)
}
release := string(releaseData)
publishStart := strings.Index(release, " publish-release:\n")
sealStart := strings.Index(release, " - name: Seal Formula-only Code Admission contexts\n")
if publishStart < 0 || sealStart <= publishStart {
t.Fatal("release workflow missing Formula-only seal start")
}
if !strings.Contains(release[publishStart:sealStart], "timeout-minutes: 30") {
t.Error("publish-release must not be extended by cache acknowledgement polling")
}
sealEnd := strings.Index(release[sealStart:], "\n - name: Reverify exact immutable npm package\n")
if sealEnd < 0 {
t.Fatal("release workflow missing Formula-only seal boundaries")
}
seal := release[sealStart : sealStart+sealEnd]
for _, want := range []string{
`core.setOutput("coverage_baseline_required", "true")`,
`core.setOutput("coverage_baseline_commit", commit)`,
} {
if !strings.Contains(seal, want) {
t.Errorf("Formula seal missing cache-promotion dispatch contract %q", want)
}
}
for _, forbidden := range []string{
"for (let attempt = 1; attempt <= 180; attempt += 1)",
"github.rest.checks.get",
"promotionComplete",
"Coverage Baseline Cache",
"github.rest.repos.createDispatchEvent",
} {
if strings.Contains(seal, forbidden) {
t.Errorf("irreversible publish-release must not wait on cache through %q", forbidden)
}
}
confirmationStart := strings.Index(release, " coverage-baseline-confirmation:\n")
deliveryGateStart := strings.Index(release, " release-delivery-gate:\n")
if confirmationStart < 0 || deliveryGateStart <= confirmationStart {
t.Fatal("release workflow missing independent Formula baseline confirmation job")
}
confirmation := release[confirmationStart:deliveryGateStart]
for _, want := range []string{
"if (!['true', 'false'].includes(rawRequired))",
"github.rest.checks.create",
"name: 'Coverage Baseline Cache'",
"status: 'queued'",
"external_id: expectedExternalId",
"github.rest.repos.createDispatchEvent",
"event_type: 'coverage-baseline-promote'",
"check_run_id: String(promotionCheck.id)",
"conclusion: 'failure'",
"for (let attempt = 1; attempt <= 180; attempt += 1)",
"github.rest.checks.get",
"currentCheck.name !== 'Coverage Baseline Cache'",
"currentCheck.conclusion !== 'success'",
"setTimeout(resolve, 10000)",
"Formula baseline promotion timed out",
} {
if !strings.Contains(confirmation, want) {
t.Errorf("independent Formula baseline confirmation missing %q", want)
}
}
}
func TestCoverageBaselineRepairContract(t *testing.T) {
root, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("Abs(repo root) error = %v", err)
}
data, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "coverage-baseline-repair.yml"))
if err != nil {
t.Fatalf("ReadFile(coverage-baseline-repair.yml) error = %v", err)
}
workflow := string(data)
for _, want := range []string{
"pull_request_target:",
"branches: [main]",
"types: [closed]",
"workflow_run:",
"workflows: [CI]",
"types: [completed]",
"repository_dispatch:",
"types: [coverage-baseline-repair]",
"schedule:",
`cron: "23 * * * *"`,
"workflow_dispatch:",
"group: coverage-baseline-repair-${{ github.event_name == 'pull_request_target' && github.event.pull_request.merge_commit_sha || github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.event_name == 'repository_dispatch' && github.event.client_payload.merge_commit_sha || github.sha }}",
"cancel-in-progress: false",
"queue: max",
} {
if !strings.Contains(workflow, want) {
t.Errorf("coverage baseline repair missing workflow contract %q", want)
}
}
dispatchStart := strings.Index(workflow, "\n dispatch-merged-pr:\n")
failedDispatchStart := strings.Index(workflow, "\n dispatch-failed-ci:\n")
repairStart := strings.Index(workflow, "\n repair:\n")
if dispatchStart < 0 || failedDispatchStart <= dispatchStart || repairStart <= failedDispatchStart {
t.Fatal("coverage baseline repair is missing ordered merged-PR, failed-CI, and producer jobs")
}
dispatcher := workflow[dispatchStart:failedDispatchStart]
for _, want := range []string{
"github.event_name == 'pull_request_target'",
"github.event.pull_request.merged == true",
"github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli'",
"actions: read",
"contents: write",
"pull-requests: read",
"actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b",
"context.payload.repository?.default_branch !== 'main'",
"currentPull.state === 'closed'",
"currentPull.merged === true",
"typeof currentPull.merged_at === 'string'",
"const baseRef = eventPull?.base?.ref;",
"baseRef !== 'main'",
"currentPull.base?.ref === 'main'",
"isStableMergedPRIdentity(",
"currentPull.head?.sha === headSha",
"currentPull.merge_commit_sha === mergeCommitSha",
"for (let attempt = 1; attempt <= 6; attempt += 1)",
"setTimeout(resolve, 5000)",
"basehead: `${targetSha}...${branch.commit.sha}`",
"['ahead', 'identical'].includes(comparison.status)",
"for (let attempt = 1; attempt <= 12; attempt += 1)",
"github.rest.actions.getWorkflow",
"workflow_id: '.github/workflows/ci.yml'",
"ciWorkflow.name !== 'CI'",
"ciWorkflow.path !== '.github/workflows/ci.yml'",
"ciWorkflow.state !== 'active'",
"github.rest.actions.listWorkflowRunsForRepo",
"branch: 'main'",
"event: 'push'",
"run.name === 'CI'",
"run.workflow_id === ciWorkflow.id",
"run.path === ciWorkflow.path",
"run.event === 'push'",
"run.head_sha === mergeCommitSha",
"run.head_branch === 'main'",
"['queued', 'in_progress', 'completed'].includes(run.status)",
"setTimeout(resolve, 5000)",
"repair dispatch is unnecessary",
"github.rest.repos.createDispatchEvent",
"event_type: 'coverage-baseline-repair'",
"source: 'merged_pr'",
"pull_number: String(pullNumber)",
"head_sha: headSha",
"merge_commit_sha: mergeCommitSha",
} {
if !strings.Contains(dispatcher, want) {
t.Errorf("merged-PR repair dispatcher missing %q", want)
}
}
ciWorkflowLookup := strings.Index(dispatcher, "github.rest.actions.getWorkflow")
pushRunLookup := strings.Index(dispatcher, "github.rest.actions.listWorkflowRunsForRepo")
repairDispatch := strings.Index(dispatcher, "github.rest.repos.createDispatchEvent")
if ciWorkflowLookup < 0 || pushRunLookup <= ciWorkflowLookup || repairDispatch <= pushRunLookup {
t.Error("merged-PR dispatcher must bind the fixed CI workflow before exhausting exact push-run lookup and dispatch")
}
stableIdentityCheck := strings.Index(dispatcher, "if (!isStableMergedPRIdentity(")
mainContainmentCheck := strings.Index(dispatcher, "await requireMainContainment(mergeCommitSha)")
if stableIdentityCheck < 0 || mainContainmentCheck <= stableIdentityCheck || repairDispatch <= mainContainmentCheck {
t.Error("merged-PR dispatcher must prove stable PR identity and main containment before dispatch")
}
for _, forbidden := range []string{
"actions/checkout@",
"actions/cache/",
"actions/setup-go@",
"go test ",
"github.event.pull_request.head.ref",
"currentPull.base.sha",
"base_sha",
"secrets.",
} {
if strings.Contains(dispatcher, forbidden) {
t.Errorf("privileged pull_request_target dispatcher must not contain %q", forbidden)
}
}
failedDispatcher := workflow[failedDispatchStart:repairStart]
for _, want := range []string{
"github.event_name == 'workflow_run'",
"github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli'",
"github.event.workflow_run.name == 'CI'",
"github.event.workflow_run.event == 'push'",
"github.event.workflow_run.head_branch == 'main'",
"github.event.workflow_run.status == 'completed'",
"github.event.workflow_run.conclusion != 'success'",
"actions: read",
"contents: write",
"actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b",
"context.payload.repository?.default_branch !== 'main'",
"eventRun?.name !== 'CI'",
"eventRun?.event !== 'push'",
"eventRun?.head_branch !== 'main'",
"eventRun?.status !== 'completed'",
"conclusion === 'success'",
"github.rest.actions.getWorkflow",
"workflow_id: '.github/workflows/ci.yml'",
"github.rest.actions.getWorkflowRun",
"eventRun.workflow_id !== ciWorkflow.id",
"currentRun.workflow_id !== ciWorkflow.id",
"currentRun.name !== 'CI'",
"currentRun.event !== 'push'",
"currentRun.head_branch !== 'main'",
"currentRun.head_sha !== headSha",
"currentRun.run_attempt !== runAttempt",
"currentRun.status !== 'completed'",
"currentRun.conclusion !== conclusion",
"currentRun.repository?.full_name !== upstream",
"currentRun.head_repository?.full_name !== upstream",
"github.rest.repos.createDispatchEvent",
"event_type: 'coverage-baseline-repair'",
"source: 'failed_ci'",
"workflow_run_id: String(runID)",
"workflow_run_attempt: String(runAttempt)",
"workflow_conclusion: conclusion",
"merge_commit_sha: headSha",
} {
if !strings.Contains(failedDispatcher, want) {
t.Errorf("failed-CI repair dispatcher missing %q", want)
}
}
failedRunRead := strings.Index(failedDispatcher, "github.rest.actions.getWorkflowRun")
failedRepairDispatch := strings.Index(failedDispatcher, "github.rest.repos.createDispatchEvent")
if failedRunRead < 0 || failedRepairDispatch <= failedRunRead {
t.Error("workflow_run dispatcher must API-bind the exact failed CI run before repository dispatch")
}
for _, forbidden := range []string{
"actions/checkout@",
"actions/cache/",
"actions/setup-go@",
"go test ",
"secrets.",
} {
if strings.Contains(failedDispatcher, forbidden) {
t.Errorf("privileged workflow_run dispatcher must not contain %q", forbidden)
}
}
producer := workflow[repairStart:]
for _, want := range []string{
"github.event_name == 'repository_dispatch' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'",
"timeout-minutes: 35",
"actions: read",
"contents: read",
"pull-requests: read",
"id: resolve-target",
"context.eventName === 'repository_dispatch'",
"payload.source === 'merged_pr'",
"payload.pull_number",
"payload.head_sha",
"payload.merge_commit_sha",
"currentPull.state === 'closed'",
"currentPull.merged === true",
"typeof currentPull.merged_at === 'string'",
"currentPull.base?.ref === 'main'",
"isStableMergedPRIdentity(",
"currentPull.head?.sha === headSha",
"currentPull.merge_commit_sha === mergeCommitSha",
"payload.source === 'failed_ci'",
"payload.workflow_run_id",
"payload.workflow_run_attempt",
"payload.workflow_conclusion",
"github.rest.actions.getWorkflow",
"workflow_id: '.github/workflows/ci.yml'",
"github.rest.actions.getWorkflowRun",
"ciWorkflow.name !== 'CI'",
"ciWorkflow.path !== '.github/workflows/ci.yml'",
"currentRun.id !== workflowRunID",
"currentRun.workflow_id !== ciWorkflow.id",
"currentRun.name !== 'CI'",
"currentRun.event !== 'push'",
"currentRun.head_branch !== 'main'",
"currentRun.head_sha !== targetSha",
"currentRun.run_attempt !== workflowRunAttempt",
"currentRun.status !== 'completed'",
"currentRun.conclusion !== workflowConclusion",
"coverage-baseline-repair payload has an unknown source",
"context.ref !== 'refs/heads/main'",
"targetSha = context.sha",
"for (let attempt = 1; attempt <= 6; attempt += 1)",
"basehead: `${targetSha}...${branch.commit.sha}`",
"core.setOutput('target_sha', targetSha)",
"actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683",
"fetch-depth: 0",
"persist-credentials: false",
"ref: ${{ steps.resolve-target.outputs.target_sha }}",
`run: test "$(git rev-parse HEAD)" = "$TARGET_SHA"`,
"actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff",
"id: target-cache",
"actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830",
"key: dws-coverage-full-v2-${{ steps.resolve-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}",
"go test -count=1 -p 1",
"./ ./cmd/... ./internal/... ./skills/...",
"actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830",
"id: target-cache-verification",
"lookup-only: true",
"fail-on-cache-miss: true",
"EXACT_CACHE_HIT: ${{ steps.target-cache-verification.outputs.cache-hit }}",
`run: test "$EXACT_CACHE_HIT" = true`,
} {
if !strings.Contains(producer, want) {
t.Errorf("trusted coverage repair producer missing %q", want)
}
}
for _, forbidden := range []string{
"restore-keys",
"github.event.pull_request.head.ref",
"currentPull.base.sha",
"base_sha",
"HOMEBREW_PR_TOKEN",
"RELEASE_GOVERNANCE_TOKEN",
"REVIEWER_ROUTER_APP_PRIVATE_KEY",
"contents: write",
} {
if strings.Contains(producer, forbidden) {
t.Errorf("trusted coverage repair producer must not contain %q", forbidden)
}
}
if strings.Count(producer, "path: coverage-cache.txt") != 3 {
t.Error("coverage repair must restore, save, and independently verify one exact cache path/version")
}
if strings.Count(producer, "key: dws-coverage-full-v2-${{ steps.resolve-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}") != 3 {
t.Error("coverage repair restore, save, and verification must share one exact target key")
}
stablePayloadIdentityCheck := strings.Index(producer, "if (!isStableMergedPRIdentity(")
mainPayloadContainmentCheck := strings.Index(producer, "await requireMainContainment(targetSha)")
resolvedTargetOutput := strings.Index(producer, "core.setOutput('target_sha', targetSha)")
if stablePayloadIdentityCheck < 0 ||
mainPayloadContainmentCheck <= stablePayloadIdentityCheck ||
resolvedTargetOutput <= mainPayloadContainmentCheck {
t.Error("merged-PR producer must prove stable payload identity and main containment before resolving checkout target")
}
validateIndex := strings.Index(producer, "name: Resolve trusted main repair target")
checkoutIndex := strings.Index(producer, "name: Check out exact protected-main target")
if validateIndex < 0 || checkoutIndex <= validateIndex {
t.Error("repository_dispatch input must be fully bound to its protected-main source before checkout")
}
}
+429 -1
View File
@@ -3,17 +3,22 @@ package scripts_test
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"syscall"
"testing"
"time"
)
var expectedPackagedSkillTargets = []string{
@@ -93,6 +98,350 @@ func TestPackageManagerVersionVerificationReadsRawBinary(t *testing.T) {
}
}
func TestNPMWrapperForwardsSIGTERMToVendor(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("POSIX signal forwarding contract")
}
nodePath, err := exec.LookPath("node")
if err != nil {
t.Skip("node is required to exercise the npm wrapper")
}
root := t.TempDir()
binDir := filepath.Join(root, "bin")
vendorDir := filepath.Join(root, "vendor")
if err := os.MkdirAll(binDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(vendorDir, 0o755); err != nil {
t.Fatal(err)
}
wrapperSource := filepath.Join("..", "..", "build", "npm", "bin", "dws.js")
wrapperData, err := os.ReadFile(wrapperSource)
if err != nil {
t.Fatal(err)
}
wrapperPath := filepath.Join(binDir, "dws.js")
if err := os.WriteFile(wrapperPath, wrapperData, 0o755); err != nil {
t.Fatal(err)
}
pidPath := filepath.Join(root, "child.pid")
signalPath := filepath.Join(root, "child.signal")
vendorPath := filepath.Join(vendorDir, "dws")
vendorScript := "#!/bin/sh\n" +
"printf '%s' \"$$\" > \"$DWS_TEST_CHILD_PID_FILE\"\n" +
"trap 'printf TERM > \"$DWS_TEST_SIGNAL_FILE\"; exit 0' TERM\n" +
"while :; do sleep 0.1; done\n"
if err := os.WriteFile(vendorPath, []byte(vendorScript), 0o755); err != nil {
t.Fatal(err)
}
cmd := exec.Command(nodePath, wrapperPath)
cmd.Env = append(os.Environ(),
"DWS_TEST_CHILD_PID_FILE="+pidPath,
"DWS_TEST_SIGNAL_FILE="+signalPath,
)
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
childPID := waitForPIDFile(t, pidPath)
t.Cleanup(func() {
_ = exec.Command("kill", "-TERM", strconv.Itoa(childPID)).Run()
})
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
t.Fatalf("signal npm wrapper: %v", err)
}
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case err := <-done:
if err == nil {
t.Fatal("signal-terminated npm wrapper returned success")
}
case <-time.After(5 * time.Second):
t.Fatal("npm wrapper did not exit after SIGTERM")
}
signalData, err := os.ReadFile(signalPath)
if err != nil {
t.Fatalf("vendor did not record SIGTERM: %v", err)
}
if got := strings.TrimSpace(string(signalData)); got != "TERM" {
t.Fatalf("vendor signal = %q, want TERM", got)
}
if err := exec.Command("kill", "-0", strconv.Itoa(childPID)).Run(); err == nil {
t.Fatalf("vendor process %d is still running after npm wrapper exited", childPID)
}
}
func TestNPMWrapperForwardsForegroundGroupSIGINTToVendorGroupOnce(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("POSIX foreground process-group signal contract")
}
nodePath, err := exec.LookPath("node")
if err != nil {
t.Skip("node is required to exercise the npm wrapper")
}
root := t.TempDir()
binDir := filepath.Join(root, "bin")
vendorDir := filepath.Join(root, "vendor")
if err := os.MkdirAll(binDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(vendorDir, 0o755); err != nil {
t.Fatal(err)
}
wrapperData, err := os.ReadFile(filepath.Join("..", "..", "build", "npm", "bin", "dws.js"))
if err != nil {
t.Fatal(err)
}
wrapperPath := filepath.Join(binDir, "dws.js")
if err := os.WriteFile(wrapperPath, wrapperData, 0o755); err != nil {
t.Fatal(err)
}
pidPath := filepath.Join(root, "child.pid")
signalPath := filepath.Join(root, "child.signal")
descendantPIDPath := filepath.Join(root, "descendant.pid")
descendantSignalPath := filepath.Join(root, "descendant.signal")
vendorPath := filepath.Join(vendorDir, "dws")
vendorScript := `#!/usr/bin/env node
"use strict";
const fs = require("fs");
const childProcess = require("child_process");
const descendantSource = String.raw` + "`" + `
"use strict";
const fs = require("fs");
let exiting = false;
process.on("SIGINT", () => {
fs.appendFileSync(process.env.DWS_TEST_DESCENDANT_SIGNAL_FILE, "INT\n");
if (!exiting) {
exiting = true;
setTimeout(() => process.exit(0), 250);
}
});
fs.writeFileSync(process.env.DWS_TEST_DESCENDANT_PID_FILE, String(process.pid));
setInterval(() => {}, 1000);
` + "`" + `;
childProcess.spawn(process.execPath, ["-e", descendantSource], {
stdio: "inherit",
env: process.env,
});
let exiting = false;
process.on("SIGINT", () => {
fs.appendFileSync(process.env.DWS_TEST_SIGNAL_FILE, "INT\n");
if (!exiting) {
exiting = true;
setTimeout(() => process.exit(0), 250);
}
});
fs.writeFileSync(process.env.DWS_TEST_CHILD_PID_FILE, String(process.pid));
setInterval(() => {}, 1000);
`
if err := os.WriteFile(vendorPath, []byte(vendorScript), 0o755); err != nil {
t.Fatal(err)
}
cmd := exec.Command(nodePath, wrapperPath)
cmd.Env = append(os.Environ(),
"DWS_TEST_CHILD_PID_FILE="+pidPath,
"DWS_TEST_SIGNAL_FILE="+signalPath,
"DWS_TEST_DESCENDANT_PID_FILE="+descendantPIDPath,
"DWS_TEST_DESCENDANT_SIGNAL_FILE="+descendantSignalPath,
)
configureIsolatedProcessGroup(cmd)
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
childPID := waitForPIDFile(t, pidPath)
descendantPID := waitForPIDFile(t, descendantPIDPath)
t.Cleanup(func() {
_ = exec.Command("kill", "-TERM", strconv.Itoa(childPID)).Run()
_ = exec.Command("kill", "-TERM", strconv.Itoa(descendantPID)).Run()
})
wrapperGroup, err := processGroupID(cmd.Process.Pid)
if err != nil {
t.Fatalf("wrapper process group: %v", err)
}
childGroup, err := processGroupID(childPID)
if err != nil {
t.Fatalf("vendor process group: %v", err)
}
if childGroup == wrapperGroup {
t.Fatalf("vendor process group = wrapper process group %d; foreground signal would be duplicated", childGroup)
}
descendantGroup, err := processGroupID(descendantPID)
if err != nil {
t.Fatalf("descendant process group: %v", err)
}
if descendantGroup != childGroup {
t.Fatalf("descendant process group = %d, want vendor process group %d", descendantGroup, childGroup)
}
if err := signalProcessGroup(wrapperGroup, syscall.SIGINT); err != nil {
t.Fatalf("signal foreground process group: %v", err)
}
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case err := <-done:
if err == nil {
t.Fatal("signal-terminated npm wrapper returned success")
}
case <-time.After(5 * time.Second):
t.Fatal("npm wrapper did not exit after foreground SIGINT")
}
signalData, err := os.ReadFile(signalPath)
if err != nil {
t.Fatalf("vendor did not record SIGINT: %v", err)
}
if got := strings.Fields(string(signalData)); len(got) != 1 || got[0] != "INT" {
t.Fatalf("vendor signals = %q, want exactly one INT", got)
}
descendantSignalData, err := os.ReadFile(descendantSignalPath)
if err != nil {
t.Fatalf("vendor descendant did not record SIGINT: %v", err)
}
if got := strings.Fields(string(descendantSignalData)); len(got) != 1 || got[0] != "INT" {
t.Fatalf("vendor descendant signals = %q, want exactly one INT", got)
}
waitForProcessExit(t, childPID, "vendor")
waitForProcessExit(t, descendantPID, "vendor descendant")
}
func TestNPMWrapperPreservesInteractiveTTY(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("POSIX controlling-terminal contract")
}
nodePath, err := exec.LookPath("node")
if err != nil {
t.Skip("node is required to exercise the npm wrapper")
}
root := t.TempDir()
binDir := filepath.Join(root, "bin")
vendorDir := filepath.Join(root, "vendor")
if err := os.MkdirAll(binDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(vendorDir, 0o755); err != nil {
t.Fatal(err)
}
wrapperData, err := os.ReadFile(filepath.Join("..", "..", "build", "npm", "bin", "dws.js"))
if err != nil {
t.Fatal(err)
}
wrapperPath := filepath.Join(binDir, "dws.js")
if err := os.WriteFile(wrapperPath, wrapperData, 0o755); err != nil {
t.Fatal(err)
}
resultPath := filepath.Join(root, "interactive.result")
readyPath := filepath.Join(root, "interactive.ready")
vendorPath := filepath.Join(vendorDir, "dws")
vendorScript := "#!/bin/sh\n" +
"printf 'DWS prompt: ' > /dev/tty\n" +
"printf ready > \"$DWS_TEST_INTERACTIVE_READY\"\n" +
"IFS= read -r answer < /dev/tty\n" +
"printf '%s' \"$answer\" > \"$DWS_TEST_INTERACTIVE_RESULT\"\n"
if err := os.WriteFile(vendorPath, []byte(vendorScript), 0o755); err != nil {
t.Fatal(err)
}
cmd := exec.Command(nodePath, wrapperPath)
cmd.Env = append(os.Environ(),
"DWS_TEST_INTERACTIVE_RESULT="+resultPath,
"DWS_TEST_INTERACTIVE_READY="+readyPath,
)
terminal, err := startWithPTY(cmd)
if err != nil {
t.Fatalf("start npm wrapper with PTY: %v", err)
}
defer terminal.Close()
t.Cleanup(func() { _ = cmd.Process.Kill() })
var output bytes.Buffer
outputDone := make(chan struct{})
go func() {
_, _ = io.Copy(&output, terminal)
close(outputDone)
}()
waitForFile(t, readyPath)
if _, err := fmt.Fprintln(terminal, "confirmed"); err != nil {
t.Fatalf("write interactive input: %v", err)
}
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
var waitErr error
select {
case waitErr = <-done:
case <-time.After(5 * time.Second):
t.Fatal("interactive npm wrapper did not exit after terminal input")
}
_ = terminal.Close()
<-outputDone
if waitErr != nil {
t.Fatalf("interactive npm wrapper: %v\noutput:\n%s", waitErr, output.String())
}
result, err := os.ReadFile(resultPath)
if err != nil {
t.Fatalf("interactive vendor did not record terminal input: %v\noutput:\n%s", err, output.String())
}
if got := strings.TrimSpace(string(result)); got != "confirmed" {
t.Fatalf("interactive input = %q, want confirmed\noutput:\n%s", got, output.String())
}
}
func waitForPIDFile(t *testing.T, path string) int {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
data, err := os.ReadFile(path)
if err == nil {
pid, parseErr := strconv.Atoi(strings.TrimSpace(string(data)))
if parseErr == nil && pid > 0 {
return pid
}
}
time.Sleep(25 * time.Millisecond)
}
t.Fatalf("timed out waiting for vendor pid file %s", path)
return 0
}
func waitForFile(t *testing.T, path string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if _, err := os.Stat(path); err == nil {
return
}
time.Sleep(25 * time.Millisecond)
}
t.Fatalf("timed out waiting for file %s", path)
}
func waitForProcessExit(t *testing.T, pid int, label string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if err := exec.Command("kill", "-0", strconv.Itoa(pid)).Run(); err != nil {
return
}
time.Sleep(25 * time.Millisecond)
}
t.Fatalf("%s process %d is still running after npm wrapper exited", label, pid)
}
func TestPackageManagerVerifierCoversSpecificAndFallbackSkillRoots(t *testing.T) {
t.Parallel()
@@ -2082,6 +2431,7 @@ func TestReleaseWorkflowDeliveryGateFailsClosed(t *testing.T) {
"- verify-darwin-signatures",
"- publish-release",
"- publish-channels",
"- coverage-baseline-confirmation",
"- mirror-gitee-release",
"- repair-npm",
"- repair-channel",
@@ -2091,6 +2441,7 @@ func TestReleaseWorkflowDeliveryGateFailsClosed(t *testing.T) {
`RELEASE_VALIDATION_RESULT: ${{ needs.release-validation.result }}`,
`RELEASE_PLAN_RESULT: ${{ needs.release-plan.result }}`,
`SEAL_RELEASE_RESULT: ${{ needs.seal-release.result }}`,
`COVERAGE_BASELINE_CONFIRMATION_RESULT: ${{ needs.coverage-baseline-confirmation.result }}`,
"require_publication",
`require_result release-contract "$RELEASE_CONTRACT_RESULT" success`,
`require_result release-validation "$RELEASE_VALIDATION_RESULT" success`,
@@ -2098,6 +2449,8 @@ func TestReleaseWorkflowDeliveryGateFailsClosed(t *testing.T) {
`require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" success`,
`require_result publish-release "$PUBLISH_RELEASE_RESULT" success`,
`require_result publish-channels "$PUBLISH_CHANNELS_RESULT" success`,
`require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" success`,
`require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" skipped`,
"workflow_dispatch:recover_release",
"workflow_dispatch:create_release",
"workflow_dispatch:plan_release",
@@ -2438,8 +2791,12 @@ func TestReleaseWorkflowSealsOnlyVerifiedFormulaCommitContexts(t *testing.T) {
}
for _, required := range []string{
"timeout-minutes: 30",
`coverage_baseline_required: ${{ steps.seal-formula.outputs.coverage_baseline_required }}`,
`coverage_baseline_commit: ${{ steps.seal-formula.outputs.coverage_baseline_commit }}`,
"id: homebrew-stable",
"id: homebrew-beta",
"id: seal-formula",
`FORMULA_CHANGED: ${{ steps.homebrew-stable.outputs.formula_changed || steps.homebrew-beta.outputs.formula_changed }}`,
`FORMULA_COMMIT: ${{ steps.homebrew-stable.outputs.published_commit || steps.homebrew-beta.outputs.published_commit }}`,
} {
@@ -2481,6 +2838,8 @@ func TestReleaseWorkflowSealsOnlyVerifiedFormulaCommitContexts(t *testing.T) {
`head_sha: commit`,
`status: "completed"`,
`conclusion: "success"`,
`core.setOutput("coverage_baseline_required", "true")`,
`core.setOutput("coverage_baseline_commit", commit)`,
} {
if !strings.Contains(seal, required) {
t.Errorf("Formula-only Code Admission sealing is missing %q", required)
@@ -2492,12 +2851,17 @@ func TestReleaseWorkflowSealsOnlyVerifiedFormulaCommitContexts(t *testing.T) {
}
}
if strings.Count(seal, "github.rest.checks.create") != 1 {
t.Error("Formula-only checks must be created only by the single verified context loop")
t.Error("Formula sealing must write only the reviewed Code Admission contexts")
}
for _, forbidden := range []string{
"head_sha: context.sha",
"head_sha: parent",
"head_sha: branch.data.commit.sha",
"github.rest.checks.get",
"promotionComplete",
"setTimeout(resolve, 10000)",
"Coverage Baseline Cache",
"github.rest.repos.createDispatchEvent",
} {
if strings.Contains(seal, forbidden) {
t.Errorf("Formula-only Code Admission must not mark an unverified head green: found %q", forbidden)
@@ -2505,6 +2869,9 @@ func TestReleaseWorkflowSealsOnlyVerifiedFormulaCommitContexts(t *testing.T) {
}
createCheck := strings.Index(seal, "github.rest.checks.create")
if createCheck == -1 {
t.Error("Formula sealing must create the verified Code Admission contexts")
}
for name, marker := range map[string]string{
"single-parent Formula-only identity": "const exactFormulaCommit",
"successful parent contexts": "invalidParentContexts.length > 0",
@@ -2518,6 +2885,67 @@ func TestReleaseWorkflowSealsOnlyVerifiedFormulaCommitContexts(t *testing.T) {
}
}
func TestReleaseWorkflowConfirmsFormulaCacheWithoutBlockingChannels(t *testing.T) {
t.Parallel()
workflow := readReleaseWorkflow(t)
publishJob := releaseWorkflowSection(t, workflow, " publish-release:\n", "\n publish-channels:\n")
channelsJob := releaseWorkflowSection(t, workflow, " publish-channels:\n", "\n mirror-gitee-release:\n")
confirmation := releaseWorkflowSection(
t,
workflow,
" coverage-baseline-confirmation:\n",
"\n release-delivery-gate:\n",
)
for _, required := range []string{
`if: ${{ !cancelled() && (needs.publish-release.result == 'success' || needs.publish-release.outputs.coverage_baseline_required == 'true') }}`,
"needs: publish-release",
"timeout-minutes: 35",
"checks: write",
"contents: write",
`BASELINE_REQUIRED: ${{ needs.publish-release.outputs.coverage_baseline_required }}`,
`FORMULA_COMMIT: ${{ needs.publish-release.outputs.coverage_baseline_commit }}`,
"if (!['true', 'false'].includes(rawRequired))",
"Formula baseline requirement is invalid",
"github.rest.checks.create",
"name: 'Coverage Baseline Cache'",
"status: 'queued'",
"external_id: expectedExternalId",
"github.rest.repos.createDispatchEvent",
"event_type: 'coverage-baseline-promote'",
"source_run_id: String(context.runId)",
"check_run_id: String(promotionCheck.id)",
"github.rest.checks.update",
"conclusion: 'failure'",
"github.rest.checks.get",
"currentCheck.head_sha !== targetSha",
"currentCheck.external_id !== expectedExternalId",
"currentCheck.app?.slug !== 'github-actions'",
"for (let attempt = 1; attempt <= 180; attempt += 1)",
"currentCheck.conclusion !== 'success'",
"await new Promise(resolve => setTimeout(resolve, 10000))",
"Formula baseline promotion timed out",
} {
if !strings.Contains(confirmation, required) {
t.Errorf("Formula baseline confirmation missing %q", required)
}
}
if strings.Contains(channelsJob, "coverage-baseline-confirmation") ||
strings.Contains(channelsJob, "needs.coverage-baseline-confirmation") {
t.Error("cache acknowledgement must not block npm or mirror publication")
}
for _, forbidden := range []string{
"Coverage Baseline Cache",
"github.rest.repos.createDispatchEvent",
"github.rest.checks.get",
"promotionComplete",
} {
if strings.Contains(publishJob, forbidden) {
t.Errorf("irreversible publication job must not own cache lifecycle marker %q", forbidden)
}
}
}
func TestReleaseWorkflowWaitsForNPMDistTagPropagation(t *testing.T) {
workflow := readReleaseWorkflow(t)
script := releaseWorkflowRunScript(
+27
View File
@@ -0,0 +1,27 @@
//go:build !windows
package scripts_test
import (
"os"
"os/exec"
"syscall"
"github.com/creack/pty"
)
func configureIsolatedProcessGroup(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
func processGroupID(pid int) (int, error) {
return syscall.Getpgid(pid)
}
func signalProcessGroup(groupID int, signal syscall.Signal) error {
return syscall.Kill(-groupID, signal)
}
func startWithPTY(cmd *exec.Cmd) (*os.File, error) {
return pty.Start(cmd)
}
@@ -0,0 +1,24 @@
//go:build windows
package scripts_test
import (
"errors"
"os"
"os/exec"
"syscall"
)
func configureIsolatedProcessGroup(*exec.Cmd) {}
func processGroupID(int) (int, error) {
return 0, errors.New("POSIX process groups are unavailable on Windows")
}
func signalProcessGroup(int, syscall.Signal) error {
return errors.New("POSIX process groups are unavailable on Windows")
}
func startWithPTY(*exec.Cmd) (*os.File, error) {
return nil, errors.New("POSIX pseudo terminals are unavailable on Windows")
}
@@ -0,0 +1,173 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package scripts_test
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"gopkg.in/yaml.v3"
)
const mergeDefaultsProjectionHelper = `function classifyMergeDefaults(repository) {
if (
repository === null ||
typeof repository !== 'object' ||
Array.isArray(repository)
) {
return 'invalid';
}
const hasTitle = Object.prototype.hasOwnProperty.call(
repository,
'merge_commit_title',
);
const hasMessage = Object.prototype.hasOwnProperty.call(
repository,
'merge_commit_message',
);
if (!hasTitle && !hasMessage) {
return 'omitted';
}
if (!hasTitle || !hasMessage) {
return 'invalid';
}
if (
repository.merge_commit_title === 'MERGE_MESSAGE' &&
['PR_TITLE', 'BLANK'].includes(repository.merge_commit_message)
) {
return 'reviewed';
}
return 'invalid';
}`
const privilegedMergeDefaultsGuard = `if (mergeDefaultsProjection !== 'reviewed') {
throw new Error(
'Dedicated Reviewer Router App cannot verify the reviewed repository merge-message defaults.',
);
}`
func TestReviewerRouterMergeDefaultsProjection(t *testing.T) {
t.Parallel()
node, err := exec.LookPath("node")
if err != nil {
t.Skip("node is required to verify merge-default projection semantics")
}
root, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("resolve repository root: %v", err)
}
var scripts []string
for _, relativePath := range []string{
filepath.Join(".github", "workflows", "ci.yml"),
filepath.Join(".github", "workflows", "reviewer-router.yml"),
} {
path := filepath.Join(root, relativePath)
data, readErr := os.ReadFile(path)
if readErr != nil {
t.Fatalf("read %s: %v", path, readErr)
}
var workflow any
if unmarshalErr := yaml.Unmarshal(data, &workflow); unmarshalErr != nil {
t.Fatalf("parse %s: %v", path, unmarshalErr)
}
collectGitHubScripts(workflow, &scripts)
}
checked := 0
readOnlyChecks := 0
privilegedChecks := 0
for _, script := range scripts {
if strings.Count(script, mergeDefaultsProjectionHelper) != 1 {
continue
}
checked++
for _, marker := range []string{
"github.rest.repos.get",
"const mergeDefaultsProjection = classifyMergeDefaults(repository);",
} {
if !strings.Contains(script, marker) {
t.Errorf("merge-default script does not enforce marker %q", marker)
}
}
if strings.Contains(script, "MINTED_REVIEWER_ROUTER_APP_SLUG") {
privilegedChecks++
for _, marker := range []string{
privilegedMergeDefaultsGuard,
} {
if !strings.Contains(script, marker) {
t.Errorf("privileged merge-default script missing marker %q", marker)
}
}
guardIndex := strings.Index(script, privilegedMergeDefaultsGuard)
guardEndIndex := guardIndex + len(privilegedMergeDefaultsGuard)
firstMutationIndex := len(script)
for _, mutation := range []string{
"disablePullRequestAutoMerge",
"enablePullRequestAutoMerge",
} {
if index := strings.Index(script, mutation); index >= 0 && index < firstMutationIndex {
firstMutationIndex = index
}
}
if guardIndex < 0 || firstMutationIndex == len(script) || guardEndIndex >= firstMutationIndex {
t.Error("privileged merge-default validation must precede every auto-merge mutation")
}
continue
}
readOnlyChecks++
for _, marker := range []string{
"mergeDefaultsProjection === 'invalid'",
"mergeDefaultsProjection === 'omitted'",
"Read-only CI cannot observe repository merge-message defaults; exact validation is delegated to the dedicated App.",
} {
if !strings.Contains(script, marker) {
t.Errorf("read-only merge-default script missing marker %q", marker)
}
}
if strings.Contains(script, "mergeDefaultsProjection !== 'reviewed'") {
t.Error("read-only CI must distinguish exact dual omission from malformed projections")
}
}
if checked != 3 || readOnlyChecks != 1 || privilegedChecks != 2 {
t.Fatalf(
"merge-default scripts checked=%d read-only=%d privileged=%d, want 3/1/2",
checked,
readOnlyChecks,
privilegedChecks,
)
}
verification := mergeDefaultsProjectionHelper + `
const cases = [
['reviewed PR title', {merge_commit_title: 'MERGE_MESSAGE', merge_commit_message: 'PR_TITLE'}, 'reviewed'],
['reviewed blank body', {merge_commit_title: 'MERGE_MESSAGE', merge_commit_message: 'BLANK'}, 'reviewed'],
['dual omission', {}, 'omitted'],
['null repository', null, 'invalid'],
['array repository', [], 'invalid'],
['title only', {merge_commit_title: 'MERGE_MESSAGE'}, 'invalid'],
['message only', {merge_commit_message: 'PR_TITLE'}, 'invalid'],
['own undefined title', {merge_commit_title: undefined, merge_commit_message: 'PR_TITLE'}, 'invalid'],
['own undefined message', {merge_commit_title: 'MERGE_MESSAGE', merge_commit_message: undefined}, 'invalid'],
['null title', {merge_commit_title: null, merge_commit_message: 'PR_TITLE'}, 'invalid'],
['null message', {merge_commit_title: 'MERGE_MESSAGE', merge_commit_message: null}, 'invalid'],
['unsafe title', {merge_commit_title: 'PR_TITLE', merge_commit_message: 'PR_TITLE'}, 'invalid'],
['unsafe body', {merge_commit_title: 'MERGE_MESSAGE', merge_commit_message: 'PR_BODY'}, 'invalid'],
];
for (const [name, repository, want] of cases) {
const got = classifyMergeDefaults(repository);
if (got !== want) {
throw new Error(name + ': got ' + got + ', want ' + want);
}
}
`
command := exec.Command(node, "-e", verification)
if output, runErr := command.CombinedOutput(); runErr != nil {
t.Fatalf("merge-default projection verification failed: %v\n%s", runErr, output)
}
}
@@ -0,0 +1,188 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package scripts_test
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"gopkg.in/yaml.v3"
)
const strictUpdateRuleHelper = `function isStrictUpdateRule(rule) {
if (rule?.type !== 'update') {
return false;
}
if (!Object.prototype.hasOwnProperty.call(rule, 'parameters')) {
return true;
}
const parameters = rule.parameters;
if (
parameters === null ||
typeof parameters !== 'object' ||
Array.isArray(parameters)
) {
return false;
}
const parameterKeys = Object.keys(parameters);
return (
parameterKeys.length === 1 &&
parameterKeys[0] === 'update_allows_fetch_and_merge' &&
parameters.update_allows_fetch_and_merge === false
);
}`
const strictGraphQLUpdateRuleHelper = `function isStrictGraphQLUpdateRule(restRuleset, graphRuleset) {
const restRulesetID = Number(restRuleset?.id);
const graphRulesetID = Number(graphRuleset?.databaseId);
const graphRules = graphRuleset?.rules;
const graphRule = graphRules?.nodes?.[0];
return (
Number.isSafeInteger(restRulesetID) &&
restRulesetID > 0 &&
graphRulesetID === restRulesetID &&
graphRuleset.name === restRuleset.name &&
graphRuleset.enforcement === 'ACTIVE' &&
graphRuleset.target === 'BRANCH' &&
graphRules?.totalCount === 1 &&
graphRules.nodes?.length === 1 &&
graphRule?.type === 'UPDATE' &&
graphRule.parameters?.__typename === 'UpdateParameters' &&
graphRule.parameters.updateAllowsFetchAndMerge === false
);
}`
func TestReviewerRouterAcceptsGitHubStrictUpdateReadProjection(t *testing.T) {
t.Parallel()
node, err := exec.LookPath("node")
if err != nil {
t.Skip("node is required to verify the ruleset projection helper")
}
root, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("resolve repository root: %v", err)
}
var scripts []string
for _, relativePath := range []string{
filepath.Join(".github", "workflows", "ci.yml"),
filepath.Join(".github", "workflows", "reviewer-router.yml"),
} {
path := filepath.Join(root, relativePath)
data, readErr := os.ReadFile(path)
if readErr != nil {
t.Fatalf("read %s: %v", path, readErr)
}
var workflow any
if unmarshalErr := yaml.Unmarshal(data, &workflow); unmarshalErr != nil {
t.Fatalf("parse %s: %v", path, unmarshalErr)
}
collectGitHubScripts(workflow, &scripts)
}
checked := 0
for _, script := range scripts {
if !strings.Contains(script, "const writerRuleset = writerRulesets[0];") {
continue
}
checked++
if got := strings.Count(script, strictUpdateRuleHelper); got != 1 {
t.Errorf("writer-ruleset script contains strict update helper %d times, want 1", got)
}
if got := strings.Count(script, strictGraphQLUpdateRuleHelper); got != 1 {
t.Errorf("writer-ruleset script contains GraphQL strict update helper %d times, want 1", got)
}
if strings.Contains(
script,
"parameters?.update_allows_fetch_and_merge !== false",
) {
t.Error("writer-ruleset script rejects GitHub's omitted strict-false read projection")
}
if !strings.Contains(script, "!isStrictUpdateRule(writerRuleset.rules[0])") {
t.Error("writer-ruleset script does not enforce the strict update helper")
}
for _, marker := range []string{
"query ReviewerRouterWriterRule($rulesetID: ID!)",
"rules(first: 2)",
"updateAllowsFetchAndMerge",
"{rulesetID: writerRuleset.node_id}",
"!isStrictGraphQLUpdateRule(writerRuleset, graphWriterRuleset)",
} {
if !strings.Contains(script, marker) {
t.Errorf("writer-ruleset script does not enforce GraphQL marker %q", marker)
}
}
}
if checked != 3 {
t.Fatalf("writer-ruleset scripts checked = %d, want App enable, reconcile, and CI self-check", checked)
}
verification := strictUpdateRuleHelper + "\n" + strictGraphQLUpdateRuleHelper + `
const cases = [
['omitted parameters', {type: 'update'}, true],
['explicit false', {type: 'update', parameters: {update_allows_fetch_and_merge: false}}, true],
['null parameters', {type: 'update', parameters: null}, false],
['empty parameters', {type: 'update', parameters: {}}, false],
['boolean parameters', {type: 'update', parameters: false}, false],
['string parameters', {type: 'update', parameters: 'malformed'}, false],
['array parameters', {type: 'update', parameters: []}, false],
['explicit true', {type: 'update', parameters: {update_allows_fetch_and_merge: true}}, false],
['null field', {type: 'update', parameters: {update_allows_fetch_and_merge: null}}, false],
['string false', {type: 'update', parameters: {update_allows_fetch_and_merge: 'false'}}, false],
['numeric false', {type: 'update', parameters: {update_allows_fetch_and_merge: 0}}, false],
['extra parameter', {type: 'update', parameters: {update_allows_fetch_and_merge: false, future_exception: false}}, false],
['wrong rule type', {type: 'creation'}, false],
['null rule', null, false],
];
for (const [name, rule, want] of cases) {
const got = isStrictUpdateRule(rule);
if (got !== want) {
throw new Error(name + ': got ' + got + ', want ' + want);
}
}
const restRuleset = {id: 21363804, name: 'main-merge-writers'};
const graphRule = {
type: 'UPDATE',
parameters: {
__typename: 'UpdateParameters',
updateAllowsFetchAndMerge: false,
},
};
const graphRuleset = {
databaseId: 21363804,
name: 'main-merge-writers',
enforcement: 'ACTIVE',
target: 'BRANCH',
rules: {totalCount: 1, nodes: [graphRule]},
};
const graphCases = [
['valid GraphQL projection', restRuleset, graphRuleset, true],
['invalid REST id', {id: 0, name: 'main-merge-writers'}, graphRuleset, false],
['mismatched GraphQL id', restRuleset, {...graphRuleset, databaseId: 7}, false],
['mismatched name', restRuleset, {...graphRuleset, name: 'other'}, false],
['inactive ruleset', restRuleset, {...graphRuleset, enforcement: 'EVALUATE'}, false],
['wrong target', restRuleset, {...graphRuleset, target: 'TAG'}, false],
['extra rule', restRuleset, {...graphRuleset, rules: {totalCount: 2, nodes: [graphRule, graphRule]}}, false],
['wrong rule type', restRuleset, {...graphRuleset, rules: {totalCount: 1, nodes: [{...graphRule, type: 'CREATION'}]}}, false],
['wrong parameter type', restRuleset, {...graphRuleset, rules: {totalCount: 1, nodes: [{...graphRule, parameters: {...graphRule.parameters, __typename: 'PullRequestParameters'}}]}}, false],
['fetch and merge enabled', restRuleset, {...graphRuleset, rules: {totalCount: 1, nodes: [{...graphRule, parameters: {...graphRule.parameters, updateAllowsFetchAndMerge: true}}]}}, false],
['missing GraphQL node', restRuleset, null, false],
];
for (const [name, rest, graph, want] of graphCases) {
const got = isStrictGraphQLUpdateRule(rest, graph);
if (got !== want) {
throw new Error(name + ': got ' + got + ', want ' + want);
}
}
`
command := exec.Command(node, "-e", verification)
if output, runErr := command.CombinedOutput(); runErr != nil {
t.Fatalf("strict update projection verification failed: %v\n%s", runErr, output)
}
}
+502 -17
View File
@@ -16,6 +16,7 @@ import (
type reviewerRouterWorkflow struct {
On map[string]reviewerRouterTrigger `yaml:"on"`
Permissions map[string]string `yaml:"permissions"`
Concurrency map[string]string `yaml:"concurrency"`
Jobs map[string]reviewerRouterJob `yaml:"jobs"`
}
@@ -25,12 +26,16 @@ type reviewerRouterTrigger struct {
}
type reviewerRouterJob struct {
If string `yaml:"if"`
Steps []reviewerRouterStep `yaml:"steps"`
If string `yaml:"if"`
Permissions map[string]string `yaml:"permissions"`
Steps []reviewerRouterStep `yaml:"steps"`
}
type reviewerRouterStep struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
Uses string `yaml:"uses"`
Env map[string]string `yaml:"env"`
With map[string]string `yaml:"with"`
}
@@ -68,8 +73,8 @@ func TestReviewerRouterWorkflowContract(t *testing.T) {
}
}
if len(workflow.On) != 1 {
t.Fatalf("workflow triggers = %v, want pull_request_target only", workflow.On)
if len(workflow.On) != 3 {
t.Fatalf("workflow triggers = %v, want pull_request_target plus protected-main reconciliation", workflow.On)
}
trigger, ok := workflow.On["pull_request_target"]
if !ok {
@@ -78,47 +83,108 @@ func TestReviewerRouterWorkflowContract(t *testing.T) {
if wantBranches := []string{"main"}; !reflect.DeepEqual(trigger.Branches, wantBranches) {
t.Fatalf("pull_request_target branches = %v, want %v", trigger.Branches, wantBranches)
}
wantTypes := []string{"opened", "synchronize", "reopened", "ready_for_review"}
wantTypes := []string{"opened", "synchronize", "reopened", "ready_for_review", "edited", "auto_merge_enabled"}
if !reflect.DeepEqual(trigger.Types, wantTypes) {
t.Fatalf("pull_request_target types = %v, want %v", trigger.Types, wantTypes)
}
if _, ok := workflow.On["workflow_dispatch"]; !ok {
t.Fatalf("workflow triggers = %v, want workflow_dispatch reconciliation", workflow.On)
}
push, ok := workflow.On["push"]
if !ok || !reflect.DeepEqual(push.Branches, []string{"main"}) {
t.Fatalf("push trigger = %v, want protected main only", push)
}
wantPermissions := map[string]string{
"contents": "write",
"contents": "read",
"pull-requests": "write",
}
if !reflect.DeepEqual(workflow.Permissions, wantPermissions) {
t.Fatalf("workflow permissions = %v, want exactly %v", workflow.Permissions, wantPermissions)
}
wantConcurrency := map[string]string{
"group": "reviewer-router-${{ github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number) || 'reconcile-main' }}",
"cancel-in-progress": "${{ github.event_name == 'pull_request_target' }}",
}
if !reflect.DeepEqual(workflow.Concurrency, wantConcurrency) {
t.Fatalf("workflow concurrency = %v, want serialized reconciliation and cancellable PR events %v", workflow.Concurrency, wantConcurrency)
}
if len(workflow.Jobs) != 1 {
t.Fatalf("workflow jobs = %v, want one isolated routing job", workflow.Jobs)
if len(workflow.Jobs) != 3 {
t.Fatalf("workflow jobs = %v, want event routing plus controlled reconciliation", workflow.Jobs)
}
job, ok := workflow.Jobs["route"]
if !ok {
t.Fatalf("workflow jobs = %v, want route", workflow.Jobs)
}
if job.If != "github.event.pull_request.draft == false" {
if job.If != "github.event_name == 'pull_request_target' && github.event.pull_request.draft == false" {
t.Fatalf("route.if = %q, want non-draft guard", job.If)
}
const checkoutSHA = "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683"
const githubScriptSHA = "actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b"
if len(job.Steps) != 2 || job.Steps[0].Uses != checkoutSHA || job.Steps[0].With["ref"] != "${{ github.event.pull_request.base.sha }}" || job.Steps[0].With["persist-credentials"] != "false" || job.Steps[1].Uses != githubScriptSHA {
t.Fatalf("route steps = %#v, want trusted base checkout followed by pinned github-script", job.Steps)
const appTokenSHA = "actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1"
if len(job.Steps) != 2 ||
job.Steps[0].Uses != checkoutSHA ||
job.Steps[0].With["ref"] != "${{ github.event.pull_request.base.sha }}" ||
job.Steps[0].With["persist-credentials"] != "false" ||
job.Steps[1].Uses != githubScriptSHA {
t.Fatalf("route steps = %#v, want trusted base checkout followed by routing only", job.Steps)
}
script := job.Steps[1].With["script"]
autoMergeJob, ok := workflow.Jobs["manage-auto-merge"]
if !ok {
t.Fatalf("workflow jobs = %v, want manage-auto-merge", workflow.Jobs)
}
if autoMergeJob.If != "github.event_name == 'pull_request_target' && github.event.pull_request.draft == false" {
t.Fatalf("manage-auto-merge.if = %q, want exact ready event guard", autoMergeJob.If)
}
wantAutoPermissions := map[string]string{
"contents": "write",
"pull-requests": "write",
}
if !reflect.DeepEqual(autoMergeJob.Permissions, wantAutoPermissions) {
t.Fatalf("manage-auto-merge permissions = %v, want exactly %v", autoMergeJob.Permissions, wantAutoPermissions)
}
if len(autoMergeJob.Steps) != 3 ||
autoMergeJob.Steps[0].Uses != githubScriptSHA ||
autoMergeJob.Steps[1].ID != "reviewer-router-token" ||
autoMergeJob.Steps[1].Uses != appTokenSHA ||
autoMergeJob.Steps[2].Uses != githubScriptSHA {
t.Fatalf("manage-auto-merge steps = %#v, want unsafe-owner cleanup before dedicated App enable", autoMergeJob.Steps)
}
appToken := autoMergeJob.Steps[1]
if len(appToken.With) != 6 {
t.Fatalf("reviewer-router token inputs = %v, want exactly the reviewed repository scope and two permissions", appToken.With)
}
for key, want := range map[string]string{
"client-id": "${{ vars.REVIEWER_ROUTER_APP_CLIENT_ID }}",
"private-key": "${{ secrets.REVIEWER_ROUTER_APP_PRIVATE_KEY }}",
"owner": "${{ github.repository_owner }}",
"repositories": "${{ github.event.repository.name }}",
"permission-contents": "write",
"permission-pull-requests": "write",
} {
if got := appToken.With[key]; got != want {
t.Errorf("reviewer-router token input %q = %q, want %q", key, got, want)
}
}
if _, ok := appToken.With["skip-token-revoke"]; ok {
t.Error("reviewer-router App token must be revoked automatically after the job")
}
routingScript := job.Steps[1].With["script"]
for _, want := range []string{
"REVIEWER_POOL",
"resolveReviewRouting",
"github.rest.pulls.get",
"github.rest.pulls.listFiles",
"currentPull.head.sha !== eventHeadSha",
"currentPull.base.sha !== eventBaseSha",
"currentPull.state !== 'open'",
"currentPull.draft",
"currentPull.base.ref !== 'main'",
"getReadyEventPull('review request')",
"getReadyEventPull('auto-merge enable')",
"context.payload.action === 'synchronize'",
"context.payload.sender?.login?.toLowerCase()",
"reviewer.toLowerCase() !== author",
@@ -142,22 +208,441 @@ func TestReviewerRouterWorkflowContract(t *testing.T) {
"github.rest.pulls.requestReviewers",
"trying the next candidate",
"Reviewer routing hit an unexpected error",
"enablePullRequestAutoMerge",
"mergeMethod: MERGE",
"core.warning",
} {
if !strings.Contains(script, want) {
if !strings.Contains(routingScript, want) {
t.Errorf("reviewer router script is missing contract marker %q", want)
}
}
for _, forbidden := range []string{
"enablePullRequestAutoMerge",
"disablePullRequestAutoMerge",
"mergeMethod: MERGE",
} {
if strings.Contains(routingScript, forbidden) {
t.Errorf("built-in routing token must not control auto-merge through %q", forbidden)
}
}
cleanupStep := autoMergeJob.Steps[0]
if _, ok := cleanupStep.With["github-token"]; ok {
t.Error("unsafe-owner cleanup must use only the job-scoped built-in token")
}
cleanupScript := cleanupStep.With["script"]
for _, want := range []string{
"currentPull.head.sha !== eventHeadSha",
"currentPull.base.sha !== eventBaseSha",
"currentPull.state !== 'open'",
"currentPull.draft",
"currentPull.base.ref !== 'main'",
"const unsafeOwner = 'github-actions[bot]'",
"const skipWorkflowPattern =",
"currentPull.title",
"currentPull.auto_merge?.commit_title",
"currentPull.auto_merge?.commit_message",
"const skipRequested =",
"enabledBy !== unsafeOwner",
"disablePullRequestAutoMerge",
"cleanedPull.auto_merge",
"Cleared workflow-skipping auto-merge metadata",
"throw new Error",
} {
if !strings.Contains(cleanupScript, want) {
t.Errorf("unsafe-owner cleanup is missing contract marker %q", want)
}
}
if strings.Contains(cleanupScript, "enablePullRequestAutoMerge") {
t.Error("built-in cleanup token must never enable auto-merge")
}
if got := strings.Count(cleanupScript, "const skipWorkflowPattern ="); got != 1 {
t.Errorf("unsafe-request cleanup skip-workflow pattern declarations = %d, want exactly 1", got)
}
autoMergeStep := autoMergeJob.Steps[2]
if got, want := autoMergeStep.With["github-token"], "${{ steps.reviewer-router-token.outputs.token }}"; got != want {
t.Fatalf("auto-merge github-token = %q, want %q", got, want)
}
if got, want := autoMergeStep.Env["MINTED_REVIEWER_ROUTER_APP_SLUG"], "${{ steps.reviewer-router-token.outputs.app-slug }}"; got != want {
t.Fatalf("minted auto-merge App slug = %q, want %q", got, want)
}
if got, want := autoMergeStep.Env["REVIEWER_ROUTER_APP_SLUG"], "${{ vars.REVIEWER_ROUTER_APP_SLUG }}"; got != want {
t.Fatalf("configured auto-merge App slug = %q, want %q", got, want)
}
autoMergeScript := autoMergeStep.With["script"]
for _, want := range []string{
"github.rest.pulls.get",
"currentPull.head.sha !== eventHeadSha",
"currentPull.base.sha !== eventBaseSha",
"currentPull.state !== 'open'",
"currentPull.draft",
"currentPull.base.ref !== 'main'",
"getReadyEventPull('auto-merge enable')",
"process.env.MINTED_REVIEWER_ROUTER_APP_SLUG?.trim()",
"process.env.REVIEWER_ROUTER_APP_SLUG?.trim()",
"appSlug === 'github-actions'",
"mintedAppSlug !== appSlug",
"const expectedAppOwner = `${appSlug}[bot]`",
"GET /repos/{owner}/{repo}/rules/branches/{branch}",
".map(rule => Number(rule.ruleset_id))",
"Number.isSafeInteger(rulesetID) && rulesetID > 0",
"GET /repos/{owner}/{repo}/rulesets/{ruleset_id}",
"ruleset.enforcement !== 'active'",
"ruleset.target !== 'branch'",
"ruleset.name === 'main-merge-writers'",
"writerRuleset.source_type !== 'Repository'",
"writerRuleset.source?.toLowerCase() !== repositorySource",
"writerIncludes[0] !== 'refs/heads/main'",
"writerExcludes.length !== 0",
"writerRuleset.rules?.length !== 1",
"function isStrictUpdateRule(rule)",
"rule?.type !== 'update'",
"!Object.prototype.hasOwnProperty.call(rule, 'parameters')",
"parameters === null",
"typeof parameters !== 'object'",
"Array.isArray(parameters)",
"const parameterKeys = Object.keys(parameters)",
"parameterKeys.length === 1",
"parameterKeys[0] === 'update_allows_fetch_and_merge'",
"parameters.update_allows_fetch_and_merge === false",
"!isStrictUpdateRule(writerRuleset.rules[0])",
"typeof writerRuleset.node_id !== 'string'",
"function isStrictGraphQLUpdateRule(restRuleset, graphRuleset)",
"graphRule.parameters.updateAllowsFetchAndMerge === false",
"query ReviewerRouterWriterRule($rulesetID: ID!)",
"... on UpdateParameters",
"{rulesetID: writerRuleset.node_id}",
"!isStrictGraphQLUpdateRule(writerRuleset, graphWriterRuleset)",
"current_user_can_bypass !== 'pull_requests_only'",
"ruleset.current_user_can_bypass !== 'never'",
"Reviewer Router App must never bypass main ruleset",
"const skipWorkflowPattern =",
"const safeCommitHeadline = `Merge pull request #${pullNumber}`",
"const safeCommitBody =",
"workflow-skip cleanup",
"already has the reviewed ${expectedAppOwner} auto-merge request",
"preserving it",
"non-dedicated owner cleanup",
"unsafe dedicated-App metadata cleanup",
"getReadyEventPull('post-owner cleanup')",
"enablePullRequestAutoMerge",
"mergeMethod: MERGE",
"expectedHeadOid: $expectedHeadOid",
"commitHeadline: $commitHeadline",
"commitBody: $commitBody",
"expectedHeadOid: eventHeadSha",
"const exactRevision =",
"enabledBy === expectedAppOwner",
"disablePullRequestAutoMerge",
"unsafe-merge-message cleanup",
"enabledPull.auto_merge?.commit_title !== safeCommitHeadline",
"enabledPull.auto_merge?.commit_message !== safeCommitBody",
"if (revertedPull.auto_merge)",
"enabledBy !== expectedAppOwner",
} {
if !strings.Contains(autoMergeScript, want) {
t.Errorf("dedicated auto-merge script is missing contract marker %q", want)
}
}
if got := strings.Count(autoMergeScript, "const skipWorkflowPattern ="); got != 1 {
t.Errorf("dedicated auto-merge script skip-workflow pattern declarations = %d, want exactly 1", got)
}
for _, want := range []string{
"if (!exactRevision && enabledBy === expectedAppOwner)",
"disableAutoMerge(enabledPull, 'stale-enable cleanup')",
"existingOwner === expectedAppOwner &&",
"disableAutoMerge(currentPull, 'workflow-skip cleanup')",
"currentPull.auto_merge.commit_title === safeCommitHeadline",
"currentPull.auto_merge.commit_message === safeCommitBody",
"disableAutoMerge(enabledPull, 'unexpected owner cleanup')",
} {
if !strings.Contains(autoMergeScript, want) {
t.Errorf("dedicated App stale-state cleanup is missing %q", want)
}
}
for _, forbidden := range []string{"core.warning", "catch ("} {
if strings.Contains(autoMergeScript, forbidden) {
t.Errorf("dedicated auto-merge must fail closed instead of handling errors through %q", forbidden)
}
}
for _, forbidden := range []string{
"rule.ruleset_source_type",
"rule.ruleset_source?.toLowerCase()",
"ruleset.source_type !== 'Repository'",
"ruleset.source?.toLowerCase() !== repositorySource",
} {
if strings.Contains(autoMergeScript, forbidden) {
t.Errorf("dedicated App must inspect every applicable main ruleset, not prefilter through %q", forbidden)
}
}
reconcile, ok := workflow.Jobs["reconcile"]
if !ok {
t.Fatalf("workflow jobs = %v, want reconcile", workflow.Jobs)
}
if reconcile.If != "(github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' && github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli'" {
t.Fatalf("reconcile.if = %q, want exact protected-main manual guard", reconcile.If)
}
if len(reconcile.Steps) != 2 ||
reconcile.Steps[0].ID != "reviewer-router-reconcile-token" ||
reconcile.Steps[0].Uses != appTokenSHA ||
reconcile.Steps[1].Uses != githubScriptSHA {
t.Fatalf("reconcile steps = %#v, want scoped App token followed by pinned migration script", reconcile.Steps)
}
if len(reconcile.Permissions) != 0 {
t.Fatalf("reconcile built-in token permissions = %v, want none", reconcile.Permissions)
}
if len(reconcile.Steps[0].With) != 6 {
t.Fatalf("reconcile token inputs = %v, want exactly the reviewed repository scope and two permissions", reconcile.Steps[0].With)
}
for key, want := range map[string]string{
"client-id": "${{ vars.REVIEWER_ROUTER_APP_CLIENT_ID }}",
"private-key": "${{ secrets.REVIEWER_ROUTER_APP_PRIVATE_KEY }}",
"owner": "${{ github.repository_owner }}",
"repositories": "${{ github.event.repository.name }}",
"permission-contents": "write",
"permission-pull-requests": "write",
} {
if got := reconcile.Steps[0].With[key]; got != want {
t.Errorf("reconcile token input %q = %q, want %q", key, got, want)
}
}
if _, ok := reconcile.Steps[0].With["skip-token-revoke"]; ok {
t.Error("reconcile App token must be revoked automatically after the job")
}
if got, want := reconcile.Steps[1].With["github-token"], "${{ steps.reviewer-router-reconcile-token.outputs.token }}"; got != want {
t.Fatalf("reconcile github-token = %q, want %q", got, want)
}
if got, want := reconcile.Steps[1].Env["MINTED_REVIEWER_ROUTER_APP_SLUG"], "${{ steps.reviewer-router-reconcile-token.outputs.app-slug }}"; got != want {
t.Fatalf("minted reconcile App slug = %q, want %q", got, want)
}
if got, want := reconcile.Steps[1].Env["REVIEWER_ROUTER_APP_SLUG"], "${{ vars.REVIEWER_ROUTER_APP_SLUG }}"; got != want {
t.Fatalf("configured reconcile App slug = %q, want %q", got, want)
}
reconcileScript := reconcile.Steps[1].With["script"]
for _, want := range []string{
"github.paginate(github.rest.pulls.list",
"state: 'open'",
"base: 'main'",
"candidate.draft",
"const candidateOwner =",
"const candidateSkipRequested =",
"const candidateHasSafeAppRequest =",
"candidateOwner === expectedAppOwner",
"!candidate.auto_merge",
"candidateHasSafeAppRequest",
"currentPull.head.sha !== expectedHeadSha",
"currentPull.base.sha !== expectedBaseSha",
"currentPull.state !== 'open'",
"currentPull.draft",
"currentPull.base.ref !== 'main'",
"const currentHasSafeAppRequest =",
"currentOwner === expectedAppOwner",
"currentHasSafeAppRequest",
"async function disableAutoMerge(pullNumber, pullRequestId, phase)",
"disablePullRequestAutoMerge",
"if (revertedPull.auto_merge)",
"return revertedPull",
"replace non-dedicated auto-merge owner",
"repair dedicated-App merge metadata",
"enablePullRequestAutoMerge",
"mergeMethod: MERGE",
"const expectedAppOwner = `${appSlug}[bot]`",
"GET /repos/{owner}/{repo}/rules/branches/{branch}",
".map(rule => Number(rule.ruleset_id))",
"Number.isSafeInteger(rulesetID) && rulesetID > 0",
"GET /repos/{owner}/{repo}/rulesets/{ruleset_id}",
"ruleset.enforcement !== 'active'",
"ruleset.target !== 'branch'",
"ruleset.name === 'main-merge-writers'",
"writerRuleset.source_type !== 'Repository'",
"writerRuleset.source?.toLowerCase() !== repositorySource",
"writerIncludes[0] !== 'refs/heads/main'",
"writerExcludes.length !== 0",
"writerRuleset.rules?.length !== 1",
"function isStrictUpdateRule(rule)",
"rule?.type !== 'update'",
"!Object.prototype.hasOwnProperty.call(rule, 'parameters')",
"parameters === null",
"typeof parameters !== 'object'",
"Array.isArray(parameters)",
"const parameterKeys = Object.keys(parameters)",
"parameterKeys.length === 1",
"parameterKeys[0] === 'update_allows_fetch_and_merge'",
"parameters.update_allows_fetch_and_merge === false",
"!isStrictUpdateRule(writerRuleset.rules[0])",
"typeof writerRuleset.node_id !== 'string'",
"function isStrictGraphQLUpdateRule(restRuleset, graphRuleset)",
"graphRule.parameters.updateAllowsFetchAndMerge === false",
"query ReviewerRouterWriterRule($rulesetID: ID!)",
"... on UpdateParameters",
"{rulesetID: writerRuleset.node_id}",
"!isStrictGraphQLUpdateRule(writerRuleset, graphWriterRuleset)",
"current_user_can_bypass !== 'pull_requests_only'",
"ruleset.current_user_can_bypass !== 'never'",
"Reviewer Router App must never bypass main ruleset",
"const skipWorkflowPattern =",
"const skipRequested =",
"workflow-skip directive found in merge metadata; left manual-merge only",
"const safeCommitHeadline = `Merge pull request #${candidate.number}`",
"const safeCommitBody =",
"expectedHeadOid: $expectedHeadOid",
"commitHeadline: $commitHeadline",
"commitBody: $commitBody",
"expectedHeadOid: expectedHeadSha",
"enabledPull.auto_merge?.commit_title !== safeCommitHeadline",
"enabledPull.auto_merge?.commit_message !== safeCommitBody",
"const failures = []",
"catch (error)",
"failures.push(failure)",
"core.setFailed",
"revisionChanged && enabledBy === expectedAppOwner",
"enabledBy !== expectedAppOwner",
"unexpected owner cleanup",
"unsafe merge-message cleanup",
} {
if !strings.Contains(reconcileScript, want) {
t.Errorf("reconciliation script is missing contract marker %q", want)
}
}
if disableIndex, enableIndex := strings.Index(reconcileScript, "disablePullRequestAutoMerge"), strings.Index(reconcileScript, "enablePullRequestAutoMerge"); disableIndex < 0 || enableIndex <= disableIndex {
t.Error("legacy reconciliation must disable the exact unsafe owner before App-token enable")
}
if got := strings.Count(reconcileScript, "const skipWorkflowPattern ="); got != 1 {
t.Errorf("reconciliation script skip-workflow pattern declarations = %d, want exactly 1", got)
}
if strings.Contains(reconcileScript, "const unsafeOwner") {
t.Error("reconciliation must converge every non-App owner, not only the legacy built-in owner")
}
for _, forbidden := range []string{
"rule.ruleset_source_type",
"rule.ruleset_source?.toLowerCase()",
"ruleset.source_type !== 'Repository'",
"ruleset.source?.toLowerCase() !== repositorySource",
} {
if strings.Contains(reconcileScript, forbidden) {
t.Errorf("reconciliation App must inspect every applicable main ruleset, not prefilter through %q", forbidden)
}
}
for _, forbidden := range []string{
"['APPROVED', 'CHANGES_REQUESTED', 'COMMENTED']",
"PeterGuy326",
"core.setFailed",
"secrets.HOMEBREW_PR_TOKEN",
"secrets.RELEASE_GOVERNANCE_TOKEN",
"secrets.GITHUB_TOKEN",
} {
if strings.Contains(string(data), forbidden) {
t.Errorf("reviewer router must not contain %q", forbidden)
}
}
}
func TestCodeAdmissionEnforcesReviewerRouterWriterBoundary(t *testing.T) {
t.Parallel()
root, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("Abs(repo root) error = %v", err)
}
data, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "ci.yml"))
if err != nil {
t.Fatalf("ReadFile(ci.yml) error = %v", err)
}
workflow := string(data)
if !strings.Contains(workflow, "pull_request:\n types: [opened, synchronize, reopened, ready_for_review, edited, auto_merge_enabled, auto_merge_disabled]") {
t.Error("CI must rerun admission when a draft becomes ready or merge metadata changes")
}
start := strings.Index(workflow, "\n test:\n")
end := strings.Index(workflow, "\n test-darwin:\n")
if start < 0 || end <= start {
t.Fatal("CI workflow missing Test aggregate boundaries")
}
testJob := workflow[start:end]
for _, want := range []string{
"contents: read",
"pull-requests: read",
"name: Verify auto-merge identity",
"if: github.event_name == 'pull_request' && github.event.pull_request.draft == false",
"REVIEWER_ROUTER_APP_SLUG: ${{ vars.REVIEWER_ROUTER_APP_SLUG }}",
"process.env.REVIEWER_ROUTER_APP_SLUG?.trim()",
"const expectedAppOwner = `${appSlug}[bot]`",
"github.rest.pulls.get",
"github.rest.repos.get",
"function classifyMergeDefaults(repository)",
"mergeDefaultsProjection === 'invalid'",
"mergeDefaultsProjection === 'omitted'",
"exact validation is delegated to the dedicated App",
"const writerRulesetName = 'main-merge-writers'",
"GET /repos/{owner}/{repo}/rules/branches/{branch}",
"rule.ruleset_source_type === 'Repository'",
"rule.ruleset_source?.toLowerCase() === repositorySource",
"GET /repos/{owner}/{repo}/rulesets/{ruleset_id}",
"ruleset.enforcement !== 'active'",
"ruleset.target !== 'branch'",
"ruleset.source_type !== 'Repository'",
"ruleset.source?.toLowerCase() !== repositorySource",
"writerIncludes[0] !== 'refs/heads/main'",
"writerExcludes.length !== 0",
"writerRuleset.rules?.length !== 1",
"function isStrictUpdateRule(rule)",
"rule?.type !== 'update'",
"!Object.prototype.hasOwnProperty.call(rule, 'parameters')",
"parameters === null",
"typeof parameters !== 'object'",
"Array.isArray(parameters)",
"const parameterKeys = Object.keys(parameters)",
"parameterKeys.length === 1",
"parameterKeys[0] === 'update_allows_fetch_and_merge'",
"parameters.update_allows_fetch_and_merge === false",
"!isStrictUpdateRule(writerRuleset.rules[0])",
"typeof writerRuleset.node_id !== 'string'",
"function isStrictGraphQLUpdateRule(restRuleset, graphRuleset)",
"graphRule.parameters.updateAllowsFetchAndMerge === false",
"query ReviewerRouterWriterRule($rulesetID: ID!)",
"... on UpdateParameters",
"{rulesetID: writerRuleset.node_id}",
"!isStrictGraphQLUpdateRule(writerRuleset, graphWriterRuleset)",
"writerRuleset.current_user_can_bypass !== 'never'",
"deny this built-in Actions identity any bypass",
"const skipWorkflowPattern =",
"currentPull.title",
"currentPull.auto_merge?.commit_title",
"currentPull.auto_merge?.commit_message",
"merge metadata contains a GitHub workflow-skip directive",
"currentPull.head.sha !== eventHeadSha",
"currentPull.base.sha !== eventBaseSha",
"currentPull.state !== 'open'",
"currentPull.draft",
"currentPull.base.ref !== 'main'",
"currentPull.auto_merge",
"enabled_by?.login",
"enabledBy === expectedAppOwner",
"currentPull.auto_merge.commit_title === safeCommitHeadline",
"currentPull.auto_merge.commit_message === safeCommitBody",
"must be null or owned by ${expectedAppOwner}",
"const maxAttempts = 6",
"setTimeout(resolve, 5000)",
"core.setFailed",
} {
if !strings.Contains(testJob, want) {
t.Errorf("Test aggregate missing auto-merge identity contract %q", want)
}
}
if !strings.Contains(testJob, "Null and non-built-in merge identities emit either the protected-main") ||
!strings.Contains(testJob, "main-merge-writers never lets it update main") {
t.Error("auto-merge identity comment must name the push/repair paths and built-in writer self-check")
}
for _, forbidden := range []string{
"REVIEWER_ROUTER_APP_PRIVATE_KEY",
"HOMEBREW_PR_TOKEN",
"RELEASE_GOVERNANCE_TOKEN",
"can emit protected-main workflow events",
"pull-requests: write",
"contents: write",
"actions: write",
} {
if strings.Contains(testJob, forbidden) {
t.Errorf("read-only admission identity check must not consume %q", forbidden)
}
}
}