Repository navigation
feat: implement issue #397 — Compliance: copilot-setup-steps-invalid-job-name #483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
73c842d
feat: implement issue #397 — Compliance: copilot-setup-steps-invalid-…
donpetry-bot 7368dea
chore: apply manual instructions [skip ci-relay]
donpetry-bot f3390b7
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 3cb7e6b
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 4b53701
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry a28edba
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ea67f15
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry c4b56d5
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 02a9c7e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry c6bdf46
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ffb06e2
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry cba5b1a
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 6834cce
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry e1a4004
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 79721da
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 3a519d1
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry f808644
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 378132b
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 1c1d562
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 99ad8c9
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 9b26a8c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry e153459
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 28de899
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 97d11ac
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 024739a
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 93decf8
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 143546e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 55cabe6
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry b16d63c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry cb03d50
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry b57974c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 2b8f009
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 454d7c8
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry b9cd5a3
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 1e2a048
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry a12b91f
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ff95072
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ebe1191
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 218ca45
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 14f5474
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 5324b43
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry a7e7795
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 7e88ef5
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry a4aaf09
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ea97144
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 5e7b868
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry b35e412
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 5d6890e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 1fa073e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 0d1411e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 72099b4
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ce859e6
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ca605fe
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 717a3a9
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry c1c3350
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry adb5ef2
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry af06ae0
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] d8f068b
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] eddc5bd
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] c0e8288
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] 8d31755
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] 392575d
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry dbefd2f
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 6a40497
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry c1d7a91
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry f04d036
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry c03d818
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry d608479
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry f749492
chore: apply manual instructions [skip ci-relay]
donpetry-bot a53a49c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 0afa74a
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry d0da149
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry b1bf9ec
Merge branch 'main' into dev-lead/issue-397-20260608-0049
donpetry-bot d9a60cf
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry ad005da
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry 74bb0b7
fix(reviews): address review comments [skip ci-relay]
donpetry-bot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # ───────────────────────────────────────────────────────────────────────────── | ||
| # SOURCE OF TRUTH: petry-projects/.github/standards/workflows/copilot-setup-steps.yml | ||
| # Standard: petry-projects/.github/standards/ci-standards.md#11-copilot-cloud-agent-setup | ||
| # | ||
| # ADOPTING THIS WORKFLOW: | ||
| # 1. Copy this file to .github/workflows/copilot-setup-steps.yml in your repo. | ||
| # 2. Keep every REQUIRED section — do NOT remove the checkout or verify steps. | ||
| # 3. Uncomment and adapt the stack blocks that match your repo's tech stack. | ||
| # 4. Delete (not just comment out) stacks that do not apply after initial setup. | ||
| # 5. Merge to the default branch — the workflow only triggers from the default branch. | ||
| # 6. Run manually from Actions → "Copilot Setup Steps" → "Run workflow" to verify. | ||
| # | ||
| # WHAT THIS FILE DOES: | ||
| # Bootstraps Copilot cloud agent's ephemeral environment BEFORE the agent starts | ||
| # working on your repo. Pre-installing dependencies deterministically: | ||
| # • Speeds up every agent session (no trial-and-error dependency discovery) | ||
| # • Makes private/internal packages available (impossible for the agent alone) | ||
| # • Ensures exact tool versions that match your CI pipeline | ||
| # Without this file the agent installs dependencies itself — slower, non-deterministic, | ||
| # and unreliable for repos with private packages or complex build graphs. | ||
| # | ||
| # SEE ALSO: | ||
| # AGENTS.md — authoritative development standards for this repo | ||
| # .github/copilot-instructions.md — always-on Copilot instructions (summary of AGENTS.md) | ||
| # .github/instructions/ — path-scoped instruction files per language | ||
| # | ||
| # CONSTRAINTS (enforced by GitHub, documented at docs.github.com): | ||
| # • Job MUST be named `copilot-setup-steps` to be recognized by Copilot cloud agent | ||
| # • timeout-minutes maximum: 59 (hard limit) | ||
| # • Customizable fields: steps, permissions, runs-on, services, snapshot, timeout-minutes | ||
| # • All other job-level settings are silently ignored by GitHub | ||
| # • fetch-depth on checkout is always overridden by the agent — do not rely on it here | ||
| # • This file MUST be present on the default branch to take effect | ||
| # | ||
| # AGENTS — READ BEFORE EDITING: | ||
| # This file is a PER-REPO TEMPLATE (Tier 2). There is no central reusable workflow | ||
| # because setup steps are inherently tech-stack-specific. Each repo owns its copy. | ||
| # Stay within the patterns documented in ci-standards.md §11. Do not change: | ||
| # • the job name (`copilot-setup-steps`) | ||
| # • the fork guard condition | ||
| # • the trigger events | ||
| # • the verify-environment step | ||
| # For changes to the org-wide pattern, open a PR against this file in | ||
| # petry-projects/.github and propagate to the fleet. | ||
| # ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| name: "Copilot Setup Steps" | ||
|
|
||
| # Run automatically when this file changes (validates the setup steps work), | ||
| # and allow manual runs from the Actions tab at any time. | ||
| # NOT a standard CI trigger — this file should NOT be added to push: branches: [main]. | ||
| on: | ||
| workflow_dispatch: | ||
| push: | ||
| paths: | ||
| - .github/workflows/copilot-setup-steps.yml | ||
| pull_request: | ||
| paths: | ||
| - .github/workflows/copilot-setup-steps.yml | ||
|
|
||
| # Defense-in-depth: reset top-level permissions; set exact grants per-job. | ||
| # Pattern from envoyproxy/envoy and the org Permissions Policy (ci-standards.md). | ||
| permissions: {} | ||
|
|
||
| jobs: | ||
| # ── REQUIRED: Job name must be exactly `copilot-setup-steps` ──────────────── | ||
| copilot-setup-steps: | ||
| # Skip on fork-origin pull requests — forks cannot access org secrets required for private packages. | ||
| # `github.event.repository.fork` reflects the base repo (always false for org repos); the | ||
| # correct guard is on the PR head repo so that external fork PRs are skipped while direct | ||
| # pushes and manual runs always proceed. | ||
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false | ||
|
|
||
| runs-on: ubuntu-latest | ||
|
|
||
| # Adjust upward only if your build or download steps genuinely need more time. | ||
| # Hard maximum enforced by GitHub is 59 minutes. | ||
| timeout-minutes: 30 | ||
|
|
||
| # Minimum permissions — Copilot receives its own separate token for its operations. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| # ── REQUIRED ────────────────────────────────────────────────────────────── | ||
| - name: Checkout code | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| # ── REQUIRED ────────────────────────────────────────────────────────────── | ||
| # Environment verification — always runs last. Fails loud if something is | ||
| # missing so the problem is caught here rather than mid-agent-session. | ||
| # Also surfaces the agent instruction files so session logs confirm they | ||
| # were found before the agent began working. | ||
| - name: Verify environment | ||
| run: | | ||
| echo "=== petry-projects Copilot cloud agent environment ===" | ||
| echo "Repository : ${GITHUB_REPOSITORY}" | ||
| echo "Ref : ${GITHUB_REF}" | ||
| echo "Runner : ${RUNNER_OS} / ${RUNNER_ARCH}" | ||
| echo "" | ||
| echo "--- Installed tool versions ---" | ||
| git --version | ||
| gh --version 2>/dev/null | head -1 || echo "gh: not installed" | ||
| node --version 2>/dev/null || echo "node: not installed" | ||
| npm --version 2>/dev/null || echo "npm: not installed" | ||
| go version 2>/dev/null || echo "go: not installed" | ||
| python --version 2>/dev/null || echo "python: not installed" | ||
| echo "" | ||
| echo "--- Agent instruction files ---" | ||
| if [ -f "AGENTS.md" ]; then | ||
| echo "✅ AGENTS.md ($(wc -l < AGENTS.md) lines)" | ||
| else | ||
| echo "❌ AGENTS.md — MISSING. Add this file per agent-standards.md." | ||
| exit 1 | ||
| fi | ||
| if [ -f ".github/copilot-instructions.md" ]; then | ||
| echo "✅ .github/copilot-instructions.md" | ||
| else | ||
| echo "ℹ️ .github/copilot-instructions.md — not present (optional but recommended)" | ||
| fi | ||
| # Count path-scoped instruction files (guard against missing directory) | ||
| INSTR_COUNT=0 | ||
| if [ -d ".github/instructions" ]; then | ||
| INSTR_COUNT=$(find .github/instructions -name "*.instructions.md" | wc -l) | ||
| fi | ||
| echo "ℹ️ .github/instructions/: ${INSTR_COUNT} file(s)" | ||
| echo "" | ||
| echo "✅ Setup complete — Copilot cloud agent is ready to work" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| #!/usr/bin/env bash | ||
| # tests/test_copilot_setup_steps.sh | ||
| # | ||
| # Validates that .github/workflows/copilot-setup-steps.yml exists and contains | ||
| # a job named exactly `copilot-setup-steps`, which GitHub requires to recognise | ||
| # the file for the Copilot cloud agent setup feature. | ||
| # | ||
| # Run: bash tests/test_copilot_setup_steps.sh | ||
|
|
||
| set -euo pipefail | ||
|
|
||
|
don-petry marked this conversation as resolved.
|
||
| if ! command -v python3 &>/dev/null; then | ||
| echo "ERROR: python3 is required to run this script." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! python3 -c "import yaml" &>/dev/null; then | ||
| echo "ERROR: python3 'yaml' module (PyYAML) is required to run this script." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| PASS=0 | ||
| FAIL=0 | ||
| ERRORS="" | ||
|
|
||
| ok() { | ||
| local name="$1" | ||
| PASS=$((PASS + 1)) | ||
| printf 'PASS %s\n' "$name" | ||
| } | ||
|
|
||
| fail() { | ||
| local name="$1" | ||
| local msg="$2" | ||
| FAIL=$((FAIL + 1)) | ||
| ERRORS="${ERRORS}FAIL ${name}: ${msg}\n" | ||
| printf 'FAIL %s: %s\n' "$name" "$msg" | ||
| } | ||
|
|
||
| WORKFLOW=".github/workflows/copilot-setup-steps.yml" | ||
| YAML_VALID=0 | ||
|
|
||
|
don-petry marked this conversation as resolved.
|
||
| # --------------------------------------------------------------------------- | ||
| # Test 1: File exists | ||
| # --------------------------------------------------------------------------- | ||
| if [ -f "$WORKFLOW" ]; then | ||
| ok "copilot-setup-steps.yml exists" | ||
| else | ||
| fail "copilot-setup-steps.yml exists" "file not found at $WORKFLOW" | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Test 1.5: YAML is valid | ||
| # --------------------------------------------------------------------------- | ||
| if [ -f "$WORKFLOW" ]; then | ||
| if python3 - "$WORKFLOW" << 'PY' 2>/dev/null | ||
| import sys, yaml | ||
| with open(sys.argv[1]) as f: | ||
| yaml.safe_load(f) | ||
| PY | ||
| then | ||
| ok "copilot-setup-steps.yml is valid YAML" | ||
| YAML_VALID=1 | ||
| else | ||
| fail "copilot-setup-steps.yml is valid YAML" "failed to parse YAML file" | ||
| fi | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Test 2: Job named `copilot-setup-steps` is present | ||
| # GitHub requires this exact name to activate the workflow for Copilot agent. | ||
| # --------------------------------------------------------------------------- | ||
| if [ "$YAML_VALID" -eq 1 ]; then | ||
| if python3 - "$WORKFLOW" << 'PY' | ||
| import sys, yaml | ||
|
don-petry marked this conversation as resolved.
|
||
| with open(sys.argv[1]) as f: | ||
| wf = yaml.safe_load(f) or {} | ||
| jobs = wf.get("jobs") or {} | ||
| if "copilot-setup-steps" not in jobs: | ||
| print(f"jobs keys found: {list(jobs.keys())}", file=sys.stderr) | ||
| sys.exit(1) | ||
| PY | ||
|
don-petry marked this conversation as resolved.
|
||
| then | ||
| ok "job named 'copilot-setup-steps' is present" | ||
| else | ||
| fail "job named 'copilot-setup-steps' is present" \ | ||
| "no job with that exact name — GitHub will not recognise the workflow" | ||
| fi | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Test 3: No other job names (GitHub only uses the copilot-setup-steps job) | ||
| # --------------------------------------------------------------------------- | ||
| if [ "$YAML_VALID" -eq 1 ]; then | ||
| JOB_COUNT=$(python3 - "$WORKFLOW" << 'PY' | ||
| import sys, yaml | ||
| with open(sys.argv[1]) as f: | ||
| wf = yaml.safe_load(f) or {} | ||
| print(len(wf.get("jobs") or {})) | ||
| PY | ||
| ) | ||
| if [[ "$JOB_COUNT" == "1" ]]; then | ||
| ok "workflow contains exactly 1 job" | ||
| else | ||
| fail "workflow contains exactly 1 job" "found $JOB_COUNT jobs — expected only copilot-setup-steps" | ||
| fi | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Test 4: timeout-minutes does not exceed 59 (GitHub hard limit) | ||
| # --------------------------------------------------------------------------- | ||
| if [ "$YAML_VALID" -eq 1 ]; then | ||
| TIMEOUT=$(python3 - "$WORKFLOW" << 'PY' | ||
| import sys, yaml | ||
| with open(sys.argv[1]) as f: | ||
| wf = yaml.safe_load(f) or {} | ||
| jobs = wf.get("jobs") or {} | ||
| job = jobs.get("copilot-setup-steps") or {} | ||
| print(job.get("timeout-minutes", 0)) | ||
| PY | ||
| ) | ||
| if [[ "$TIMEOUT" =~ ^[0-9]+$ ]] && [ "$TIMEOUT" -le 59 ] && [ "$TIMEOUT" -gt 0 ]; then | ||
| ok "timeout-minutes ($TIMEOUT) is within GitHub's 59-minute limit" | ||
| else | ||
| fail "timeout-minutes within limit" \ | ||
| "timeout-minutes=$TIMEOUT is invalid (must be 1–59)" | ||
| fi | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Test 5: Top-level permissions are reset to {} (least-privilege policy) | ||
| # --------------------------------------------------------------------------- | ||
| if [ "$YAML_VALID" -eq 1 ]; then | ||
| PERMS=$(python3 - "$WORKFLOW" << 'PY' | ||
| import sys, yaml | ||
| with open(sys.argv[1]) as f: | ||
| wf = yaml.safe_load(f) or {} | ||
| perms = wf.get("permissions", "MISSING") | ||
| # {} or None (null) both represent "reset to empty" in YAML | ||
| if perms == {} or perms is None: | ||
| print("empty") | ||
| else: | ||
| print(repr(perms)) | ||
| PY | ||
| ) | ||
| if [ "$PERMS" = "empty" ]; then | ||
| ok "top-level permissions reset to {}" | ||
| else | ||
| fail "top-level permissions reset to {}" \ | ||
| "got: $PERMS — ci-standards.md requires top-level permissions: {}" | ||
| fi | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Summary | ||
| # --------------------------------------------------------------------------- | ||
| echo "" | ||
| echo "Results: $PASS passed, $FAIL failed" | ||
| if [ "$FAIL" -gt 0 ]; then | ||
| printf '%b' "$ERRORS" | ||
| exit 1 | ||
| fi | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.