Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/agent-guides/catalogue.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 4 additions & 10 deletions .github/agent-guides/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion .github/imported-workflows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 12 additions & 15 deletions .github/scripts/ci-selection/dotnet_workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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" "$@"
Expand Down
11 changes: 5 additions & 6 deletions .github/scripts/ci-selection/inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 ') }}"},
Expand Down Expand Up @@ -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/"],
Expand Down Expand Up @@ -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": [],
Expand Down
28 changes: 11 additions & 17 deletions .github/scripts/ci-selection/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -151,27 +150,22 @@ 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)
}
}
for id, caller := range dotnetOwners {
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
Expand Down
6 changes: 3 additions & 3 deletions .github/scripts/ci-selection/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion .github/tests/test-catalogue-credentials-controls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading