Skip to content
Merged
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ A state-changing GitHub call is the highest-blast-radius thing an agent does her
- **All commits must be cryptographically signed (SSH or GPG).** Branch protection enforces this on both branches; unsigned commits are rejected on push. Signing depends on environment configuration - `git config commit.gpgsign true`, a configured `user.signingkey`, and a working signing agent (loaded `ssh-agent` for SSH, or `gpg-agent` for GPG). If signing is not configured in the environment, **do not commit** - surface the missing config to the developer and stop at `git add`. Verify before any agent-authored commit (`git config --get commit.gpgsign && ssh-add -L` or the GPG equivalent). **Signing must be live before the *first* commit, not retrofitted.** Turning on `Require signed commits` against a branch that already has unsigned commits forces a rewrite of that entire history to re-sign it - changing every commit SHA and making whoever does the rewrite the committer and signer of every commit (a rebase preserves the `author` field but not the original signatures; you cannot sign another contributor's commits for them). During new-repo setup, never create commits until signing is verified.
- **Commit under the committing account's own GitHub `noreply` identity - never a private, personal, or invented address.** The `author` and `committer` on every agent-authored commit are the GitHub `noreply` address of the account whose key signs the commit (above) - GitHub issues these in a `username@users.noreply.github.com` or `ID+username@users.noreply.github.com` form, and for this single-maintainer fleet it is the owner's `ptr727@users.noreply.github.com`. Do not set `user.name`/`user.email` to a fabricated persona, bot name, or product name, and do not commit under whatever identity the environment happens to carry: verify `git config --get user.email` is that GitHub `noreply` address before committing, and fix it if not. A wrong identity is not cosmetic - a private email trips GitHub's email-privacy push protection (GH007), and an unrecognized or invented author pollutes history. Identity is separate from signing: a wrong author does not by itself fail the signature rule, but the ad-hoc identities that produce it are typically also unsigned, which the signing rule above then rejects on push.
- **Never force push.** Do not run `git push --force` or `git push --force-with-lease` under any circumstances. Force pushing rewrites shared history and can cause data loss.
- **A history rewrite includes only the commits that must change, and re-identifies any commit it rewrites that is not yours.** Filtering history (`git filter-repo` / `filter-branch`, e.g. to strip PII) rewrites the touched commits and you re-sign them with your key, while the tooling preserves each commit's original `author` and `committer` unless told otherwise. GitHub verifies a signature against the commit's `committer` identity, so a your-key signature over a commit still committed by a bot (`dependabot[bot]`, `github-actions[bot]`) or by GitHub's web-flow does not match its committer and is marked `unknown_key`/unverified, which a require-signed-commits ruleset then rejects. Two gates keep committer and signature aligned. **First, scope the rewrite to only the commits that must be modified** - by default those are your own, whose committer is already your identity, and a commit that does not need changing is kept out of the rewrite so its identity and signature are never touched. **Second, if a commit that must change is not yours, set its `committer` to the signing identity before re-signing** (and its `author` too, since a rewrite that alters the content should not keep attributing it to the bot), so the committer GitHub verifies matches your key - the original bot attribution is deliberately given up as the cost of having to rewrite it. Never leave your signature over a commit committed by another identity. Verify after the rewrite that every rewritten commit is signed and committed under your identity (`git log --show-signature`).
- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction.

## Branching Model
Expand Down
2 changes: 1 addition & 1 deletion STANDUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Implement the Actions that satisfy [`WORKFLOW.md`][workflow] for the repo's type

## 4. Apply Settings, Rulesets, and Secrets

Run `repo-config/configure.sh [owner/repo] [release|operational]` (the repo defaults to the current one, the model to the registry lookup or, absent a registry, to the carried payload) to apply the fleet settings and the two rulesets idempotently (import the JSON, never hand-build - see [`docs/repo-config-carry.md`][repo-config-carry]). Configure every required secret per [`spec/secrets.json`][secrets] (the registry `requiredSecrets[]` list plus the implicit baseline) in the right store(s) - Actions, and Dependabot where the mechanism needs it - and confirm no forbidden secret is present. The required check binds by name (`Check pull request workflow status job`) and turns green only after the PR workflow has run once.
Run `repo-config/configure.sh apply [owner/repo] [release|operational]` (the repo defaults to the current one, the model to the registry lookup or, absent a registry, to the carried payload) to apply the fleet settings, the Dependabot security features, and the two rulesets idempotently (import the JSON, never hand-build - see [`docs/repo-config-carry.md`][repo-config-carry]), then `repo-config/configure.sh check [owner/repo] [release|operational]` to validate the repo and exit non-zero on any drift. Configure every required secret per [`spec/secrets.json`][secrets] (the registry `requiredSecrets[]` list plus the implicit baseline) in the right store(s) - Actions, and Dependabot where the mechanism needs it - and confirm no forbidden secret is present. The required check binds by name (`Check pull request workflow status job`) and turns green only after the PR workflow has run once.

## 5. Verify - Run the Audit

Expand Down
8 changes: 4 additions & 4 deletions docs/repo-config-carry.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ Every fleet repo carries the `repo-config/` directory. The hub keeps the canonic

**Configure by importing the JSON payloads, never by hand-building the rules** (hand reconstruction has gone wrong on past setups). The result must be **exactly two rulesets named `develop` and `main`** - the names are load-bearing (`AGENTS.md` and the workflows reference them). Only the `develop` *content* varies by model.

First remove all legacy classic branch-protection rules and any stray rulesets, then run `configure.sh` (which picks the `develop` payload from the repo's `workflowModel`, or infers it from the carried payload when no registry is present, and applies `settings.json` alongside the rulesets):
First remove all legacy classic branch-protection rules and any stray rulesets, then run `configure.sh apply` (which picks the `develop` payload from the repo's `workflowModel`, or infers it from the carried payload when no registry is present, and applies `settings.json` and the Dependabot security features alongside the rulesets):

```sh
repo-config/configure.sh [owner/repo] [release|operational]
repo-config/configure.sh apply [owner/repo] [release|operational]
```

Or import each ruleset by hand with `gh api -X POST repos/<owner>/<repo>/rulesets --input repo-config/<name>.json` (operational repos use `operational/develop.json` for `develop`). `gh ruleset` is read-only, so creation goes through `gh api`. The required check binds by name and only turns green after the repo's PR workflow runs once. To edit a live ruleset, GET it, change the field, and PUT the whole writable subset back (a partial PUT `422`s).
Then validate the result with `repo-config/configure.sh check [owner/repo] [release|operational]`, which asserts every applied ruleset, setting, and security feature and exits non-zero on drift (the ruleset and settings checks are driven by the committed payloads, so they stay repo-agnostic). Or import each ruleset by hand with `gh api -X POST repos/<owner>/<repo>/rulesets --input repo-config/<name>.json` (operational repos use `operational/develop.json` for `develop`). `gh ruleset` is read-only, so creation goes through `gh api`. The required check binds by name and only turns green after the repo's PR workflow runs once. To edit a live ruleset, GET it, change the field, and PUT the whole writable subset back (a partial PUT `422`s).

## Regenerating the Payloads

Expand Down Expand Up @@ -50,7 +50,7 @@ done

## Brownfield Migration (Maintainer Only)

`Require signed commits` rejects any pre-existing unsigned commit, so the first `develop -> main` release on a repo with unsigned history is blocked. Re-signing that history is a non-fast-forward that the `Block force pushes` rule rejects, **and the admin bypass does not cover `git push --force`**. Completing it requires temporarily disabling the ruleset and a maintainer force-push. This is a one-time, maintainer-performed migration that deliberately uses the force-push [AGENTS.md "Git and Commit Rules"][agents-git-and-commit-rules] forbids agents from running - **an agent must never execute it - surface it to the maintainer**. Greenfield repos where signing is live before the first commit never hit this.
`Require signed commits` rejects any pre-existing unsigned commit, so the first `develop -> main` release on a repo with unsigned history is blocked. Re-signing that history is a non-fast-forward that the `Block force pushes` rule rejects, **and the admin bypass does not cover `git push --force`**. Completing it requires temporarily disabling the ruleset and a maintainer force-push. This is a one-time, maintainer-performed migration that deliberately uses the force-push [AGENTS.md "Git and Commit Rules"][agents-git-and-commit-rules] forbids agents from running - **an agent must never execute it - surface it to the maintainer**. Greenfield repos where signing is live before the first commit never hit this. When the rewrite touches commits committed under a bot or web-flow identity (`dependabot[bot]`, `github-actions[bot]`), set each commit's committer to the signing identity before re-signing so the committer GitHub verifies matches your key, rather than leaving your key over another identity's commit (see the history-rewrite rule in [AGENTS.md "Git and Commit Rules"][agents-git-and-commit-rules]).

<!-- Repo -->

Expand Down
4 changes: 2 additions & 2 deletions repo-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Repository and branch configuration held as committed files, kept out of `.githu

- `main.json` plus one `develop` variant - the branch rulesets as the writable API subset (`name`, `target`, `enforcement`, `bypass_actors`, `conditions`, `rules`). The `develop` payload is `develop.json` (`release` repos) or `operational/develop.json` (`operational` repos). These are the canonical expected payloads that the self-audit (`AUDIT.md`) diffs the live rulesets against.
- `operational/develop.json` - the `develop` ruleset for **operational** repos (registry `workflowModel: operational`): direct signed pushes, no PR gate. Present in operational repos only - a `release` repo does not have it. See "Rulesets" below.
- `configure.sh` - applies the rulesets and settings to a repository via the GitHub API (create or full-payload update, idempotent). Run `repo-config/configure.sh [owner/repo] [release|operational]`; the model may also be passed as the sole argument (`repo-config/configure.sh operational`). The model defaults to the registry `workflowModel` lookup where a registry is present, else it is inferred from which `develop` payload is present (an ambiguous layout aborts rather than guesses).
- `configure.sh` - two modes over the GitHub API. `configure.sh apply [owner/repo] [release|operational]` creates-or-updates the settings, the Dependabot security features, and the rulesets idempotently (a full-payload update). `configure.sh check [owner/repo] [release|operational]` is the read-only inverse and exits non-zero on any drift, with the ruleset and settings assertions driven by the committed payloads so they stay repo-agnostic (rule presence, merge methods, and required checks, not a byte diff - so a GitHub-normalized stored ruleset does not false-positive). The command defaults to `apply`, the repo to the current one, and the model to the registry `workflowModel` lookup (or, absent a registry, inference from the carried `develop` payload - an ambiguous layout aborts rather than guesses). The model may be passed as the sole positional (`configure.sh check operational`).

## Rulesets

Expand All @@ -23,7 +23,7 @@ Publish credentials required per mechanism are enumerated in `spec/secrets.json`

## Repo Settings

The fleet-standard general settings live in [`settings.json`][settings-json] and are applied idempotently by `configure.sh` alongside the rulesets (`gh api PATCH /repos/{owner}/{repo}`). The two settings that depend on per-repo state - `has_discussions` (visibility) and `default_branch` (main-must-exist) - are computed by the script, not stored in the file.
The fleet-standard general settings live in [`settings.json`][settings-json] and are applied idempotently by `configure.sh apply` alongside the rulesets (`gh api PATCH /repos/{owner}/{repo}`). The two settings that depend on per-repo state - `has_discussions` (visibility) and `default_branch` (main-must-exist) - are computed by the script, not stored in the file. `configure.sh apply` also enables Dependabot vulnerability alerts and automated security updates - fleet policy applied via the API, not a `settings.json` key. `configure.sh check` validates all of these and exits non-zero on drift.

- **Default branch `main`** (the script sets it only when a `main` branch exists, never pointing the default at a missing branch).
- **Merge methods**: `Allow merge commits` and `Allow squash merging` on, **rebase off** - each branch ruleset then picks its method (merge on `main`, squash on `develop`).
Expand Down
Loading