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 .github/actions/prose-gate/prose_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ def path_candidate(token: str, in_span: bool = True) -> str | None:
"repo-config/main.json",
"repo-config/README.md",
"repo-config/settings.json",
"repo-config/labels.json",
"spec/secrets.json",
".github/workflows/get-version-task.yml",
".github/workflows/publish-plan-task.yml",
Expand Down
2 changes: 1 addition & 1 deletion AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Run [`WORKFLOW.md`][workflow]'s methodology against the repo's **own** Actions:

## 6. Validate Settings, Rulesets, and Secrets

- **General settings and rulesets** - fetch the hub and check out `main`. Run `repo-config/configure.sh check <owner>/<repo> release|operational` from that checkout. Pass the target repository and its registry `workflowModel` explicitly. The command checks the shared settings, state-dependent settings, Dependabot security features, and both rulesets against the hub payloads. It preserves and reports `bypass_actors` without asserting them because bypass authority is a per-repository human decision.
- **General settings, labels, and rulesets** - fetch the hub and check out `main`. Run `repo-config/configure.sh check <owner>/<repo> release|operational` from that checkout. Pass the target repository and its registry `workflowModel` explicitly. The command checks what `configure.sh apply` writes: the declared settings, the derived settings and the registry description, the declared labels, the Dependabot security features, the shared `main` ruleset, and the `develop` ruleset the model selects. It preserves and reports `bypass_actors` without asserting them because bypass authority is a per-repository human decision.

- **Secrets** - from the same hub checkout, run [`spec/audit.py`][audit-runner] `[repo]` and read its Secrets section. It resolves the required set from the hub's own [`spec/secrets.json`][secrets] plus the registry entry's `publish[]`/`types[]`/`requiredSecrets[]`, confirming each required name exists (name only, not the values) in the Actions store and, where the mechanism needs it (Docker Hub, codegen App), the Dependabot store too.

Expand Down
2 changes: 1 addition & 1 deletion OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ repo-config/configure.sh apply owner/repo release|operational

`check` is read-only and exits non-zero on drift. `apply` is idempotent and drives entirely from the committed payloads, so it is a no-op on a conformant repo.

`apply` is not a narrow toggle. One run patches every key in `repo-config/settings.json`, sets the default branch, enables both Dependabot features, and creates or updates both branch rulesets. On a repository that has deliberately drifted it silently reasserts the fleet configuration.
`apply` is not a narrow toggle. One run patches every key in `repo-config/settings.json`, sets the default branch, enables both Dependabot features, creates or updates every label in `repo-config/labels.json`, and creates or updates both branch rulesets. On a repository that has deliberately drifted it silently reasserts the fleet configuration.

The model argument selects which develop payload is applied, so passing the wrong one applies the wrong ruleset.

Expand Down
2 changes: 1 addition & 1 deletion STANDUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ Three conditions fail here, and the two commands together are what separate them

Each is step 0A's escalation rather than something to work around.

Run `repo-config/configure.sh apply owner/repo release|operational` from a hub checkout at `main`, naming the repo being stood up and its model, to apply the fleet settings, Dependabot security features, and two rulesets idempotently (import the JSON, never hand-build it, per [`docs/repo-config.md`][repo-config-doc]). Then run `repo-config/configure.sh check owner/repo release|operational` from the same checkout. Pass the model explicitly because the repository is outside the registry during this step. Configure every required secret per [`spec/secrets.json`][secrets] (the registry `requiredSecrets[]` list plus the implicit baseline) in the right store(s), meaning Actions plus 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, which is why this step follows step 3 rather than preceding it. A ruleset requiring a name no run has ever reported leaves the first pull request waiting on a status nothing produces, and on an operational repo the `develop -> main` promotion is a pull request too, so the same wait applies there.
Run `repo-config/configure.sh apply owner/repo release|operational` from a hub checkout at `main`, naming the repo being stood up and its model, to apply the fleet settings, Dependabot security features, label set, and two rulesets idempotently (import the JSON, never hand-build it, per [`docs/repo-config.md`][repo-config-doc]). Then run `repo-config/configure.sh check owner/repo release|operational` from the same checkout. Pass the model explicitly because the repository is outside the registry during this step. Configure every required secret per [`spec/secrets.json`][secrets] (the registry `requiredSecrets[]` list plus the implicit baseline) in the right store(s), meaning Actions plus 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, which is why this step follows step 3 rather than preceding it. A ruleset requiring a name no run has ever reported leaves the first pull request waiting on a status nothing produces, and on an operational repo the `develop -> main` promotion is a pull request too, so the same wait applies there.

For a **private** repo, confirm the account-wide toggle at `https://github.com/settings/security_analysis`, `Dependabot on self-hosted runners`, is off, along with `Automatically enable for new repositories` beside it. If self-hosted routing is wanted instead, register a matching self-hosted runner rather than disabling the toggle. Left on with no self-hosted runner registered on the account, Dependabot's own update jobs queue for up to 24 hours, then get cancelled. That cancelled-with-zero-steps pattern is the only Actions-API-visible signal, not an explicit cause, and ordinary CI is unaffected. The account-setting root cause surfaces only as a `Self-hosted runner unavailable` message on the repo's own Dependabot page. GitHub never routes a public repo through this setting, so a public standup is unaffected (ptr727/ProjectTemplate#1015). A repo standing up from a **partial state** may already carry queued or cancelled jobs from before this check ran. Fixing the toggle does not rerun those. A manual `Check for Updates` click on the repo's own Dependabot page does.

Expand Down
5 changes: 3 additions & 2 deletions docs/repo-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The hub holds all fleet-wide repository configuration:
- `main.json` declares the shared `main` ruleset.
- `develop.json` declares the release-model `develop` ruleset.
- `operational/develop.json` declares the operational-model `develop` ruleset.
- `labels.json` declares the fleet label set.
- `configure.sh` applies or checks those payloads through the GitHub API.

Downstream repositories carry no `repo-config/` directory. The registry's `workflowModel` selects the `develop` payload. Commands that operate before registry enrollment pass the model explicitly.
Expand All @@ -24,13 +25,13 @@ Downstream repositories carry no copy of `spec/secrets.json`. `baseline` applies

**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`**, and the names are load-bearing (`AGENTS.md` and the workflows reference them). Only the `develop` *content* varies by model.

Remove all classic branch-protection rules and stray rulesets. Run `configure.sh apply` from a hub checkout at `main`, naming the target repository and its model. The script applies `settings.json`, the Dependabot security features, and both rulesets. A registered repository can omit the model and use the registry lookup. A repository outside the registry passes the model explicitly:
Remove all classic branch-protection rules and stray rulesets. Run `configure.sh apply` from a hub checkout at `main`, naming the target repository and its model. The script applies `settings.json`, the Dependabot security features, the label set, and both rulesets. A registered repository can omit the model and use the registry lookup. A repository outside the registry passes the model explicitly:

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

Then validate the result with `repo-config/configure.sh check owner/repo release|operational`, run from the same checkout, 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).
Then validate the result with `repo-config/configure.sh check owner/repo release|operational`, run from the same checkout, which asserts every applied ruleset, setting, label, 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
15 changes: 14 additions & 1 deletion repo-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Hub-only repository and branch configuration held as committed files, kept out of `.github/` (which holds the GitHub-consumed configuration: workflows, Dependabot). Downstream repositories carry no `repo-config/` directory. Apply and check commands run from a hub checkout at `main` and name the target repository.

- `main.json`, `develop.json`, and `operational/develop.json`: the canonical branch rulesets as the managed part of the writable API subset (`name`, `target`, `enforcement`, `conditions`, `rules`). `main.json` is shared. `develop.json` serves release repos, and `operational/develop.json` serves operational repos. `repo-config/configure.sh check owner/repo release|operational` compares the selected payloads with the live rulesets. `bypass_actors` is writable and deliberately unmanaged, so no payload declares one and nothing diffs it: who may bypass a ruleset is a human decision taken in the UI, which `repo-config/configure.sh` preserves on `apply` and reports without asserting on `check`.
- `configure.sh`: run from a hub checkout at `main`, per [GOVERNANCE.md "Hub-Hosted Tooling"][governance-hub-hosted-tooling]. It resolves every payload path against the hub's `repo-config/` directory. Name the target repository explicitly, since the command defaults to whichever repository the shell is sitting in. `repo-config/configure.sh apply owner/repo release|operational` creates or updates the settings, Dependabot security features, and rulesets idempotently. `repo-config/configure.sh check owner/repo release|operational` is the read-only inverse and exits non-zero on drift. The model defaults to the registry `workflowModel` lookup. Pass it explicitly for a repository outside the registry.
- `labels.json`: the fleet label set, one `name`, `color`, and `description` per label. `repo-config/configure.sh apply owner/repo release|operational` creates or updates every declared label by name and deletes nothing, so a label a repo adds of its own stays. `check` asserts each declared label on all three fields and reports the undeclared ones without judging them.
- `configure.sh`: run from a hub checkout at `main`, per [GOVERNANCE.md "Hub-Hosted Tooling"][governance-hub-hosted-tooling]. It resolves every payload path against the hub's `repo-config/` directory. Name the target repository explicitly, since the command defaults to whichever repository the shell is sitting in. `repo-config/configure.sh apply owner/repo release|operational` creates or updates the settings, Dependabot security features, labels, and rulesets idempotently. `repo-config/configure.sh check owner/repo release|operational` is the read-only inverse and exits non-zero on drift. The model defaults to the registry `workflowModel` lookup. Pass it explicitly for a repository outside the registry.

## Rulesets

Expand All @@ -20,6 +21,18 @@ The result is **exactly two rulesets named `develop` and `main`**, and the names

Publish credentials required per mechanism are enumerated in `spec/secrets.json`. A repo needs only the mechanisms its own publish targets use, so a source-only repo needs none of the publish credentials below. NuGet and PyPI use keyless OIDC Trusted Publishing (no stored key, so the publish job needs `id-token: write`, and PyPI additionally an `environment: pypi` gate). That publish job belongs to the repo's own workflow file, since trusted publishing validates the OIDC token's `job_workflow_ref` claim against the repository owning the package and rejects a reusable workflow's ref, so `id-token: write` is granted at that one entry point and nowhere else. The registry-side policy is the other half of that pairing and is configured on nuget.org or PyPI rather than here: it names the repository and the workflow file the push runs from, so moving the push between workflow files means repointing the policy in the same change. Docker Hub has no OIDC equivalent and uses a stored `DOCKER_HUB_USERNAME` + `DOCKER_HUB_ACCESS_TOKEN` in both the Actions and Dependabot secret stores. Codegen and merge-bot repos add a GitHub App (`CODEGEN_APP_CLIENT_ID` + `CODEGEN_APP_PRIVATE_KEY` in both stores, and the app must be installed, not just created). App-token call sites use `client-id`, never the deprecated `app-id`.

## Labels

The triage labels classify an issue by the kind of work it needs, so a backlog sweep can pick the gates and scripts, which converge, ahead of the prose defects, which re-enter the review loop when worked one bundle at a time. An issue carries exactly one of these five, or `enhancement` for a feature, beside whatever surface labels it also carries.

- **`gate`**: a rule that exists in prose with no mechanical check, or a check that misses a shape.
- **`script`**: a defect in hub tooling.
- **`prose`**: a defect in rule or procedure text.
- **`decision`**: needs the maintainer's decision before it can be worked.
- **`chore`**: registry, labels, rollout, and other fleet housekeeping.

The class labels `introduced` and `pre-existing` record which class, per the `local-strict-review` Skill's "Disposing of Findings", a filed review finding carried. A `style` finding is declined rather than filed, so it has no label. `agents`, `skills`, and `codegen` mark the surface, and the rest are GitHub's own defaults and the Dependabot pair, declared so every fleet repo carries at least this set.

## Repo Settings

The fleet-standard general settings live in [`settings.json`][settings-json] and are applied idempotently by `repo-config/configure.sh apply owner/repo release|operational` 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. `apply` also enables Dependabot vulnerability alerts and automated security updates, fleet policy applied via the API rather than a `settings.json` key. `repo-config/configure.sh check owner/repo release|operational` validates all of these and exits non-zero on drift.
Expand Down
Loading