Skip to content
Merged
Show file tree
Hide file tree
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 Jun 8, 2026
7368dea
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 8, 2026
f3390b7
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
3cb7e6b
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
4b53701
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
a28edba
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
ea67f15
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
c4b56d5
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
02a9c7e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
c6bdf46
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
ffb06e2
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
cba5b1a
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
6834cce
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
e1a4004
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
79721da
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
3a519d1
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 8, 2026
f808644
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
378132b
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
1c1d562
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
99ad8c9
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
9b26a8c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
e153459
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
28de899
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 9, 2026
97d11ac
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
024739a
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
93decf8
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
143546e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
55cabe6
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
b16d63c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
cb03d50
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
b57974c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
2b8f009
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
454d7c8
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
b9cd5a3
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
1e2a048
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
a12b91f
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
ff95072
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
ebe1191
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
218ca45
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
14f5474
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
5324b43
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
a7e7795
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 10, 2026
7e88ef5
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
a4aaf09
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
ea97144
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
5e7b868
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
b35e412
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
5d6890e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
1fa073e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 11, 2026
0d1411e
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 12, 2026
72099b4
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 12, 2026
ce859e6
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 12, 2026
ca605fe
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 12, 2026
717a3a9
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 12, 2026
c1c3350
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 13, 2026
adb5ef2
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 13, 2026
af06ae0
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] Jun 13, 2026
d8f068b
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] Jun 13, 2026
eddc5bd
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] Jun 13, 2026
c0e8288
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] Jun 13, 2026
8d31755
Merge branch 'main' into dev-lead/issue-397-20260608-0049
github-actions[bot] Jun 13, 2026
392575d
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 13, 2026
dbefd2f
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 13, 2026
6a40497
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 13, 2026
c1d7a91
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 13, 2026
f04d036
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
c03d818
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
d608479
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
f749492
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 14, 2026
a53a49c
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
0afa74a
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
d0da149
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
b1bf9ec
Merge branch 'main' into dev-lead/issue-397-20260608-0049
donpetry-bot Jun 14, 2026
d9a60cf
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
ad005da
Merge branch 'main' into dev-lead/issue-397-20260608-0049
don-petry Jun 14, 2026
74bb0b7
fix(reviews): address review comments [skip ci-relay]
donpetry-bot Jun 14, 2026
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
130 changes: 130 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
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
Comment thread
don-petry marked this conversation as resolved.
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"
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ jobs:

- name: Run list-prs sort tests
run: bash tests/test_list_prs_sort.sh

- name: Install test dependencies
run: pip install pyyaml

- name: Run copilot-setup-steps workflow tests
run: bash tests/test_copilot_setup_steps.sh
162 changes: 162 additions & 0 deletions tests/test_copilot_setup_steps.sh
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

Comment thread
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

Comment thread
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
Comment thread
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
Comment thread
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
Loading