diff --git a/.github/agent-guides/catalogue.md b/.github/agent-guides/catalogue.md index 92c653d1..bb936b0b 100644 --- a/.github/agent-guides/catalogue.md +++ b/.github/agent-guides/catalogue.md @@ -26,6 +26,8 @@ with a `workflow_call` trigger. ### Layout +The unconsumed .NET workflow export is retired after native template enforcement and observed policy retirement (#703). Products call the shared `actions/run-dotnet-tests` action from their own matrix; historical immutable releases remain available. + The default-off `inspect-release-recovery.yaml` diagnoses only public GitHub release evidence; it cannot publish or repair channel notes. Its Go inspector lives in `.github/scripts/inspect-release-recovery/`. Run diff --git a/.github/agent-guides/workflows.md b/.github/agent-guides/workflows.md index 0bfde445..dcb1dc09 100644 --- a/.github/agent-guides/workflows.md +++ b/.github/agent-guides/workflows.md @@ -10,7 +10,7 @@ Choose the ownership boundary before adding a workflow. Organization-required wo PR-only, cross-product concerns such as scanning and linting. A reusable workflow needs more than one real job-level consumer; fixture-only calls do not establish that need. A single-product pipeline or reusable-workflow callsite belongs in its consuming repository, using local actions -for manageable step groups. Retired Pages, policy-sync and run-cleanup exports must not be +for manageable step groups. Retired Pages, policy-sync, .NET-test and run-cleanup exports must not be restored without a new ownership decision; immutable historical releases remain available. 1. **Use the `workflow_call` trigger** โ€” this is what makes them reusable. @@ -190,21 +190,15 @@ gated test jobs. Recognized action-owned paths narrow that set, including review transitive caller and inline-lockstep ownership. The cleanup workflow and its generated read-only projection have exact file ownership bound to all three native callers; their admission stays intact, and affected callers must succeed in the -required reducer. Ordinary regular-file modifications to `run-dotnet-tests.yaml` -select its normal native caller, hosted helper-checkout proof and credential -lockstep control. All four reviewed dependencies must retain exact ownership, -including the separately admitted authenticated caller; an unreviewed caller or -missing owner fails validation. Additions, deletions, type changes and unclassified -rename destinations keep full coverage. The authenticated caller's original -event/author admission remains outside mandatory execution selection. +required reducer. Ordinary regular-file modifications to the shared .NET action retain its native callers and credential controls. The authenticated caller keeps its independently admitted same-repository maintainer event boundary. The retired .NET workflow has no selective owner; restoring or changing a workflow keeps full coverage. Shared helpers, other workflows, tests, fixtures, root configuration and unknown paths retain full coverage; missing Git evidence fails the selector. Classification runs from the immutable trusted base before candidate checkout and selection on the same read-only runner; there is no separate prerequisite runner assignment. A successful trusted-base scope decision -for a deployment-only PR omits only the 48 explicitly inventoried smoke jobs; the +for a deployment-only PR omits only the 47 explicitly inventoried smoke jobs; the separate event-gated dependency-review smoke retains its scope condition. The other -31 selectable jobs and all independent shared guards remain available. Missing or +32 selectable jobs and all independent shared guards remain available. Missing or malformed scope output fails selection; unproven classification keeps full coverage. Renames retain both old and new owners. Main keeps full coverage, and existing merge-group/release exclusions, credential-specific and diff --git a/.github/imported-workflows.txt b/.github/imported-workflows.txt index 91219236..0c0bf247 100644 --- a/.github/imported-workflows.txt +++ b/.github/imported-workflows.txt @@ -8,7 +8,6 @@ enable-auto-merge.yaml lint.yaml publish-app.yaml publish-manifests.yaml -run-dotnet-tests.yaml scan-for-todo-comments.yaml scan-for-workflow-vulnerabilities.yaml sync-cluster-policies.yaml diff --git a/.github/scripts/ci-selection/dotnet_workflow_test.go b/.github/scripts/ci-selection/dotnet_workflow_test.go index e31de505..21e4f1cc 100644 --- a/.github/scripts/ci-selection/dotnet_workflow_test.go +++ b/.github/scripts/ci-selection/dotnet_workflow_test.go @@ -9,9 +9,9 @@ import ( "testing" ) -const dotnetSubject = ".github/workflows/run-dotnet-tests.yaml" +const dotnetSubject = "actions/run-dotnet-tests/action.yaml" -var dotnetSubjects = []string{"test-run-dotnet-tests-gate-lockstep", "test-run-dotnet-tests-mtp", "test-run-dotnet-tests-workflow", "test-run-dotnet-tests-workflow-authenticated"} +var dotnetSubjects = []string{"test-run-dotnet-tests", "test-run-dotnet-tests-authenticated", "test-run-dotnet-tests-gate-lockstep", "test-run-dotnet-tests-mtp"} func dotnetSelectionFixture(t *testing.T) (inventory, workflow) { t.Helper() @@ -22,12 +22,9 @@ func dotnetSelectionFixture(t *testing.T) (inventory, workflow) { } i.Preserved = map[string]job{} for _, id := range dotnetSubjects { - i.Jobs[id] = []string{dotnetSubject} + i.Jobs[id] = []string{"actions/run-dotnet-tests/"} j := job{If: selectionGuard(id), Needs: []any{"select-ci-tests"}} - if id == "test-run-dotnet-tests-workflow" || id == "test-run-dotnet-tests-workflow-authenticated" { - j.Uses = "./" + dotnetSubject - } - if id == "test-run-dotnet-tests-workflow-authenticated" { + if id == "test-run-dotnet-tests-authenticated" { j.If = "${{ trusted_same_repository_pull_request }}" j.Needs = nil i.Preserved[id] = job{If: j.If} @@ -79,19 +76,19 @@ func TestDotnetWorkflowOwnershipCannotOmitOrInventDependencies(t *testing.T) { case "unrelated owner": i.Jobs["test-one"] = append(i.Jobs["test-one"], dotnetSubject) case "duplicate owner": - i.Jobs[dotnetSubjects[0]] = append(i.Jobs[dotnetSubjects[0]], dotnetSubject) + i.Jobs[dotnetSubjects[0]] = append(i.Jobs[dotnetSubjects[0]], "actions/run-dotnet-tests/") case "changed caller": - j := w.Jobs["test-run-dotnet-tests-workflow"] + j := w.Jobs["test-run-dotnet-tests"] j.Uses = "./.github/workflows/other.yaml" - w.Jobs["test-run-dotnet-tests-workflow"] = j + w.Jobs["test-run-dotnet-tests"] = j case "unowned caller": j := w.Jobs["test-one"] j.Uses = "./" + dotnetSubject w.Jobs["test-one"] = j case "changed authenticated admission": - j := w.Jobs["test-run-dotnet-tests-workflow-authenticated"] + j := w.Jobs["test-run-dotnet-tests-authenticated"] j.If = "${{ true }}" - w.Jobs["test-run-dotnet-tests-workflow-authenticated"] = j + w.Jobs["test-run-dotnet-tests-authenticated"] = j } if _, err := validateInventory(i, w); err == nil { t.Fatalf("accepted %s", kind) @@ -107,7 +104,7 @@ func TestDotnetWorkflowUnknownAndSharedPathsKeepFullSelection(t *testing.T) { all = append(all, id) } sort.Strings(all) - for _, paths := range [][]string{{dotnetSubject + ".bak"}, {dotnetSubject + "/extra"}, {dotnetSubject, ".github/workflows/ci.yaml"}, {dotnetSubject, ".github/scripts/helper.sh"}} { + for _, paths := range [][]string{{"actions/run-dotnet-tests-extra/action.yaml"}, {".github/workflows/unknown.yaml"}, {dotnetSubject, ".github/workflows/ci.yaml"}, {dotnetSubject, ".github/scripts/helper.sh"}} { if got := selectJobs(i, "pull_request", paths); !reflect.DeepEqual(got, all) { t.Fatalf("incomplete fallback for %v: %v", paths, got) } @@ -151,7 +148,7 @@ func TestDotnetWorkflowGitStatusRetainsFullFallback(t *testing.T) { t.Fatal(err) } } - git("add", "--", ".github/workflows") + git("add", "--", "actions/run-dotnet-tests") git("commit", "-qm", "workflow change") paths, err := changedPaths(dir, base, git("rev-parse", "HEAD")) if err != nil { @@ -202,7 +199,7 @@ if [[ "$1" == diff && "$2" == --raw ]]; then "$CI_REAL_GIT" "$@" exit 17 fi - printf ':100644 100644 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb M\0.github/workflows/run-dotnet-tests.yaml' + printf ':100644 100644 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb M\0actions/run-dotnet-tests/action.yaml' exit 0 fi exec "$CI_REAL_GIT" "$@" diff --git a/.github/scripts/ci-selection/inventory.json b/.github/scripts/ci-selection/inventory.json index c260d1e9..8546b2b3 100644 --- a/.github/scripts/ci-selection/inventory.json +++ b/.github/scripts/ci-selection/inventory.json @@ -6,7 +6,7 @@ "test-enable-auto-merge-queue", "test-free-disk-space", "test-free-disk-space-keep", "test-gh-skill-install-script", "test-govulncheck-allowlist-honored", "test-govulncheck-strict-blocks", "test-govulncheck-timeout-retry-live", "test-guard-installed-skill-edits", "test-lint-blocks", "test-login-to-ghcr", "test-login-to-ghcr-empty-token", - "test-prepare-fixes", "test-retry-script", "test-run-dotnet-tests", "test-run-dotnet-tests-blocks", + "test-prepare-fixes", "test-retry-script", "test-run-dotnet-tests-blocks", "test-run-dotnet-tests-coverage-inline-lockstep", "test-run-dotnet-tests-gate-lockstep", "test-run-dotnet-tests-missing-working-directory", "test-run-dotnet-tests-preserves-source-mapping", "test-setup-agent-skills-inline", "test-setup-agent-skills-malformed-line", "test-setup-agent-skills-missing-input", @@ -44,7 +44,7 @@ "test-dependency-review": {"if": "${{ github.event_name != 'merge_group' && !startsWith(github.event.head_commit.message, 'chore(main): release ') && startsWith(github.event_name, 'pull_request') && needs.select-ci-tests.outputs.catalogue == 'true' }}", "needs": "select-ci-tests"}, "test-enable-auto-merge-on-pr": {"if": "github.event_name != 'merge_group' && !startsWith(github.event.head_commit.message, 'chore(main): release ') && startsWith(github.event_name, 'pull_request') && (\n github.event.pull_request.user.login == 'dependabot[bot]' ||\n github.event.pull_request.user.login == 'renovate[bot]' ||\n github.event.pull_request.user.login == 'botantler[bot]' ||\n github.event.pull_request.user.login == 'devantler'\n)"}, "test-zizmor": {"if": "${{ github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'chore(main): release ') }}"}, - "test-run-dotnet-tests-workflow-authenticated": {"if": "${{\n github.event_name != 'merge_group' &&\n !startsWith(github.event.head_commit.message, 'chore(main): release ') &&\n github.event_name == 'pull_request' &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.event.pull_request.user.login == 'devantler'\n}}"}, + "test-run-dotnet-tests-authenticated": {"if": "${{\n github.event_name != 'merge_group' &&\n !startsWith(github.event.head_commit.message, 'chore(main): release ') &&\n github.event_name == 'pull_request' &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.event.pull_request.user.login == 'devantler'\n}}"}, "test-publish-app": {"if": "${{ github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'chore(main): release ') }}"}, "test-publish-app-caller-pin": {"if": "${{ github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'chore(main): release ') }}"}, "test-publish-caller-pin-live": {"if": "${{ github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'chore(main): release ') }}"}, @@ -80,8 +80,8 @@ "test-run-dotnet-tests-preserves-source-mapping": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], "test-run-dotnet-tests-missing-working-directory": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], "test-run-dotnet-tests-blocks": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], - "test-run-dotnet-tests-mtp": ["actions/run-dotnet-tests/", "actions/upload-coverage/", ".github/workflows/run-dotnet-tests.yaml"], - "test-run-dotnet-tests-gate-lockstep": ["actions/run-dotnet-tests/", "actions/upload-coverage/", ".github/workflows/run-dotnet-tests.yaml"], + "test-run-dotnet-tests-mtp": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], + "test-run-dotnet-tests-gate-lockstep": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], "test-run-dotnet-tests-coverage-inline-lockstep": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], "test-setup-agent-skills-inline": ["actions/setup-agent-skills/"], "test-setup-agent-skills-pinned": ["actions/setup-agent-skills/"], @@ -115,8 +115,7 @@ "test-validate-go-project-apply-signed-fixes": ["actions/upload-coverage/"], "test-validate-go-project-test-default-branch": ["actions/upload-coverage/"], "test-validate-go-project-maintenance-default-branch": ["actions/upload-coverage/"], - "test-run-dotnet-tests-workflow": ["actions/run-dotnet-tests/", "actions/upload-coverage/", ".github/workflows/run-dotnet-tests.yaml"], - "test-run-dotnet-tests-workflow-authenticated": ["actions/run-dotnet-tests/", "actions/upload-coverage/", ".github/workflows/run-dotnet-tests.yaml"], + "test-run-dotnet-tests-authenticated": ["actions/run-dotnet-tests/", "actions/upload-coverage/"], "test-enable-auto-merge": [], "test-enable-auto-merge-actor-trust": [], "test-enable-auto-merge-queued": [], diff --git a/.github/scripts/ci-selection/main.go b/.github/scripts/ci-selection/main.go index 34ee123f..79b7f9f8 100644 --- a/.github/scripts/ci-selection/main.go +++ b/.github/scripts/ci-selection/main.go @@ -18,20 +18,20 @@ import ( const scheduling = "github.event_name != 'merge_group' && !startsWith(github.event.head_commit.message, 'chore(main): release ')" -const dotnetWorkflow = ".github/workflows/run-dotnet-tests.yaml" +const dotnetAction = "actions/run-dotnet-tests/action.yaml" const goCacheObserver = "test-go-cache-native-observer" const goCacheFixture = "test-validate-go-postgresql-fixture" var ordinarySelectiveFiles = map[string]bool{ - dotnetWorkflow: true, + dotnetAction: true, } // These include inline hosted and credential controls, not just reusable callers. var dotnetOwners = map[string]bool{ - "test-run-dotnet-tests-gate-lockstep": false, - "test-run-dotnet-tests-mtp": false, - "test-run-dotnet-tests-workflow": true, - "test-run-dotnet-tests-workflow-authenticated": true, + "test-run-dotnet-tests-gate-lockstep": false, + "test-run-dotnet-tests-mtp": false, + "test-run-dotnet-tests": false, + "test-run-dotnet-tests-authenticated": false, } type inventory struct { @@ -82,8 +82,7 @@ func validateInventory(i inventory, w workflow) ([]string, error) { } seen[id] = true for _, p := range paths { - if !regexp.MustCompile(`^actions/[a-z0-9-]+/$`).MatchString(p) && - (p != dotnetWorkflow || !hasDotnetOwner(id)) { + if !regexp.MustCompile(`^actions/[a-z0-9-]+/$`).MatchString(p) { return nil, fmt.Errorf("invalid selective owner %q", p) } } @@ -151,14 +150,9 @@ func validateGoCacheOwners(i inventory, w workflow) error { return nil } -func hasDotnetOwner(id string) bool { - _, ok := dotnetOwners[id] - return ok -} - func validateDotnetOwners(i inventory, w workflow) error { for id, j := range w.Jobs { - if j.Uses == "./"+dotnetWorkflow && !dotnetOwners[id] { + if j.Uses == "./"+dotnetAction && !dotnetOwners[id] { return fmt.Errorf("unreviewed .NET workflow caller %s", id) } } @@ -166,12 +160,12 @@ func validateDotnetOwners(i inventory, w workflow) error { j, ok := w.Jobs[id] count := 0 for _, p := range i.Jobs[id] { - if p == dotnetWorkflow { + if p == "actions/run-dotnet-tests/" { count++ } } - if !ok || count != 1 || caller && j.Uses != "./"+dotnetWorkflow || !caller && j.Uses != "" { - return fmt.Errorf("incomplete or unbound .NET workflow owner %s", id) + if !ok || count != 1 || caller && j.Uses != "./"+dotnetAction || !caller && j.Uses != "" { + return fmt.Errorf("incomplete or unbound .NET action owner %s", id) } } return nil diff --git a/.github/scripts/ci-selection/main_test.go b/.github/scripts/ci-selection/main_test.go index 9aaad23b..7f216110 100644 --- a/.github/scripts/ci-selection/main_test.go +++ b/.github/scripts/ci-selection/main_test.go @@ -239,7 +239,7 @@ func TestEntrypointBindsGitEvidenceAndSelectedOutput(t *testing.T) { fails bool }{ {"selective PR", "pull_request", "true", head, "selected=[\"test-one\",\"test-one-wrapper\"]\n", false}, - {"main retains complete inventory", "push", "true", "", "selected=[\"test-go-cache-native-observer\",\"test-one\",\"test-one-wrapper\",\"test-run-dotnet-tests-gate-lockstep\",\"test-run-dotnet-tests-mtp\",\"test-run-dotnet-tests-workflow\",\"test-two\",\"test-validate-go-postgresql-fixture\",\"test-workflow\"]\n", false}, + {"main retains complete inventory", "push", "true", "", "selected=[\"test-go-cache-native-observer\",\"test-one\",\"test-one-wrapper\",\"test-run-dotnet-tests\",\"test-run-dotnet-tests-gate-lockstep\",\"test-run-dotnet-tests-mtp\",\"test-two\",\"test-validate-go-postgresql-fixture\",\"test-workflow\"]\n", false}, {"excluded event", "merge_group", "false", "", "selected=[]\n", false}, {"missing immutable head", "pull_request", "true", strings.Repeat("a", 40), "", true}, {"unknown eligibility", "push", "", "", "", true}, @@ -307,8 +307,8 @@ func TestDeploymentCatalogueScopeComposesWithSelection(t *testing.T) { name, event, eligible, scope, want string fails bool }{ - {"deployment smoke omission only", "pull_request", "true", "false", "selected=[\"test-go-cache-native-observer\",\"test-one-wrapper\",\"test-run-dotnet-tests-gate-lockstep\",\"test-run-dotnet-tests-mtp\",\"test-run-dotnet-tests-workflow\",\"test-validate-go-postgresql-fixture\",\"test-workflow\"]\n", false}, - {"full scope", "pull_request", "true", "true", "selected=[\"test-go-cache-native-observer\",\"test-one\",\"test-one-wrapper\",\"test-run-dotnet-tests-gate-lockstep\",\"test-run-dotnet-tests-mtp\",\"test-run-dotnet-tests-workflow\",\"test-two\",\"test-validate-go-postgresql-fixture\",\"test-workflow\"]\n", false}, + {"deployment smoke omission only", "pull_request", "true", "false", "selected=[\"test-go-cache-native-observer\",\"test-one-wrapper\",\"test-run-dotnet-tests\",\"test-run-dotnet-tests-gate-lockstep\",\"test-run-dotnet-tests-mtp\",\"test-validate-go-postgresql-fixture\",\"test-workflow\"]\n", false}, + {"full scope", "pull_request", "true", "true", "selected=[\"test-go-cache-native-observer\",\"test-one\",\"test-one-wrapper\",\"test-run-dotnet-tests\",\"test-run-dotnet-tests-gate-lockstep\",\"test-run-dotnet-tests-mtp\",\"test-two\",\"test-validate-go-postgresql-fixture\",\"test-workflow\"]\n", false}, {"missing scope", "pull_request", "true", "", "", true}, {"malformed scope", "pull_request", "true", "unknown", "", true}, {"push cannot omit smoke", "push", "true", "false", "", true}, diff --git a/.github/tests/test-catalogue-credentials-controls.sh b/.github/tests/test-catalogue-credentials-controls.sh index 4ab13c39..fb72cd2d 100644 --- a/.github/tests/test-catalogue-credentials-controls.sh +++ b/.github/tests/test-catalogue-credentials-controls.sh @@ -57,7 +57,7 @@ restored skills writer .github/workflows/update-agent-skills.yaml del(.jobs["upd removed skipped dependency .github/workflows/update-agent-skills.yaml del(.jobs["open-skill-pull-requests"].needs) 1 write authority always restores skipped writer .github/workflows/update-agent-skills.yaml .jobs["open-skill-pull-requests"].if="${{ always() }}" 1 write authority restored auto merge .github/workflows/enable-auto-merge.yaml .jobs["disarm-untrusted-update"].if="${{ always() }}" 1 write authority -restored test package writer .github/workflows/run-dotnet-tests.yaml .jobs.test.permissions.packages="write" 1 write authority +restored test package writer .github/workflows/ci.yaml .jobs["test-run-dotnet-tests-mtp"].permissions.packages="write" 1 write authority restored application writer .github/workflows/publish-app.yaml del(.jobs.publish.if) 1 write authority restored manifest writer .github/workflows/publish-manifests.yaml del(.jobs["publish-manifests"].if) 1 write authority unknown condition on new writer .github/workflows/ci.yaml .jobs.new={"if":"${{ vars.MAYBE }}",permissions:{issues:"write"},steps:[{run:"echo fixture"}]} 1 write authority diff --git a/.github/tests/test-ci-catalogue-scope.sh b/.github/tests/test-ci-catalogue-scope.sh index 9d50a07a..0fdbd6b6 100644 --- a/.github/tests/test-ci-catalogue-scope.sh +++ b/.github/tests/test-ci-catalogue-scope.sh @@ -203,7 +203,7 @@ git -C "${current}" remote add origin "${current}" [[ "$(cat "${output}")" == catalogue=false ]] || fail 'real workflow did not recognize deployment-only changes' # Compose real Git classification, the production selector and the actual -# required reducer. A false scope omits 48 gated smoke jobs plus the separately +# required reducer. A false scope omits 47 gated smoke jobs plus the separately # event-gated dependency-review smoke, not the other 32 selectable jobs. Only # the retired policy-sync and three cleanup fixtures leave the previous floor. yq -o=json . "${ci}" >"${fixture}/workflow.json" @@ -232,8 +232,8 @@ expected_selected='[ "test-lint-workflow", "test-lint-workflow-manual-fixes", "test-release-please-semantics", + "test-run-dotnet-tests", "test-run-dotnet-tests-mtp", - "test-run-dotnet-tests-workflow", "test-setup-ksail-cli", "test-suite-default-branch-coverage", "test-suite-default-branch-coverage-blocks", @@ -252,7 +252,7 @@ jq -en --argjson actual "$selected" --argjson expected "$expected_selected" '$ac jq -e 'index("test-validate-go-postgresql-fixture") != null' <<<"${selected}" >/dev/null || fail 'native PostgreSQL caller left the independent coverage floor' jq -e 'index("test-go-cache-native-observer") != null' <<<"${selected}" >/dev/null || fail 'native Go cache observer left the independent coverage floor' jq -en --argjson selected "${selected}" --slurpfile inventory "${repo_root}/.github/scripts/ci-selection/inventory.json" ' - ($inventory[0].catalogue_optional | length == 48) and + ($inventory[0].catalogue_optional | length == 47) and all($selected[]; . as $id | ($inventory[0].catalogue_optional | index($id)) == null) and ($selected | index("test-approve-pr-credential-boundary") != null) and ($selected | index("test-validate-go-project-apply-signed-fixes") != null) diff --git a/.github/tests/test-ci-selection.sh b/.github/tests/test-ci-selection.sh index 8f9bf166..36d061b6 100644 --- a/.github/tests/test-ci-selection.sh +++ b/.github/tests/test-ci-selection.sh @@ -65,7 +65,7 @@ go -C "$root/.github/scripts/ci-selection" test -race -count=3 ./... go -C "$root/.github/scripts/ci-selection" vet ./... # Remove every .NET owner from both production inputs. This must fail before # selection, even though the remaining inventory still matches the workflow. -dotnet_owners='["test-run-dotnet-tests-gate-lockstep","test-run-dotnet-tests-mtp","test-run-dotnet-tests-workflow","test-run-dotnet-tests-workflow-authenticated"]' +dotnet_owners='["test-run-dotnet-tests-gate-lockstep","test-run-dotnet-tests-mtp","test-run-dotnet-tests","test-run-dotnet-tests-authenticated"]' jq --argjson owners "$dotnet_owners" 'del(.jobs[$owners[]])' "$work/workflow.json" > "$work/ownerless-workflow.json" jq --argjson owners "$dotnet_owners" 'del(.jobs[$owners[]], .preserved[$owners[]])' "$root/.github/scripts/ci-selection/inventory.json" > "$work/ownerless-inventory.json" if EVENT_NAME=push RUN_CATALOGUE=true CATALOGUE_SCOPE=true GITHUB_OUTPUT="$work/ownerless-output" \ @@ -73,13 +73,13 @@ if EVENT_NAME=push RUN_CATALOGUE=true CATALOGUE_SCOPE=true GITHUB_OUTPUT="$work/ echo 'FAIL: production entrypoint accepted removal of every required .NET owner' >&2 exit 1 fi -grep -q 'incomplete or unbound .NET workflow owner' "$work/ownerless.log" +grep -q 'incomplete or unbound .NET action owner' "$work/ownerless.log" [[ ! -e "$work/ownerless-output" ]] touch "$work/output" EVENT_NAME=push RUN_CATALOGUE=true CATALOGUE_SCOPE=true GITHUB_OUTPUT="$work/output" \ go -C "$root/.github/scripts/ci-selection" run . "$root" "$root/.github/scripts/ci-selection/inventory.json" "$work/workflow.json" selected="$(sed -n 's/^selected=//p' "$work/output")" -[[ "$(jq 'length' <<< "$selected")" == 80 ]] +[[ "$(jq 'length' <<< "$selected")" == 79 ]] jq -e 'index("test-validate-go-postgresql-fixture") != null' <<<"$selected" >/dev/null jq -e 'index("test-go-cache-native-observer") != null' <<<"$selected" >/dev/null # Hosted CI reports these callers as skipped because their callee jobs are @@ -137,25 +137,25 @@ jq -e --slurpfile inventory "$root/.github/scripts/ci-selection/inventory.json" # dependencies include two native callers and two inline controls; the authenticated # caller retains independent admission and is not promised as a mandatory execution. dotnet="$work/dotnet" -mkdir -p "$dotnet/.github/workflows" +mkdir -p "$dotnet/actions/run-dotnet-tests" git init -q "$dotnet" git -C "$dotnet" config user.name 'CI fixture' git -C "$dotnet" config user.email 'fixture@example.invalid' git -C "$dotnet" config commit.gpgsign false -printf 'base\n' > "$dotnet/.github/workflows/run-dotnet-tests.yaml" -git -C "$dotnet" add -- .github/workflows/run-dotnet-tests.yaml +printf 'base\n' > "$dotnet/actions/run-dotnet-tests/action.yaml" +git -C "$dotnet" add -- actions/run-dotnet-tests/action.yaml git -C "$dotnet" commit -qm 'test: dotnet baseline' base="$(git -C "$dotnet" rev-parse HEAD)" -printf 'changed\n' > "$dotnet/.github/workflows/run-dotnet-tests.yaml" -git -C "$dotnet" add -- .github/workflows/run-dotnet-tests.yaml +printf 'changed\n' > "$dotnet/actions/run-dotnet-tests/action.yaml" +git -C "$dotnet" add -- actions/run-dotnet-tests/action.yaml git -C "$dotnet" commit -qm 'test: dotnet change' head="$(git -C "$dotnet" rev-parse HEAD)" : > "$work/output" EVENT_NAME=pull_request RUN_CATALOGUE=true CATALOGUE_SCOPE=true BASE_SHA="$base" HEAD_SHA="$head" GITHUB_OUTPUT="$work/output" \ go -C "$root/.github/scripts/ci-selection" run . "$dotnet" "$root/.github/scripts/ci-selection/inventory.json" "$work/workflow.json" selected="$(sed -n 's/^selected=//p' "$work/output")" -jq -e '. == ["test-run-dotnet-tests-gate-lockstep","test-run-dotnet-tests-mtp","test-run-dotnet-tests-workflow"]' <<< "$selected" >/dev/null -needs="$(jq -cn --argjson selected "$selected" 'reduce $selected[] as $id ({"test-run-dotnet-tests-workflow-authenticated":{result:"skipped"},"test-enable-auto-merge-queue":{result:"skipped"}}; .[$id] = {result:"success"})')" +jq -e --slurpfile inventory "$root/.github/scripts/ci-selection/inventory.json" '. == ([$inventory[0].jobs | to_entries[] | select(any(.value[]; . == "actions/run-dotnet-tests/")) | .key | select($inventory[0].preserved[.] == null)] | sort)' <<< "$selected" >/dev/null +needs="$(jq -cn --argjson selected "$selected" 'reduce $selected[] as $id ({"test-run-dotnet-tests-authenticated":{result:"skipped"},"test-enable-auto-merge-queue":{result:"skipped"}}; .[$id] = {result:"success"})')" JOB_RESULTS='success skipped' CATALOGUE_REQUIRED=true SELECTOR_RESULT=success SELECTED_JOBS="$selected" NEEDS_JSON="$needs" run_reducer for caller in $(jq -r '.[]' <<< "$selected"); do for outcome in skipped failure; do @@ -166,7 +166,7 @@ for caller in $(jq -r '.[]' <<< "$selected"); do fi done done -bad_needs="$(jq '."test-run-dotnet-tests-workflow-authenticated".result="failure"' <<< "$needs")" +bad_needs="$(jq '."test-run-dotnet-tests-authenticated".result="failure"' <<< "$needs")" if JOB_RESULTS='success failure' CATALOGUE_REQUIRED=true SELECTOR_RESULT=success SELECTED_JOBS="$selected" NEEDS_JSON="$bad_needs" run_reducer >/dev/null 2>&1; then echo 'FAIL: independently admitted authenticated caller failure was concealed' >&2 exit 1 diff --git a/.github/tests/test-dotnet-helper-checkout.sh b/.github/tests/test-dotnet-helper-checkout.sh deleted file mode 100644 index a06f8105..00000000 --- a/.github/tests/test-dotnet-helper-checkout.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -# Exercise the workflow's real checkout settings with Git, then run the action's -# test command from a consumer root. A full helper checkout must reproduce #258. -set -euo pipefail -root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -workflow="${1:-$root/.github/workflows/run-dotnet-tests.yaml}" -mode="${2:-offline}" -work="$(mktemp -d)" -trap 'rm -rf "$work"' EXIT -fail() { echo "FAIL: $*" >&2; exit 1; } -[[ "$mode" == offline || "$mode" == hosted ]] || fail 'unknown test mode' - -checkout="$(yq -o=json '.jobs.test.steps[] | select(.with.path == ".devantler-tech-actions") | .with' "$workflow")" -[[ -n "$checkout" ]] || fail 'helper checkout is missing' -patterns="$(jq -r '."sparse-checkout" // empty' <<<"$checkout")" -cone="$(jq -r 'if has("sparse-checkout-cone-mode") then .["sparse-checkout-cone-mode"] else true end' <<<"$checkout")" -yq -er '.runs.steps[] | select(.name == "๐Ÿงช Test") | .run' "$root/actions/run-dotnet-tests/action.yaml" >"$work/test.sh" -yq -er '.runs.steps[] | select(.name == "๐Ÿ”€ Merge coverage into a single Cobertura report") | .run' \ - "$root/actions/run-dotnet-tests/action.yaml" >"$work/merge.sh" - -mkdir -p "$work/bin" -cat >"$work/bin/dotnet" <<'SH' -#!/usr/bin/env bash -set -euo pipefail -[[ "$*" == 'test --no-restore' ]] || { echo "unexpected coverage arguments: $*" >&2; exit 1; } -printf 'credential-free tests without coverage\n' > test-completed -SH -chmod +x "$work/bin/dotnet" - -for fixture in run-dotnet-tests-mtp-no-coverage run-dotnet-tests; do - consumer="$work/$fixture" - mkdir -p "$consumer" - cp -R "$root/.github/fixtures/$fixture/." "$consumer/" - helper="$consumer/.devantler-tech-actions" - git clone --quiet --no-checkout --shared "$root" "$helper" - if [[ -n "$patterns" ]]; then - if [[ "$cone" == true ]]; then sparse_mode=--cone; else sparse_mode=--no-cone; fi - printf '%s\n' "$patterns" | git -C "$helper" sparse-checkout set "$sparse_mode" --stdin - fi - git -C "$helper" checkout --quiet --detach HEAD - [[ -f "$helper/actions/run-dotnet-tests/action.yaml" ]] || fail 'needed action was omitted' - - if [[ "$fixture" == run-dotnet-tests-mtp-no-coverage ]]; then - if [[ "$mode" == hosted ]]; then - (cd "$consumer" && dotnet restore && bash -eo pipefail "$work/test.sh") - else - (cd "$consumer" && PATH="$work/bin:$PATH" bash -eo pipefail "$work/test.sh") || - fail 'helper fixture enabled unsupported coverage for the consumer' - [[ -f "$consumer/test-completed" ]] || fail 'consumer tests did not execute' - fi - echo 'PASS: root consumer without the extension runs without coverage' - elif [[ "$mode" == hosted ]]; then - (cd "$consumer" && dotnet restore && bash -eo pipefail "$work/test.sh" && bash -eo pipefail "$work/merge.sh") - report="$consumer/coverage-merged/Cobertura.xml" - [[ -s "$report" ]] || fail 'consumer coverage was not merged' - grep -q 'Example' "$report" || fail 'consumer coverage is missing' - if grep -q 'sample.go' "$report"; then fail 'catalogue coverage polluted the consumer report'; fi - echo 'PASS: real consumer coverage merges without catalogue coverage' - fi - - contamination="$(find "$helper" -type f \( -name '*.csproj' -o -name '*.props' -o -name '*.targets' -o -name '*.cobertura.xml' \) -print)" - [[ -z "$contamination" ]] || fail 'helper checkout contains unrelated projects or coverage reports' - echo "PASS: $fixture helper contains no scan inputs" -done diff --git a/.github/tests/test-workflow-readme-parity.sh b/.github/tests/test-workflow-readme-parity.sh index 36d33caa..5525d53b 100644 --- a/.github/tests/test-workflow-readme-parity.sh +++ b/.github/tests/test-workflow-readme-parity.sh @@ -57,7 +57,7 @@ rm "$repo/scripts/import-actions-catalogue.sh" printf '\n\n' >> "$repo/README.md" expect 'historical generation marker cannot misrepresent ownership' 2 'historical generated marker' -for retired in delete-workflow-runs delete-workflow-runs-readonly deploy-github-pages sync-cluster-policies; do +for retired in delete-workflow-runs delete-workflow-runs-readonly deploy-github-pages sync-cluster-policies run-dotnet-tests; do for extension in yaml yml; do fixture printf 'on: {workflow_call: {}}\njobs: {}\n' >"$repo/.github/workflows/$retired.$extension" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5702860e..2be97f68 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1253,10 +1253,6 @@ jobs: fi echo "Tests ran on Microsoft.Testing.Platform without the coverage extension โœ…" - - name: ๐Ÿงช Exercise consumer-root testing with the workflow helper checkout - if: runner.os == 'Linux' - shell: bash - run: bash .github/tests/test-dotnet-helper-checkout.sh .github/workflows/run-dotnet-tests.yaml hosted test-run-dotnet-tests-preserves-source-mapping: needs: [select-ci-tests] @@ -3099,62 +3095,6 @@ jobs: fi echo "Credential-free tests cannot reach the token-bearing coverage upload โœ…" - - name: ๐Ÿ”’ Assert the authenticated opt-in rejects fork and bot pull requests - shell: bash - env: - WORKFLOW: .github/workflows/run-dotnet-tests.yaml - run: | - set -euo pipefail - token_expr="$(yq -r '.jobs.test.steps[] | select(.name == "๐Ÿงช Test .NET solution or project") | .with.github-token' "$WORKFLOW")" - for required_guard in \ - "inputs.enable-github-packages" \ - "github.event.pull_request.head.repo.full_name" \ - "github.repository" \ - "github.event.pull_request.user.login" \ - "[bot]"; do - if [[ "$token_expr" != *"$required_guard"* ]]; then - echo "::error::$WORKFLOW authenticated pull-request path is missing the '$required_guard' trust guard" - exit 1 - fi - done - if [[ "$token_expr" == *"github.actor"* ]]; then - echo "::error::$WORKFLOW must trust the immutable pull-request author, not the actor who triggered this event" - exit 1 - fi - - for source_step in \ - "๐Ÿ”Ž Snapshot authenticated GitHub Packages source" \ - "๐Ÿ”’ Verify authenticated GitHub Packages source state is preserved"; do - source_if="$(SOURCE_STEP="$source_step" yq -r '.jobs.test.steps[] | select(.name == strenv(SOURCE_STEP)) | .if' "$WORKFLOW")" - for required_guard in \ - "inputs.enable-github-packages" \ - "github.event.pull_request.head.repo.full_name" \ - "github.repository" \ - "github.event.pull_request.user.login" \ - "[bot]"; do - if [[ "$source_if" != *"$required_guard"* ]]; then - echo "::error::$WORKFLOW '$source_step' is missing the '$required_guard' trust guard" - exit 1 - fi - done - if [[ "$source_if" == *"github.actor"* ]]; then - echo "::error::$WORKFLOW '$source_step' must trust the pull-request author, not the event actor" - exit 1 - fi - done - - verify_run="$(yq -r '.jobs.test.steps[] | select(.name == "๐Ÿ”’ Verify authenticated GitHub Packages source state is preserved") | .run' "$WORKFLOW")" - verify_before_state="$(yq -r '.jobs.test.steps[] | select(.name == "๐Ÿ”’ Verify authenticated GitHub Packages source state is preserved") | .env.BEFORE_STATE' "$WORKFLOW")" - expected_comparison="if [[ \"\$after_state\" != \"\$BEFORE_STATE\" ]]; then" - if [[ "$verify_run" != *'after_state'* || "$verify_run" != *"$expected_comparison"* ]]; then - echo "::error::$WORKFLOW authenticated-source assertion must compare the post-action state with its snapshot" - exit 1 - fi - if [[ "$verify_before_state" != *"steps.github-source-before.outputs.state"* ]]; then - echo "::error::$WORKFLOW authenticated-source assertion must consume the pre-action snapshot" - exit 1 - fi - echo "Only an explicit same-repository non-bot pull request can receive the ephemeral restore token, and its source state is preserved โœ…" # run-dotnet-tests INLINES upload-coverage's single pinned step (a composite action # cannot `uses:` its own repo at the same commit), so nothing structural ties the two @@ -3247,9 +3187,6 @@ jobs: shell: bash run: bash .github/tests/test-shell-pipeline-adoption.sh - - name: ๐Ÿงช Verify the .NET workflow helper cannot contaminate consumer scans - shell: bash - run: bash .github/tests/test-dotnet-helper-checkout.sh # โ”€โ”€ lint-readme-parity โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ lint-readme-parity: @@ -3963,21 +3900,7 @@ jobs: pull-requests: read code-quality: write - test-run-dotnet-tests-workflow: - needs: [select-ci-tests] - if: "${{ github.event_name != 'merge_group' && !startsWith(github.event.head_commit.message, 'chore(main): release ') && contains(fromJSON(needs.select-ci-tests.outputs.selected), 'test-run-dotnet-tests-workflow') }}" - name: "[Test] Run .NET Tests" - uses: ./.github/workflows/run-dotnet-tests.yaml - # Fixture-targeted: exercise the workflow against the known-good .NET fixture - # (this repo is not itself a .NET project). - with: - working-directory: .github/fixtures/run-dotnet-tests - permissions: - contents: read - packages: read - code-quality: write # callers must grant this for the coverage upload (GitHub Code Quality) - - test-run-dotnet-tests-workflow-authenticated: + test-run-dotnet-tests-authenticated: if: >- ${{ github.event_name != 'merge_group' && @@ -3987,15 +3910,78 @@ jobs: github.event.pull_request.user.login == 'devantler' }} name: "[Test] Run .NET Tests - Authenticated Packages" - uses: ./.github/workflows/run-dotnet-tests.yaml - # Exercise the opt-in state only for the maintainer-authored code from this repository. - with: - working-directory: .github/fixtures/run-dotnet-tests - enable-github-packages: true + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] permissions: contents: read packages: read - code-quality: write # callers must grant this for the coverage upload (GitHub Code Quality) + code-quality: write + steps: + - name: ๐Ÿ›ก๏ธ Harden runner + uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + with: + egress-policy: audit + + - name: ๐Ÿ“‘ Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: ๐Ÿ”Ž Snapshot authenticated GitHub Packages source + id: github-source-before + working-directory: .github/fixtures/run-dotnet-tests + shell: bash + run: | + set -euo pipefail + source_listing="$(dotnet nuget list source --format Detailed)" + github_source_state="$( + awk ' + tolower($0) ~ /^[[:space:]]*[0-9]+\.[[:space:]]+github[[:space:]]+\[(enabled|disabled)\][[:space:]]*$/ { + status = $0 + getline + sub(/^[[:space:]]+/, "") + print status "|" $0 + exit + } + ' <<<"$source_listing" + )" + echo "state=$github_source_state" >>"$GITHUB_OUTPUT" + + - name: ๐Ÿงช Test .NET solution or project + uses: ./actions/run-dotnet-tests + with: + # The job admits only same-repository maintainer pull requests. + # Other native .NET fixtures remain credential-free on pull requests. + github-token: ${{ secrets.GITHUB_TOKEN }} + working-directory: .github/fixtures/run-dotnet-tests + + - name: ๐Ÿ”’ Verify authenticated GitHub Packages source state is preserved + working-directory: .github/fixtures/run-dotnet-tests + env: + BEFORE_STATE: ${{ steps.github-source-before.outputs.state }} + shell: bash + run: | + set -euo pipefail + source_listing="$(dotnet nuget list source --format Detailed)" + after_state="$( + awk ' + tolower($0) ~ /^[[:space:]]*[0-9]+\.[[:space:]]+github[[:space:]]+\[(enabled|disabled)\][[:space:]]*$/ { + status = $0 + getline + sub(/^[[:space:]]+/, "") + print status "|" $0 + exit + } + ' <<<"$source_listing" + )" + if [[ "$after_state" != "$BEFORE_STATE" ]]; then + echo "::error::Authenticated restore changed the caller's GitHub Packages source state" + exit 1 + fi + echo "Authenticated GitHub Packages source state was preserved โœ…" + test-enable-auto-merge: if: "${{ github.event_name != 'merge_group' && !startsWith(github.event.head_commit.message, 'chore(main): release ') }}" @@ -6303,8 +6289,7 @@ jobs: - test-validate-go-project-apply-signed-fixes - test-validate-go-project-test-default-branch - test-validate-go-project-maintenance-default-branch - - test-run-dotnet-tests-workflow - - test-run-dotnet-tests-workflow-authenticated + - test-run-dotnet-tests-authenticated - test-enable-auto-merge - test-enable-auto-merge-actor-trust - test-enable-auto-merge-queued @@ -6439,8 +6424,7 @@ jobs: ${{ needs.test-validate-go-project-apply-signed-fixes.result }} ${{ needs.test-validate-go-project-test-default-branch.result }} ${{ needs.test-validate-go-project-maintenance-default-branch.result }} - ${{ needs.test-run-dotnet-tests-workflow.result }} - ${{ needs.test-run-dotnet-tests-workflow-authenticated.result }} + ${{ needs.test-run-dotnet-tests-authenticated.result }} ${{ needs.test-enable-auto-merge.result }} ${{ needs.test-enable-auto-merge-actor-trust.result }} ${{ needs.test-enable-auto-merge-queued.result }} diff --git a/.github/workflows/run-dotnet-tests.yaml b/.github/workflows/run-dotnet-tests.yaml deleted file mode 100644 index d056ebe8..00000000 --- a/.github/workflows/run-dotnet-tests.yaml +++ /dev/null @@ -1,145 +0,0 @@ -name: ๐Ÿงช Run .NET Tests - -permissions: {} - -on: - workflow_call: - inputs: - enable-github-packages: - required: false - type: boolean - default: false - description: "Allow authenticated package restore and Code Quality upload for trusted same-repository pull-request code." - working-directory: - required: false - type: string - default: "" - description: "Directory containing the .NET solution/project to test. Empty = repo root (the normal consumer case); the CI self-test sets it to a fixture so this workflow can be exercised in a repo that is not itself a .NET project." - ### Required Workflow Triggers ### - pull_request: - merge_group: - ################################## - -jobs: - test: - permissions: - contents: read - packages: read - # Required for the GitHub Code Quality coverage upload. Callers of this reusable workflow - # must also grant `code-quality: write`, or the run fails at startup. - code-quality: write - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - # Run for consumer calls (their repo) and for the fixture-targeted CI self-test - # (working-directory set). Skip a bare standalone fire in THIS repo, which has no - # .NET project at its root. - if: ${{ inputs.working-directory != '' || github.repository != 'devantler-tech/.github' }} - steps: - - name: ๐Ÿ›ก๏ธ Harden runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 - with: - egress-policy: audit - - - name: ๐Ÿ“‘ Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - # Self-reference: check out THIS workflow's own repo at the exact commit it is running - # from (job.workflow_repository / job.workflow_sha), then call the sibling action by - # local path. Same-commit resolution โ€” no release lag, no tag refs; consumer SHA-pinning - # policies see only the pinned actions/checkout plus a local `./` path. - - name: ๐Ÿ“ฅ Checkout devantler-tech/.github (this workflow's commit) - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - repository: ${{ job.workflow_repository }} - ref: ${{ job.workflow_sha }} - path: .devantler-tech-actions - # Consumer-root scans must never see the catalogue's projects or coverage fixtures. - sparse-checkout: /actions/run-dotnet-tests/ - sparse-checkout-cone-mode: false - persist-credentials: false - - - name: ๐Ÿ”Ž Snapshot authenticated GitHub Packages source - id: github-source-before - if: >- - ${{ - github.event_name != 'pull_request' || - ( - inputs.enable-github-packages && - github.event.pull_request.head.repo.full_name == github.repository && - !endsWith(github.event.pull_request.user.login, '[bot]') - ) - }} - working-directory: ${{ inputs.working-directory || '.' }} - shell: bash - run: | - set -euo pipefail - source_listing="$(dotnet nuget list source --format Detailed)" - github_source_state="$( - awk ' - tolower($0) ~ /^[[:space:]]*[0-9]+\.[[:space:]]+github[[:space:]]+\[(enabled|disabled)\][[:space:]]*$/ { - status = $0 - getline - sub(/^[[:space:]]+/, "") - print status "|" $0 - exit - } - ' <<<"$source_listing" - )" - echo "state=$github_source_state" >>"$GITHUB_OUTPUT" - - - name: ๐Ÿงช Test .NET solution or project - uses: ./.devantler-tech-actions/actions/run-dotnet-tests - with: - # Pull requests are credential-free by default. A trusted workflow_call caller may - # opt in only for same-repository, non-bot code. Fork and bot pull requests cannot - # recover the token by changing the input in their workflow definition. - github-token: >- - ${{ - ( - github.event_name != 'pull_request' || - ( - inputs.enable-github-packages && - github.event.pull_request.head.repo.full_name == github.repository && - !endsWith(github.event.pull_request.user.login, '[bot]') - ) - ) && secrets.GITHUB_TOKEN || '' - }} - working-directory: ${{ inputs.working-directory || '.' }} - - - name: ๐Ÿ”’ Verify authenticated GitHub Packages source state is preserved - if: >- - ${{ - github.event_name != 'pull_request' || - ( - inputs.enable-github-packages && - github.event.pull_request.head.repo.full_name == github.repository && - !endsWith(github.event.pull_request.user.login, '[bot]') - ) - }} - working-directory: ${{ inputs.working-directory || '.' }} - env: - BEFORE_STATE: ${{ steps.github-source-before.outputs.state }} - shell: bash - run: | - set -euo pipefail - source_listing="$(dotnet nuget list source --format Detailed)" - after_state="$( - awk ' - tolower($0) ~ /^[[:space:]]*[0-9]+\.[[:space:]]+github[[:space:]]+\[(enabled|disabled)\][[:space:]]*$/ { - status = $0 - getline - sub(/^[[:space:]]+/, "") - print status "|" $0 - exit - } - ' <<<"$source_listing" - )" - if [[ "$after_state" != "$BEFORE_STATE" ]]; then - echo "::error::Authenticated restore changed the caller's GitHub Packages source state" - exit 1 - fi - echo "Authenticated GitHub Packages source state was preserved โœ…" diff --git a/.scripts/check-workflow-readme.sh b/.scripts/check-workflow-readme.sh index fa8e40e8..a2b9325f 100644 --- a/.scripts/check-workflow-readme.sh +++ b/.scripts/check-workflow-readme.sh @@ -18,7 +18,7 @@ if grep -Fq '