Skip to content

chore(rulesets): apply required_signatures across JacobPEvans + dryvist (PR #4 of unsigned-commit cleanup) #303

@JacobPEvans

Description

@JacobPEvans

Goal

Apply required_signatures Repository Ruleset to every active workflow-having repo in JacobPEvans + dryvist so that no automated commit can land unsigned, ever.

This is Layer 3 of the 8-PR "eliminate unsigned automated commits" initiative. The other layers fix the existing offenders (PR #1) and add CI/hook/audit backstops (PRs #5, #6, #7). This layer is the iron rule — once active, GitHub itself rejects unsigned pushes at the API level.

Why this is an issue and not a PR

Repository Rulesets aren't stored in code (no terraform module, no YAML files). They're a GitHub-managed resource configured via the REST API at repos/{owner}/{repo}/rulesets. Each command below is idempotent — re-running has no effect if the rule already exists.

Auth required

The current gh auth PAT lacks administration:write scope (fine-grained PAT, no admin permission). To execute the commands below, use either:

  • A classic PAT with admin:repo_hook + repo scopes: gh auth login --scopes admin:repo_hook,admin:org,repo,workflow,write:org
  • A fine-grained PAT with "Repository administration: Read and write" on the target repos
  • An installation token from the JacobPEvans-github-actions[bot] App (verify the App has the Administration: Write permission first; if not, grant via Settings → Developer settings → GitHub Apps → JacobPEvans-github-actions → Permissions)

What needs to change

1. JacobPEvans/JacobPEvans output branch extension

Depends on PR #26 (snake/3d-contrib fix) being merged first. Without that, applying the rule retroactively rejects every future snake.yml/3d-contrib.yml run.

gh api repos/JacobPEvans/JacobPEvans/rulesets/11490473 --method PUT --input - <<'EOF'
{"conditions": {"ref_name": {"include": ["~DEFAULT_BRANCH", "refs/heads/output"], "exclude": []}}}
EOF

2. JacobPEvans repos with rulesets MISSING required_signatures (PATCH each)

Repo Ruleset ID Current rules
cc-edge-claude-code-otel 13538560 deletion, non_fast_forward, pull_request
cc-edge-copilot-otel 14126034 deletion, non_fast_forward, pull_request
cc-edge-vscode-io 14126036 deletion, non_fast_forward, pull_request
cc-stream-github-copilot-rest-io 14126037 deletion, non_fast_forward, pull_request

For each:

for entry in "cc-edge-claude-code-otel:13538560" "cc-edge-copilot-otel:14126034" "cc-edge-vscode-io:14126036" "cc-stream-github-copilot-rest-io:14126037"; do
  repo="${entry%%:*}"; rid="${entry##*:}"
  current=$(gh api "repos/JacobPEvans/$repo/rulesets/$rid")
  new_rules=$(echo "$current" | jq '.rules + [{"type":"required_signatures"}]')
  gh api --method PUT "repos/JacobPEvans/$repo/rulesets/$rid" --input - <<EOF2
{"rules": $new_rules}
EOF2
done

3. JacobPEvans active workflow-having repos with NO rulesets (POST canonical)

These are repos that have workflow files but no ruleset at all — they're at risk of accepting unsigned commits if a future workflow adds git commit:

  • agentics (13 workflows — fork of githubnext/agentics, drives gh-aw)
  • agent-os (2 workflows — fork)

Skipping claude-code-automated (archived) and personal/obsidian/notes repos without workflows (they have no automation risk and the user may commit from web/mobile editors).

For each:

for repo in agentics agent-os; do
  gh api repos/JacobPEvans/$repo/rulesets --method POST --input - <<EOF2
{
  "name": "main",
  "target": "branch",
  "enforcement": "active",
  "conditions": {"ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}},
  "rules": [
    {"type": "deletion"},
    {"type": "non_fast_forward"},
    {"type": "required_signatures"}
  ]
}
EOF2
done

(Keeping the rule set minimal — deletion, non_fast_forward, required_signatures — since these are upstream forks where we don't want to enforce our own PR-review patterns.)

4. dryvist org-level → fallback to per-repo

gh api orgs/dryvist/rulesets returned 404 on probe (likely Free-tier limitation, possibly token permission). For all 9 dryvist public repos, POST the canonical ruleset:

for repo in nix-pxe-bootstrap homelab-schemas nix-ai-server ansible-proxmox-cluster ansible-server-apps tofu-proxmox-cluster .github cc-edge-pack-template cc-edge-claude-code-io; do
  gh api repos/dryvist/$repo/rulesets --method POST --input - <<EOF2
{
  "name": "main",
  "target": "branch",
  "enforcement": "active",
  "conditions": {"ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}},
  "rules": [
    {"type": "deletion"},
    {"type": "non_fast_forward"},
    {"type": "required_signatures"},
    {"type": "pull_request", "parameters": {"allowed_merge_methods": ["squash","rebase"], "dismiss_stale_reviews_on_push": false, "require_code_owner_review": false, "require_last_push_approval": false, "required_approving_review_count": 0, "required_review_thread_resolution": true, "required_reviewers": []}}
  ]
}
EOF2
done

After this, re-probe org rulesets — if org-level becomes available at any point, migrate the per-repo rulesets to org-level for unified management.

Verification after running all commands

for org in JacobPEvans dryvist; do
  echo "=== $org ==="
  for repo in $(gh repo list "$org" --limit 100 --json name --jq '.[].name'); do
    sig_present=$(gh api "repos/$org/$repo/rulesets" --jq '[.[].id]' 2>/dev/null \
      | jq -r '.[]' | xargs -I{} gh api "repos/$org/$repo/rulesets/{}" --jq '.rules[].type' 2>/dev/null \
      | grep -c required_signatures || true)
    workflows=$(gh api "repos/$org/$repo/contents/.github/workflows" --jq 'length' 2>/dev/null || echo 0)
    if [ "${sig_present:-0}" = "0" ] && [ "${workflows:-0}" -gt "0" ]; then
      echo "  STILL MISSING required_signatures (has $workflows workflows): $repo"
    fi
  done
done

Expected: empty output. Any line means a repo's automated commits could still land unsigned.

Explicitly out of scope

Tracking

Close this issue once the verification command above produces empty output AND the PR #1 sub-step is complete.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:choreChore - Maintenance tasks, dependencies, tooling

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions