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
14 changes: 7 additions & 7 deletions COVERAGE.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/reference/secrets-and-vaults.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ environments:
value: $PROD_DEPLOY_TOKEN
```

Each environment is its own sealing scope with its own public key, so the same secret name can carry a different value per environment, as above. Reconciliation runs after the environment itself is applied; in check mode against an environment that does not exist yet, the declared secrets cannot be listed, so a note says they are unverifiable until apply creates it. Within a declared `secrets` key, live secrets the entries do not declare are kept by default (their values are unrecoverable); the wrapped `{_undeclared: delete, entries}` form opts into deletion. The endpoints ride the same "Environments" PAT permission as the rest of the section.
Each environment is its own sealing scope with its own public key, so the same secret name can carry a different value per environment, as above. The public key that seals a value is read at apply, by the first sealed write of its scope; check mode never requests it. Reconciliation runs after the environment itself is applied; in check mode against an environment that does not exist yet, the declared secrets cannot be listed, so a note says they are unverifiable until apply creates it. Within a declared `secrets` key, live secrets the entries do not declare are kept by default (their values are unrecoverable); the wrapped `{_undeclared: delete, entries}` form opts into deletion. The endpoints ride the same "Environments" PAT permission as the rest of the section.

## Multi-repo fan-out

Expand Down
8 changes: 4 additions & 4 deletions docs/reference/sections.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ Every top-level key a settings file can declare, one row per section: the endpoi
| `branches` | protected-branch listing + classic branch protection + required-signatures sub-endpoint + app-by-slug actor lookup + GraphQL BranchProtectionRules + BranchProtectionRulesSnapshot + BranchProtectionRepository + BranchProtectionActorUser + BranchProtectionActorTeam + CreateBranchProtectionRule + UpdateBranchProtectionRule + DeleteBranchProtectionRule | Administration: write | untouched | `protection: null` removes protection; the protection PUT drops `required_signatures`, so declare it on any branch already carrying it; `force_push_bypassers` (users, `org/team`, `app/slug`) and `required_deployments` ride the GraphQL rule mutation; wildcard entries (`release/*`) reconcile entirely through GraphQL with a fixed key set; add Contents: read so check mode can tell a missing branch from an unprotected one |
| `autolinks` | autolinks CRUD | Administration: write | deleted (settable) | immutable upstream, so changed entries are replaced |
| `actions` | actions permissions + selected-actions + workflow token + access level + artifact/log retention + cache limits + OIDC subject claim + fork PR policies | Administration: write; the `oidc_customization_sub` key alone instead needs Actions: write | untouched | keys with their own sub-endpoint route there; everything else rides the base permissions PUT verbatim |
| `actions_secrets` | actions secrets list + public-key + sealed PUT + delete | Secrets: write | kept (settable) | `{name, value: $NAME}` sealed writes, re-sent every apply; existence-only checks, values unrecoverable |
| `dependabot_secrets` | dependabot secrets list + public-key + sealed PUT + delete | Dependabot secrets: write | kept (settable) | as `actions_secrets`, over the Dependabot secret store |
| `codespaces_secrets` | codespaces secrets list + public-key + sealed PUT + delete | Codespaces secrets: write | kept (settable) | as `actions_secrets`, over the Codespaces secret store |
| `agents_secrets` | agents secrets list + public-key + sealed PUT + delete | Agent secrets: write | kept (settable) | as `actions_secrets`, over the Copilot agents secret store |
| `actions_secrets` | actions secrets list + public-key (read at apply) + sealed PUT + delete | Secrets: write | kept (settable) | `{name, value: $NAME}` sealed writes, re-sent every apply; existence-only checks, values unrecoverable |
| `dependabot_secrets` | dependabot secrets list + public-key (read at apply) + sealed PUT + delete | Dependabot secrets: write | kept (settable) | as `actions_secrets`, over the Dependabot secret store |
| `codespaces_secrets` | codespaces secrets list + public-key (read at apply) + sealed PUT + delete | Codespaces secrets: write | kept (settable) | as `actions_secrets`, over the Codespaces secret store |
| `agents_secrets` | agents secrets list + public-key (read at apply) + sealed PUT + delete | Agent secrets: write | kept (settable) | as `actions_secrets`, over the Copilot agents secret store |
| `workflows` | Actions workflows list, enable/disable | Actions: write | untouched | `{path, state: active or disabled}`; bare file names match `.github/workflows/` |
| `check_suite_preferences` | check-suites preferences PATCH (no read endpoint exists upstream) | Checks: write | untouched | per-app `auto_trigger_checks` toggles; write-only: check mode cannot verify them (one note, zero requests) and apply re-asserts them every run; the token owner must be a repository administrator |
| `pages` | POST/PUT/DELETE pages | Pages: write | untouched | `build_type: workflow` or `legacy` + source, `cname`, `https_enforced`, `public` (GHEC site visibility); `pages: null` disables the site |
Expand Down
14 changes: 7 additions & 7 deletions docs/upgrading/v2-to-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Twenty breaks (the ninth is for library consumers). One is silent (the fallback)
| One redacted label in every mode | A single-repository run labelled its hidden target `private repository`; a fleet numbered them `private repository #N` | `private repository #N` everywhere; a run over one repository is `#1` | No error. A log filter or artifact-report reader matching `private repository:` exactly no longer matches; [section 10](#10-one-redacted-label-in-every-mode) |
| Two live items under one identity fail the section | The last one listed won silently in most sections | Every list section refuses, naming the pair | The section fails until one is deleted on GitHub; [section 11](#11-two-live-items-under-one-identity-fail-the-section) |
| The webhook snapshot placeholder | `$WEBHOOK_SECRET_<id>` | `$SECRET_WEBHOOK_<id>` | No error: an old reference keeps resolving from its old export; a new snapshot writes the new name, so move both together; [section 12](#12-the-webhook-snapshot-placeholder-leads-with-secret_) |
| One wording per concept in drift lines and notes | Per-section spellings of "cannot verify", "left out", and field drift; quoted webhook labels | One template each on the converted sites | Only a grep over the output notices; [section 13](#13-one-wording-per-concept-in-drift-lines-and-notes) |
| One wording per concept in drift lines and notes | Per-section spellings of "cannot verify", "left out", and field drift; quoted webhook labels | One template each | Only a grep over the output notices; [section 13](#13-one-wording-per-concept-in-drift-lines-and-notes) |
| Webhooks manage web hooks only | A service hook was matched and deleted like any other | A service hook or url-less hook is outside the section | It is left alone and noted by snapshot; [section 14](#14-webhooks-manage-web-hooks-only) |
| A ruleset without `source_type` is repository-owned | Kept with a note under `_undeclared: delete` | Deleted like any other undeclared repository ruleset | [section 15](#15-a-ruleset-without-source_type-is-repository-owned) |
| Underscore keys are directives, never notes | An unknown `_note: ...` at the top level was dropped silently | Only `_layering` and `_undeclared` exist; any other underscore key fails validation, on a wrapper and at the top level alike | Validation fails before any section runs, naming the two directives; [section 16](#16-underscore-keys-are-directives-never-notes) |
Expand Down Expand Up @@ -218,20 +218,20 @@ Every snapshot secret now leads with `SECRET_`, the store name second. A file ho

## 13. One wording per concept in drift lines and notes

Anything that greps the check output for these lines needs the new spelling. Two templates reach only the sites this release converts:
Anything that greps the check output for these lines needs the new spelling. Two templates reach every site:

- the cannot-verify line: the webhook secret and the write-only `check_suite_preferences` note;
- the left-out line: rulesets, webhooks, and the secondary snapshot reads (actions, environments, repository) a denied grant skips under `on-missing-permission: warn`; a denied primary read still says `skipped`.

The other cannot-verify and omission notes (interaction_limits, teams, collaborators) keep their v2 line until a later release.
- the cannot-verify line: the webhook secret, the write-only `check_suite_preferences` note, `interaction_limits.expiry`, and the repository toggles GitHub cannot read back (`enable_git_lfs`);
- the left-out line: rulesets, webhooks, the teams and collaborators snapshot notes, an inherited interaction limit, and the secondary snapshot reads (actions, environments, repository) a denied grant skips under `on-missing-permission: warn`; a denied primary read still says `skipped`.

| Line | v2 | v3 |
|---|---|---|
| A webhook's label | `webhooks["https://ci.example.com/hook"].active` | `webhooks[https://ci.example.com/hook].active` |
| A field mismatch (milestones, rulesets, collaborators, teams, every list section) | `milestones[v1].state: "closed" != "open"` and `teams[platform]: live role "read" != declared "write"` | `milestones[v1].state: declared "closed" != live "open"; apply will set the declared value` |
| A webhook's events | `... declared [...] != live [...] (compared order-insensitively)` | one line per element: `webhooks[<url>].events: missing "release"` |
| A value check mode cannot compare | `... so the declared value cannot be verified; apply re-sends it on every run so rotations propagate` | `<label>: <why>, so check mode cannot verify <what>; apply <re-sends it> on every run` |
| A resource a snapshot reads but does not declare | `rulesets[x]: inherited from the organization ..., so it is not part of the repository's snapshot` | `<label>: left out of the snapshot - <reason>` |
| A resource a snapshot reads but does not declare | `rulesets[x]: inherited from the organization ..., so it is not part of the repository's snapshot` and `teams[x]: ...; not declared` | `<label>: left out of the snapshot - <reason>` |
| An email invitation in a collaborators snapshot | `invitation 502 was sent by email, so no username can declare it; not declared, and apply leaves it untouched` | `collaborators[invitation 502]: left out of the snapshot - sent by email, so no username can declare it; apply leaves it untouched` |
| A personal account under `teams` or `custom_properties` | `teams: owner "o" is a personal account, not an organization, so team access does not apply` and `custom_properties: owner "o" is a personal account, and custom properties require an organization-owned repository` | `<section>: owner "o" is a personal account, not an organization, so this section does not apply` |
| A ruleset update's change line | `updated ruleset "main" (id 42)` | `updated ruleset "main"` |
| A milestone delete's change line | `DELETED undeclared milestone "v0.9" (detached from every issue that carried it)` | `DELETED undeclared milestone "v0.9"` (the drift line beside it still names the detaching) |

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: actions-snapshot-roundtrip
description: "mode: snapshot reads the live Actions settings back into snapshot.yml and writes nothing to GitHub. Every endpoint reads back onto its key: the permissions policy with its selected-actions allowlist, the workflow token defaults, the access level, the retention and cache limits, a customized OIDC subject claim, and both fork pull request policies; selected_actions_url and maximum_allowed_days fall away. The written file, fed back to a check run against the same state, reads clean."
description: >-
mode: snapshot reads the live Actions settings back into snapshot.yml and writes nothing to
GitHub. Every endpoint reads back onto its key: the permissions policy with its selected-actions
allowlist, the workflow token defaults, the access level, the retention and cache limits, a
customized OIDC subject claim, and both fork pull request policies; selected_actions_url and
maximum_allowed_days fall away. The written file, fed back to a check run against the same state,
reads clean.
settings: {}
live_state:
actions_permissions:
Expand Down
5 changes: 3 additions & 2 deletions src/sections/actions_secrets/actions_secrets.docs.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
sections_table:
endpoints: actions secrets list + public-key + sealed PUT + delete
endpoints: actions secrets list + public-key (read at apply) + sealed PUT + delete
notes: >-
`{name, value: $NAME}` sealed writes, re-sent every apply; existence-only checks, values
unrecoverable
coverage:
- area: "[Actions secrets](https://docs.github.com/en/rest/actions/secrets)"
notes: >-
GET /repos/{owner}/{repo}/actions/secrets (names + timestamps), GET
.../actions/secrets/public-key, PUT .../actions/secrets/{secret_name} (a sealed box in
.../actions/secrets/public-key (read at apply, never in check mode), PUT
.../actions/secrets/{secret_name} (a sealed box in
libsodium's crypto_box_seal format, {encrypted_value, key_id}), DELETE
.../actions/secrets/{secret_name}. Values never live in
settings.yml: each entry's `value` is a whole-value `$NAME` reference resolved from the step's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ expect:
never:
- "PUT /repos/{repo}/actions/secrets"
- "DELETE /repos/{repo}/actions/secrets"
- "GET /repos/{repo}/actions/secrets/public-key"
stdout_contains:
- "drift: actions_secrets[DEPLOY_TOKEN]: missing"
- values cannot be read back from GitHub
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: actions-secrets-snapshot-roundtrip
description: "mode: snapshot reads the live actions_secrets back into snapshot.yml and writes nothing to GitHub. The actions_secrets names read back as per-store $SECRET_ACTIONS_<name> references, one note each in the file header and as a notice; the values, which GitHub never reveals, come from the environment when the snapshot is checked back. The written file, fed back to a check run against the same state, reads clean."
description: >-
mode: snapshot reads the live actions_secrets back into snapshot.yml and writes nothing to GitHub.
The actions_secrets names read back as per-store $SECRET_ACTIONS_<name> references, one note each
in the file header and as a notice; the values, which GitHub never reveals, come from the
environment when the snapshot is checked back. The written file, fed back to a check run against
the same state, reads clean.
settings: {}
live_state:
actions_secrets:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: actions-variables-snapshot-roundtrip
description: "mode: snapshot reads the live actions_variables back into snapshot.yml and writes nothing to GitHub. The actions_variables read back as name and value, the timestamps dropped. The written file, fed back to a check run against the same state, reads clean."
description: >-
mode: snapshot reads the live actions_variables back into snapshot.yml and writes nothing to
GitHub. The actions_variables read back as name and value, the timestamps dropped. The written
file, fed back to a check run against the same state, reads clean.
settings: {}
live_state:
actions_variables:
Expand Down
9 changes: 5 additions & 4 deletions src/sections/agents_secrets/agents_secrets.docs.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
sections_table:
endpoints: agents secrets list + public-key + sealed PUT + delete
endpoints: agents secrets list + public-key (read at apply) + sealed PUT + delete
notes: as `actions_secrets`, over the Copilot agents secret store
coverage:
- area: "[Copilot agents secrets](https://docs.github.com/en/rest/agents/secrets)"
notes: >-
GET /repos/{owner}/{repo}/agents/secrets, GET .../agents/secrets/public-key, sealed PUT/DELETE
.../agents/secrets/{secret_name}. Same shape, sealing, and existence-only semantics as
`actions_secrets`, over the Copilot agents secret store. (GET .../agents/organization-secrets
GET /repos/{owner}/{repo}/agents/secrets, GET .../agents/secrets/public-key (read at apply),
sealed PUT/DELETE .../agents/secrets/{secret_name}. Same shape, sealing, and existence-only
semantics as `actions_secrets`, over the Copilot agents secret store. (GET
.../agents/organization-secrets
is a read-only view of org-inherited secrets, not a reconciliation target.) Undeclared secrets
kept by default; `_undeclared: delete` opts into deletion.
Snapshot lists the secret names as per-store `$SECRET_<STORE>_<NAME>` references with one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ expect:
never:
- "PUT /repos/{repo}/agents/secrets"
- "DELETE /repos/{repo}/agents/secrets"
- "GET /repos/{repo}/agents/secrets/public-key"
stdout_contains:
- "drift: agents_secrets[AGENT_TOKEN]: missing"
- values cannot be read back from GitHub
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: agents-secrets-snapshot-roundtrip
description: "mode: snapshot reads the live agents_secrets back into snapshot.yml and writes nothing to GitHub. The agents_secrets names read back as per-store $SECRET_AGENTS_<name> references, one note each in the file header and as a notice; the values, which GitHub never reveals, come from the environment when the snapshot is checked back. The written file, fed back to a check run against the same state, reads clean."
description: >-
mode: snapshot reads the live agents_secrets back into snapshot.yml and writes nothing to GitHub.
The agents_secrets names read back as per-store $SECRET_AGENTS_<name> references, one note each in
the file header and as a notice; the values, which GitHub never reveals, come from the environment
when the snapshot is checked back. The written file, fed back to a check run against the same
state, reads clean.
settings: {}
live_state:
agents_secrets:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: agents-variables-snapshot-roundtrip
description: "mode: snapshot reads the live agents_variables back into snapshot.yml and writes nothing to GitHub. The agents_variables read back as name and value, the timestamps dropped. The written file, fed back to a check run against the same state, reads clean."
description: >-
mode: snapshot reads the live agents_variables back into snapshot.yml and writes nothing to
GitHub. The agents_variables read back as name and value, the timestamps dropped. The written
file, fed back to a check run against the same state, reads clean.
settings: {}
live_state:
agents_variables:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: autolinks-snapshot-roundtrip
description: "mode: snapshot reads the live autolinks back into snapshot.yml and writes nothing to GitHub. A live autolink reads back with the is_alphanumeric flag GitHub reports. The written file, fed back to a check run against the same state, reads clean."
description: >-
mode: snapshot reads the live autolinks back into snapshot.yml and writes nothing to GitHub. A
live autolink reads back with the is_alphanumeric flag GitHub reports. The written file, fed back
to a check run against the same state, reads clean.
settings: {}
live_state:
autolinks:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ expect:
requests_contain:
- "PUT /repos/e2e-owner/e2e-repo/branches/release/protection"
stdout_contains:
- '::error::branches: replacing protection for branch "release" failed - PUT /repos/e2e-owner/e2e-repo/branches/release/protection: 404 Branch not found. The declared branch does not exist on the repo, so its protection cannot be applied; create the branch, or remove it from the settings file'
- >-
::error::branches: replacing protection for branch "release" failed - PUT
/repos/e2e-owner/e2e-repo/branches/release/protection: 404 Branch not found. The declared branch
does not exist on the repo, so its protection cannot be applied; create the branch, or remove it
from the settings file
summary_contains:
- "404 Branch not found"
summary_lacks:
Expand Down
Loading
Loading