From cd8b83500ecc418648e31718cb249c5dc716d565 Mon Sep 17 00:00:00 2001 From: Vivswan Shah <58091053+Vivswan@users.noreply.github.com> Date: Sun, 13 Sep 2026 07:14:53 -0400 Subject: [PATCH 1/6] style(scenarios): fold descriptions over the 256 cap --- .../actions/scenarios/actions-snapshot-roundtrip.yml | 8 +++++++- .../scenarios/actions-secrets-snapshot-roundtrip.yml | 7 ++++++- .../scenarios/actions-variables-snapshot-roundtrip.yml | 5 ++++- .../scenarios/agents-secrets-snapshot-roundtrip.yml | 7 ++++++- .../scenarios/agents-variables-snapshot-roundtrip.yml | 5 ++++- .../autolinks/scenarios/autolinks-snapshot-roundtrip.yml | 5 ++++- .../branches-missing-branch-no-contents-apply-fails.yml | 6 +++++- ...anches-missing-branch-no-contents-apply-warn-fails.yml | 6 +++++- .../scenarios/code-quality-setup-snapshot-roundtrip.yml | 6 +++++- .../code-scanning-default-setup-snapshot-roundtrip.yml | 6 +++++- .../scenarios/codespaces-secrets-snapshot-roundtrip.yml | 7 ++++++- .../collaborators-snapshot-expired-invitation.yml | 6 +++++- .../scenarios/collaborators-snapshot-roundtrip.yml | 7 ++++++- .../scenarios/custom-properties-snapshot-roundtrip.yml | 5 ++++- .../scenarios/dependabot-secrets-snapshot-roundtrip.yml | 7 ++++++- .../scenarios/deploy-keys-snapshot-roundtrip.yml | 5 ++++- .../scenarios/interaction-limits-snapshot-roundtrip.yml | 6 +++++- .../labels/scenarios/labels-snapshot-roundtrip.yml | 6 +++++- .../scenarios/milestones-snapshot-roundtrip.yml | 5 ++++- src/sections/pages/scenarios/pages-snapshot-roundtrip.yml | 6 +++++- .../scenarios/repository-snapshot-roundtrip.yml | 7 ++++++- .../scenarios/rulesets-snapshot-bypass-hidden.yml | 7 ++++++- .../rulesets/scenarios/rulesets-snapshot-roundtrip.yml | 7 ++++++- ...secret-scanning-custom-patterns-snapshot-roundtrip.yml | 6 +++++- src/sections/teams/scenarios/teams-snapshot-roundtrip.yml | 7 ++++++- .../webhooks/scenarios/webhooks-snapshot-roundtrip.yml | 7 ++++++- .../workflows/scenarios/workflows-snapshot-roundtrip.yml | 6 +++++- test/e2e/scenarios/multi-contents-denied.yml | 6 +++++- test/e2e/scenarios/snapshot-dir-multi-repo.yml | 7 ++++++- test/e2e/scenarios/snapshot-unsupported-and-denied.yml | 7 ++++++- 30 files changed, 158 insertions(+), 30 deletions(-) diff --git a/src/sections/actions/scenarios/actions-snapshot-roundtrip.yml b/src/sections/actions/scenarios/actions-snapshot-roundtrip.yml index e11274dc..1e2755f9 100644 --- a/src/sections/actions/scenarios/actions-snapshot-roundtrip.yml +++ b/src/sections/actions/scenarios/actions-snapshot-roundtrip.yml @@ -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: diff --git a/src/sections/actions_secrets/scenarios/actions-secrets-snapshot-roundtrip.yml b/src/sections/actions_secrets/scenarios/actions-secrets-snapshot-roundtrip.yml index a49a423f..75da9d5e 100644 --- a/src/sections/actions_secrets/scenarios/actions-secrets-snapshot-roundtrip.yml +++ b/src/sections/actions_secrets/scenarios/actions-secrets-snapshot-roundtrip.yml @@ -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_ 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_ 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: diff --git a/src/sections/actions_variables/scenarios/actions-variables-snapshot-roundtrip.yml b/src/sections/actions_variables/scenarios/actions-variables-snapshot-roundtrip.yml index 0b0fca4e..6ce7a96a 100644 --- a/src/sections/actions_variables/scenarios/actions-variables-snapshot-roundtrip.yml +++ b/src/sections/actions_variables/scenarios/actions-variables-snapshot-roundtrip.yml @@ -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: diff --git a/src/sections/agents_secrets/scenarios/agents-secrets-snapshot-roundtrip.yml b/src/sections/agents_secrets/scenarios/agents-secrets-snapshot-roundtrip.yml index 97f5f765..fc213d17 100644 --- a/src/sections/agents_secrets/scenarios/agents-secrets-snapshot-roundtrip.yml +++ b/src/sections/agents_secrets/scenarios/agents-secrets-snapshot-roundtrip.yml @@ -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_ 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_ 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: diff --git a/src/sections/agents_variables/scenarios/agents-variables-snapshot-roundtrip.yml b/src/sections/agents_variables/scenarios/agents-variables-snapshot-roundtrip.yml index eec2c5cd..4cf6c132 100644 --- a/src/sections/agents_variables/scenarios/agents-variables-snapshot-roundtrip.yml +++ b/src/sections/agents_variables/scenarios/agents-variables-snapshot-roundtrip.yml @@ -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: diff --git a/src/sections/autolinks/scenarios/autolinks-snapshot-roundtrip.yml b/src/sections/autolinks/scenarios/autolinks-snapshot-roundtrip.yml index 0949f5b9..6ea4e38c 100644 --- a/src/sections/autolinks/scenarios/autolinks-snapshot-roundtrip.yml +++ b/src/sections/autolinks/scenarios/autolinks-snapshot-roundtrip.yml @@ -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: diff --git a/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-fails.yml b/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-fails.yml index 03e66ae0..9fc1f18b 100644 --- a/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-fails.yml +++ b/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-fails.yml @@ -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: diff --git a/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-warn-fails.yml b/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-warn-fails.yml index 64a03bd1..60e2eff9 100644 --- a/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-warn-fails.yml +++ b/src/sections/branches/scenarios/branches-missing-branch-no-contents-apply-warn-fails.yml @@ -27,7 +27,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 stdout_lacks: - "::warning::branches: skipped" summary_lacks: diff --git a/src/sections/code_quality_setup/scenarios/code-quality-setup-snapshot-roundtrip.yml b/src/sections/code_quality_setup/scenarios/code-quality-setup-snapshot-roundtrip.yml index b75080db..021b082f 100644 --- a/src/sections/code_quality_setup/scenarios/code-quality-setup-snapshot-roundtrip.yml +++ b/src/sections/code_quality_setup/scenarios/code-quality-setup-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: code-quality-setup-snapshot-roundtrip -description: "mode: snapshot reads the live code_quality_setup back into snapshot.yml and writes nothing to GitHub. The not-configured default reads back with the null runner_type omitted (the slice takes no null there) and the nullable runner_label kept. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live code_quality_setup back into snapshot.yml and writes nothing to + GitHub. The not-configured default reads back with the null runner_type omitted (the slice takes + no null there) and the nullable runner_label kept. The written file, fed back to a check run + against the same state, reads clean. settings: {} live_state: {} inputs: diff --git a/src/sections/code_scanning_default_setup/scenarios/code-scanning-default-setup-snapshot-roundtrip.yml b/src/sections/code_scanning_default_setup/scenarios/code-scanning-default-setup-snapshot-roundtrip.yml index 01d0525c..311bc656 100644 --- a/src/sections/code_scanning_default_setup/scenarios/code-scanning-default-setup-snapshot-roundtrip.yml +++ b/src/sections/code_scanning_default_setup/scenarios/code-scanning-default-setup-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: code-scanning-default-setup-snapshot-roundtrip -description: "mode: snapshot reads the live code_scanning_default_setup back into snapshot.yml and writes nothing to GitHub. A configured default setup reads back as its declarable fields; updated_at and the schedule fall away. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live code_scanning_default_setup back into snapshot.yml and writes + nothing to GitHub. A configured default setup reads back as its declarable fields; updated_at and + the schedule fall away. The written file, fed back to a check run against the same state, reads + clean. settings: {} live_state: code_scanning: diff --git a/src/sections/codespaces_secrets/scenarios/codespaces-secrets-snapshot-roundtrip.yml b/src/sections/codespaces_secrets/scenarios/codespaces-secrets-snapshot-roundtrip.yml index d290aaf1..9527eb87 100644 --- a/src/sections/codespaces_secrets/scenarios/codespaces-secrets-snapshot-roundtrip.yml +++ b/src/sections/codespaces_secrets/scenarios/codespaces-secrets-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: codespaces-secrets-snapshot-roundtrip -description: "mode: snapshot reads the live codespaces_secrets back into snapshot.yml and writes nothing to GitHub. The codespaces_secrets names read back as per-store $SECRET_CODESPACES_ 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 codespaces_secrets back into snapshot.yml and writes nothing to + GitHub. The codespaces_secrets names read back as per-store $SECRET_CODESPACES_ 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: codespaces_secrets: diff --git a/src/sections/collaborators/scenarios/collaborators-snapshot-expired-invitation.yml b/src/sections/collaborators/scenarios/collaborators-snapshot-expired-invitation.yml index da1d63c9..bc2d4d09 100644 --- a/src/sections/collaborators/scenarios/collaborators-snapshot-expired-invitation.yml +++ b/src/sections/collaborators/scenarios/collaborators-snapshot-expired-invitation.yml @@ -1,5 +1,9 @@ name: collaborators-snapshot-expired-invitation -description: "mode: snapshot omits an expired invitation with a note instead of declaring it: a declared entry would be cancelled and re-sent on apply, and the undeclared one is cancelled under the delete default the snapshot carries, so the file cannot read clean against this state. The accepted collaborator still reads back." +description: >- + mode: snapshot omits an expired invitation with a note instead of declaring it: a declared entry + would be cancelled and re-sent on apply, and the undeclared one is cancelled under the delete + default the snapshot carries, so the file cannot read clean against this state. The accepted + collaborator still reads back. settings: {} live_state: collaborators: diff --git a/src/sections/collaborators/scenarios/collaborators-snapshot-roundtrip.yml b/src/sections/collaborators/scenarios/collaborators-snapshot-roundtrip.yml index 1a780237..b54dfa9d 100644 --- a/src/sections/collaborators/scenarios/collaborators-snapshot-roundtrip.yml +++ b/src/sections/collaborators/scenarios/collaborators-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: collaborators-snapshot-roundtrip -description: "mode: snapshot reads the live direct collaborators and pending username invitations back into snapshot.yml and writes nothing to GitHub. Roles read back through the inverse of the declared vocabulary (write -> push, read -> pull, a custom role by name); the repository owner and the email invitation are noted, not declared. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live direct collaborators and pending username invitations back into + snapshot.yml and writes nothing to GitHub. Roles read back through the inverse of the declared + vocabulary (write -> push, read -> pull, a custom role by name); the repository owner and the + email invitation are noted, not declared. The written file, fed back to a check run against the + same state, reads clean. settings: {} live_state: collaborators: diff --git a/src/sections/custom_properties/scenarios/custom-properties-snapshot-roundtrip.yml b/src/sections/custom_properties/scenarios/custom-properties-snapshot-roundtrip.yml index 3e73d4a6..01a898fc 100644 --- a/src/sections/custom_properties/scenarios/custom-properties-snapshot-roundtrip.yml +++ b/src/sections/custom_properties/scenarios/custom-properties-snapshot-roundtrip.yml @@ -1,5 +1,8 @@ name: custom-properties-snapshot-roundtrip -description: "mode: snapshot reads the live custom_properties back into snapshot.yml and writes nothing to GitHub. Set property values read back; a property with a null value is unset and omitted. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live custom_properties back into snapshot.yml and writes nothing to + GitHub. Set property values read back; a property with a null value is unset and omitted. The + written file, fed back to a check run against the same state, reads clean. settings: {} live_state: custom_property_values: diff --git a/src/sections/dependabot_secrets/scenarios/dependabot-secrets-snapshot-roundtrip.yml b/src/sections/dependabot_secrets/scenarios/dependabot-secrets-snapshot-roundtrip.yml index 3697543f..7fb3627e 100644 --- a/src/sections/dependabot_secrets/scenarios/dependabot-secrets-snapshot-roundtrip.yml +++ b/src/sections/dependabot_secrets/scenarios/dependabot-secrets-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: dependabot-secrets-snapshot-roundtrip -description: "mode: snapshot reads the live dependabot_secrets back into snapshot.yml and writes nothing to GitHub. The dependabot_secrets names read back as per-store $SECRET_DEPENDABOT_ 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 dependabot_secrets back into snapshot.yml and writes nothing to + GitHub. The dependabot_secrets names read back as per-store $SECRET_DEPENDABOT_ 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: dependabot_secrets: diff --git a/src/sections/deploy_keys/scenarios/deploy-keys-snapshot-roundtrip.yml b/src/sections/deploy_keys/scenarios/deploy-keys-snapshot-roundtrip.yml index e934f6bd..fa7748b3 100644 --- a/src/sections/deploy_keys/scenarios/deploy-keys-snapshot-roundtrip.yml +++ b/src/sections/deploy_keys/scenarios/deploy-keys-snapshot-roundtrip.yml @@ -1,5 +1,8 @@ name: deploy-keys-snapshot-roundtrip -description: "mode: snapshot reads the live deploy_keys back into snapshot.yml and writes nothing to GitHub. Live deploy keys read back by title, key (the comment GitHub drops is dropped here too), and read_only flag. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live deploy_keys back into snapshot.yml and writes nothing to GitHub. + Live deploy keys read back by title, key (the comment GitHub drops is dropped here too), and + read_only flag. The written file, fed back to a check run against the same state, reads clean. settings: {} live_state: deploy_keys: diff --git a/src/sections/interaction_limits/scenarios/interaction-limits-snapshot-roundtrip.yml b/src/sections/interaction_limits/scenarios/interaction-limits-snapshot-roundtrip.yml index d8151aad..32f1316d 100644 --- a/src/sections/interaction_limits/scenarios/interaction-limits-snapshot-roundtrip.yml +++ b/src/sections/interaction_limits/scenarios/interaction-limits-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: interaction-limits-snapshot-roundtrip -description: "mode: snapshot reads the live interaction_limits back into snapshot.yml and writes nothing to GitHub. The live limit, the pull-request creation cap, and its bypass list read back; the expiry cannot (GitHub reports only the computed expires_at), which the header notes. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live interaction_limits back into snapshot.yml and writes nothing to + GitHub. The live limit, the pull-request creation cap, and its bypass list read back; the expiry + cannot (GitHub reports only the computed expires_at), which the header notes. The written file, + fed back to a check run against the same state, reads clean. settings: {} live_state: interaction_limits: diff --git a/src/sections/labels/scenarios/labels-snapshot-roundtrip.yml b/src/sections/labels/scenarios/labels-snapshot-roundtrip.yml index 9672d24c..4d26617c 100644 --- a/src/sections/labels/scenarios/labels-snapshot-roundtrip.yml +++ b/src/sections/labels/scenarios/labels-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: labels-snapshot-roundtrip -description: "mode: snapshot reads the live labels back into snapshot.yml and writes nothing to GitHub. Every live label reads back with its color lower-cased and GitHub's null description spelled as the empty string the file takes. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live labels back into snapshot.yml and writes nothing to GitHub. Every + live label reads back with its color lower-cased and GitHub's null description spelled as the + empty string the file takes. The written file, fed back to a check run against the same state, + reads clean. settings: {} live_state: labels: diff --git a/src/sections/milestones/scenarios/milestones-snapshot-roundtrip.yml b/src/sections/milestones/scenarios/milestones-snapshot-roundtrip.yml index 5022148b..1ed2ef9f 100644 --- a/src/sections/milestones/scenarios/milestones-snapshot-roundtrip.yml +++ b/src/sections/milestones/scenarios/milestones-snapshot-roundtrip.yml @@ -1,5 +1,8 @@ name: milestones-snapshot-roundtrip -description: "mode: snapshot reads the live milestones back into snapshot.yml and writes nothing to GitHub. Open and closed milestones read back by title, description, and state; a null description is omitted. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live milestones back into snapshot.yml and writes nothing to GitHub. Open + and closed milestones read back by title, description, and state; a null description is omitted. + The written file, fed back to a check run against the same state, reads clean. settings: {} live_state: milestones: diff --git a/src/sections/pages/scenarios/pages-snapshot-roundtrip.yml b/src/sections/pages/scenarios/pages-snapshot-roundtrip.yml index 0fb95d69..f1fb629b 100644 --- a/src/sections/pages/scenarios/pages-snapshot-roundtrip.yml +++ b/src/sections/pages/scenarios/pages-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: pages-snapshot-roundtrip -description: "mode: snapshot reads the live pages back into snapshot.yml and writes nothing to GitHub. The live Pages site reads back as its declarable fields; the server-owned ones (url, status, html_url, the certificate) fall away. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live pages back into snapshot.yml and writes nothing to GitHub. The live + Pages site reads back as its declarable fields; the server-owned ones (url, status, html_url, the + certificate) fall away. The written file, fed back to a check run against the same state, reads + clean. settings: {} live_state: pages: diff --git a/src/sections/repository/scenarios/repository-snapshot-roundtrip.yml b/src/sections/repository/scenarios/repository-snapshot-roundtrip.yml index 57e2bc27..04903bee 100644 --- a/src/sections/repository/scenarios/repository-snapshot-roundtrip.yml +++ b/src/sections/repository/scenarios/repository-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: repository-snapshot-roundtrip -description: "mode: snapshot reads the live repository back into snapshot.yml and writes nothing to GitHub. The GET's documented PATCH fields, the topics, every readable security toggle, and the two GraphQL-only keys read back; ids, urls, counts, the owner, the license, and has_downloads fall away, and Git LFS is noted since GitHub exposes no read for it. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live repository back into snapshot.yml and writes nothing to GitHub. The + GET's documented PATCH fields, the topics, every readable security toggle, and the two + GraphQL-only keys read back; ids, urls, counts, the owner, the license, and has_downloads fall + away, and Git LFS is noted since GitHub exposes no read for it. The written file, fed back to a + check run against the same state, reads clean. settings: {} live_state: repo: diff --git a/src/sections/rulesets/scenarios/rulesets-snapshot-bypass-hidden.yml b/src/sections/rulesets/scenarios/rulesets-snapshot-bypass-hidden.yml index 99618f5f..042e0912 100644 --- a/src/sections/rulesets/scenarios/rulesets-snapshot-bypass-hidden.yml +++ b/src/sections/rulesets/scenarios/rulesets-snapshot-bypass-hidden.yml @@ -1,5 +1,10 @@ name: rulesets-snapshot-bypass-hidden -description: "mode: snapshot under a read-grade token, whose ruleset GETs omit bypass_actors. An entry without that key would clear the bypass list on the next full-payload update, so every repository ruleset is left out with a note and the section writes the empty keep wrapper, never a false 'nothing exists'; the organization's inherited ruleset is a note too. The written file, fed back to a check run against the same state, reads clean without a write." +description: >- + mode: snapshot under a read-grade token, whose ruleset GETs omit bypass_actors. An entry without + that key would clear the bypass list on the next full-payload update, so every repository ruleset + is left out with a note and the section writes the empty keep wrapper, never a false 'nothing + exists'; the organization's inherited ruleset is a note too. The written file, fed back to a check + run against the same state, reads clean without a write. settings: {} live_state: rulesets: diff --git a/src/sections/rulesets/scenarios/rulesets-snapshot-roundtrip.yml b/src/sections/rulesets/scenarios/rulesets-snapshot-roundtrip.yml index 4e653f25..8a60cbf5 100644 --- a/src/sections/rulesets/scenarios/rulesets-snapshot-roundtrip.yml +++ b/src/sections/rulesets/scenarios/rulesets-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: rulesets-snapshot-roundtrip -description: "mode: snapshot reads the live rulesets back into snapshot.yml and writes nothing to GitHub. Each repository-owned ruleset reads back as a keep entry with its target, enforcement, ref conditions, rules, and the bypass_actors the write-grade token sees; ids, the source fields, links, and timestamps fall away, and the organization's inherited ruleset is a note, not an entry. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live rulesets back into snapshot.yml and writes nothing to GitHub. Each + repository-owned ruleset reads back as a keep entry with its target, enforcement, ref conditions, + rules, and the bypass_actors the write-grade token sees; ids, the source fields, links, and + timestamps fall away, and the organization's inherited ruleset is a note, not an entry. The + written file, fed back to a check run against the same state, reads clean. settings: {} live_state: rulesets: diff --git a/src/sections/secret_scanning_custom_patterns/scenarios/secret-scanning-custom-patterns-snapshot-roundtrip.yml b/src/sections/secret_scanning_custom_patterns/scenarios/secret-scanning-custom-patterns-snapshot-roundtrip.yml index 7e3fc620..a9e9acbf 100644 --- a/src/sections/secret_scanning_custom_patterns/scenarios/secret-scanning-custom-patterns-snapshot-roundtrip.yml +++ b/src/sections/secret_scanning_custom_patterns/scenarios/secret-scanning-custom-patterns-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: secret-scanning-custom-patterns-snapshot-roundtrip -description: "mode: snapshot reads the live secret_scanning_custom_patterns back into snapshot.yml and writes nothing to GitHub. A published custom pattern reads back as its declarable fields; the server-assigned id, slug, version, and state fall away. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live secret_scanning_custom_patterns back into snapshot.yml and writes + nothing to GitHub. A published custom pattern reads back as its declarable fields; the + server-assigned id, slug, version, and state fall away. The written file, fed back to a check run + against the same state, reads clean. settings: {} live_state: secret_scanning_patterns: diff --git a/src/sections/teams/scenarios/teams-snapshot-roundtrip.yml b/src/sections/teams/scenarios/teams-snapshot-roundtrip.yml index 2940c51d..2cdb30d1 100644 --- a/src/sections/teams/scenarios/teams-snapshot-roundtrip.yml +++ b/src/sections/teams/scenarios/teams-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: teams-snapshot-roundtrip -description: "mode: snapshot lists the teams with access to an organization repo, reads each team's role through the access probe, and writes nothing to GitHub. Roles read back through the inverse of the declared vocabulary (write -> push, read -> pull, a custom role by name); a custom role literally named push has no declaration that plans as itself, so that team is noted, not declared. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot lists the teams with access to an organization repo, reads each team's role through + the access probe, and writes nothing to GitHub. Roles read back through the inverse of the + declared vocabulary (write -> push, read -> pull, a custom role by name); a custom role literally + named push has no declaration that plans as itself, so that team is noted, not declared. The + written file, fed back to a check run against the same state, reads clean. settings: {} owner_kind: org live_state: diff --git a/src/sections/webhooks/scenarios/webhooks-snapshot-roundtrip.yml b/src/sections/webhooks/scenarios/webhooks-snapshot-roundtrip.yml index 2b6c2ea8..34c3b1fa 100644 --- a/src/sections/webhooks/scenarios/webhooks-snapshot-roundtrip.yml +++ b/src/sections/webhooks/scenarios/webhooks-snapshot-roundtrip.yml @@ -1,5 +1,10 @@ name: webhooks-snapshot-roundtrip -description: "mode: snapshot reads the live webhooks back into snapshot.yml and writes nothing to GitHub. Live webhooks read back with their config, events, and active flag; the secret GitHub never reveals becomes a $SECRET_WEBHOOK_ reference with a note, and the value comes 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 webhooks back into snapshot.yml and writes nothing to GitHub. Live + webhooks read back with their config, events, and active flag; the secret GitHub never reveals + becomes a $SECRET_WEBHOOK_ reference with a note, and the value comes 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: hooks: diff --git a/src/sections/workflows/scenarios/workflows-snapshot-roundtrip.yml b/src/sections/workflows/scenarios/workflows-snapshot-roundtrip.yml index 79cbe5ca..291de3a6 100644 --- a/src/sections/workflows/scenarios/workflows-snapshot-roundtrip.yml +++ b/src/sections/workflows/scenarios/workflows-snapshot-roundtrip.yml @@ -1,5 +1,9 @@ name: workflows-snapshot-roundtrip -description: "mode: snapshot reads the live workflows back into snapshot.yml and writes nothing to GitHub. Active and manually disabled workflows read back by path and state; a deleted one has no file to speak for and is omitted. The written file, fed back to a check run against the same state, reads clean." +description: >- + mode: snapshot reads the live workflows back into snapshot.yml and writes nothing to GitHub. + Active and manually disabled workflows read back by path and state; a deleted one has no file to + speak for and is omitted. The written file, fed back to a check run against the same state, reads + clean. settings: {} live_state: workflows: diff --git a/test/e2e/scenarios/multi-contents-denied.yml b/test/e2e/scenarios/multi-contents-denied.yml index 7a58a14d..59ea806c 100644 --- a/test/e2e/scenarios/multi-contents-denied.yml +++ b/test/e2e/scenarios/multi-contents-denied.yml @@ -45,7 +45,11 @@ expect: # The proof was attempted: the default branch ref read follows the 404. - "GET /repos/e2e-owner/svc-locked/git/ref/heads/main" stdout_contains: - - "svc-locked: cannot prove .github/settings.yml is absent: reading the default branch ref heads/main returned 404. Grant the token Contents: read on this repository, or initialize its default branch; a repository whose file cannot be read never receives the defaults. To stop managing it instead, remove e2e-owner/svc-locked from the \"repos\" input" + - >- + svc-locked: cannot prove .github/settings.yml is absent: reading the default branch ref heads/main + returned 404. Grant the token Contents: read on this repository, or initialize its default branch; a + repository whose file cannot be read never receives the defaults. To stop managing it instead, + remove e2e-owner/svc-locked from the "repos" input stdout_lacks: - "applying the defaults file" - "skipped - the repository has no" diff --git a/test/e2e/scenarios/snapshot-dir-multi-repo.yml b/test/e2e/scenarios/snapshot-dir-multi-repo.yml index 8d6aec22..57a937d5 100644 --- a/test/e2e/scenarios/snapshot-dir-multi-repo.yml +++ b/test/e2e/scenarios/snapshot-dir-multi-repo.yml @@ -1,5 +1,10 @@ name: snapshot-dir-multi-repo -description: "The dir form: the targets come from the repos input exactly as in a multi-repo apply, and each is written to //.yml, the repos-dir layout. A private target (redacted by default) gets its file like the others, while the public surfaces know it only as its placeholder: its slug, its file name, and its label values appear nowhere in the log, the summary, or the outputs." +description: >- + The dir form: the targets come from the repos input exactly as in a multi-repo apply, and each is + written to //.yml, the repos-dir layout. A private target (redacted by + default) gets its file like the others, while the public surfaces know it only as its placeholder: + its slug, its file name, and its label values appear nowhere in the log, the summary, or the + outputs. settings: {} repos: e2e-owner/svc-a: diff --git a/test/e2e/scenarios/snapshot-unsupported-and-denied.yml b/test/e2e/scenarios/snapshot-unsupported-and-denied.yml index 6fc16b42..aa64f570 100644 --- a/test/e2e/scenarios/snapshot-unsupported-and-denied.yml +++ b/test/e2e/scenarios/snapshot-unsupported-and-denied.yml @@ -1,5 +1,10 @@ name: snapshot-unsupported-and-denied -description: "A snapshot under on-missing-permission: warn with a section the token cannot read and one GitHub exposes no read endpoint for. The denied section is skipped (a warning, the skipped-sections output, the header line) and the run ends partial with exit 0; the unsupported one is a notice and a header line, never a failure; the file carries the section that read back. The document itself never reaches stdout or the summary." +description: >- + A snapshot under on-missing-permission: warn with a section the token cannot read and one GitHub + exposes no read endpoint for. The denied section is skipped (a warning, the skipped-sections + output, the header line) and the run ends partial with exit 0; the unsupported one is a notice and + a header line, never a failure; the file carries the section that read back. The document itself + never reaches stdout or the summary. settings: {} live_state: labels: From 961ef7c7f78d3b8bcf9ec4715599501e88365d72 Mon Sep 17 00:00:00 2001 From: Vivswan Shah <58091053+Vivswan@users.noreply.github.com> Date: Sun, 13 Sep 2026 07:17:37 -0400 Subject: [PATCH 2/6] refactor(sections): one helper per repeated concept across the bespoke sections --- COVERAGE.md | 6 +- docs/upgrading/v2-to-v3.md | 14 ++-- .../collaborators/collaborators.docs.yml | 2 +- .../collaborators/collaborators.test.ts | 12 +-- src/sections/collaborators/index.ts | 49 ++++++----- ...laborators-snapshot-expired-invitation.yml | 2 +- .../collaborators-snapshot-roundtrip.yml | 4 +- src/sections/contract/module.ts | 12 +++ .../custom_properties.test.ts | 2 +- src/sections/custom_properties/index.ts | 46 +++++----- .../custom-properties-personal-account.yml | 2 +- src/sections/environments/branch-policies.ts | 9 +- src/sections/environments/index.ts | 11 ++- src/sections/environments/protection-rules.ts | 12 ++- src/sections/environments/snapshot.ts | 6 +- src/sections/interaction_limits/index.ts | 14 +++- .../interaction_limits.docs.yml | 2 +- .../interaction_limits.test.ts | 2 +- src/sections/repository/index.ts | 29 +++++-- .../secret_scanning_custom_patterns/index.ts | 10 ++- src/sections/shared/list-section.ts | 12 ++- src/sections/shared/org-owner.ts | 38 +++++++++ src/sections/shared/repo-secrets.ts | 7 +- src/sections/shared/roles.ts | 27 ++++++ src/sections/shared/secrets-engine.ts | 5 +- src/sections/shared/snapshot-helpers.ts | 8 ++ src/sections/shared/variables-engine.ts | 5 +- src/sections/teams/index.ts | 83 +++++++++---------- .../scenarios/teams-snapshot-roundtrip.yml | 2 +- src/sections/teams/teams.docs.yml | 2 +- src/sections/teams/teams.test.ts | 12 +-- test/engine/snapshot.test.ts | 2 +- test/scripts/changed-sections.test.ts | 1 + test/sections/snapshot-rows/collaborators.ts | 4 +- test/sections/snapshot-rows/teams.ts | 2 +- 35 files changed, 289 insertions(+), 167 deletions(-) create mode 100644 src/sections/shared/org-owner.ts diff --git a/COVERAGE.md b/COVERAGE.md index 73328175..3b74ae94 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -35,10 +35,10 @@ The tenet: this action can control everything about a repository and nothing abo | [GitHub Pages](https://docs.github.com/en/rest/pages/pages) (build_type, source, cname, https_enforced and any PUT field GitHub adds; pages: null disables the site) | `pages` | POST /repos/{owner}/{repo}/pages (create accepts only build_type/source) then PUT for the rest; existing sites get straight PUT passthrough; pages: null issues DELETE, mirroring branches' protection: null. In a mode: merge fold, pages: null over a lower layer's pages declaration removes the key from the merged document instead (the layering guide owns that rule); a multi-repo defaults-file never merges into a target's file. The Enterprise Cloud site-visibility boolean `public` (public vs repo-members-only) rides the PUT passthrough too - the dotcom docs omit it but the GHEC flavor documents it, and the GET echoes it in both, so declaring it applies and check-verifies today. Snapshot reads the live site back on the declared keys; a repository without Pages is omitted rather than declared null. | | [Code scanning default setup](https://docs.github.com/en/rest/code-scanning/code-scanning) | `code_scanning_default_setup` | GET/PATCH /repos/{owner}/{repo}/code-scanning/default-setup, PATCH body verbatim (state, query_suite, languages, runner_type, runner_label, threat_model). A 202 answer means GitHub rolls the change out in a configuration run, which the log names. Check compares declared keys only, languages as a set. Needs GitHub Advanced Security on private repositories; a 403 can mean that (or an archived repository) rather than a missing permission. Snapshot reads the GET body back on the declared keys; a null the slice does not take (a not-configured setup's runner_type) is omitted. | | [Code quality setup](https://docs.github.com/en/rest/code-quality/code-quality) | `code_quality_setup` | GET/PATCH /repos/{owner}/{repo}/code-quality/setup, PATCH body verbatim (state, languages, runner_type, runner_label, ai_findings_option), the near field-for-field mirror of code_scanning_default_setup. A 202 answer means GitHub rolls the change out in a configuration run, which the log names; a 409 means a configuration run is already in progress (re-run the workflow after it finishes); a 403 can mean code quality is unavailable on the repository or the repository is archived, rather than a missing permission, and a 422 (the change cannot be applied) carries GitHub's message verbatim. Check compares declared keys only, languages as a set. ASSUMPTION: the GET is graded as a plain read under the Administration permission, mirroring code scanning - the fine-grained grading of these endpoints is not verified against production. The sibling /code-quality/findings endpoints are read-only and stay out of scope. Snapshot reads the GET body back on the declared keys; a null the slice does not take (a not-configured setup's runner_type) is omitted. | -| [Collaborators](https://docs.github.com/en/rest/collaborators/collaborators) (direct) | `collaborators` | PUT/DELETE /repos/{owner}/{repo}/collaborators/{username} (affiliation=direct); pending invitations reconciled alongside via GET /repos/{owner}/{repo}/invitations and PATCH/DELETE /repos/{owner}/{repo}/invitations/{invitation_id}: a declared user's matching pending invitation converges, a stale permission is PATCHed in place (a declared custom role cannot be verified against an invitation - the standard-roles enum - so the invitation is kept with a note), an expired invitation is cancelled and re-sent, and undeclared pending invitations follow the section's one undeclared policy (email invitations, which no username can declare, are noted and left untouched); vocabulary mapping push<->write, pull<->read for check mode; custom org role names pass through; undeclared direct collaborators REMOVED by default (kept as notes under the wrapped `_undeclared: keep` form), owner never touched; new users get invitations. Snapshot reads the direct collaborators and the pending username invitations back as entries under the delete default, each role through the inverse mapping (write -> push, read -> pull, custom role names verbatim); the repository owner, email invitations, and expired invitations are noted, not declared, and a live role no declaration plans as (a custom role named "push" or "pull") fails the collaborators section; the snapshot is written as partial without it. | -| [Team repository permissions](https://docs.github.com/en/rest/teams/teams) (org repos) | `teams` | PUT/DELETE /orgs/{org}/teams/{slug}/repos/{owner}/{repo}; probes GET /orgs/{owner} and no-ops with a note on personal accounts (404 only; 403/5xx still fail). Both modes probe each declared team's access with the v3.repository media type to read role_name, and the grant is issued only when the team lacks access or its live role diverges. The repository's team list (GET /repos/{owner}/{repo}/teams) names the undeclared teams: kept by default and noted (a team is often granted by the org for reasons outside one repository's file); under the wrapped `_undeclared: delete` form a team's DIRECT grant is revoked, while access granted at the organization or enterprise level is noted as beyond the repository's reach. A declared child team whose access comes through an undeclared parent's grant loses it with that revocation (the probe reads the inherited role as converged): declare the parent too, or keep. Snapshot lists the same teams and reads each team's role through the same probe, so a custom role reads back by name, and writes the wrapped form with the keep default spelled out; noted, not declared: access granted at the organization or enterprise level, a probe 404 (no access, or a fine-grained token missing the grant; the note names both), a role the probe does not report, and a role no declaration plans as (a custom role named "push" or "pull"). A personal account snapshots nothing. | +| [Collaborators](https://docs.github.com/en/rest/collaborators/collaborators) (direct) | `collaborators` | PUT/DELETE /repos/{owner}/{repo}/collaborators/{username} (affiliation=direct); pending invitations reconciled alongside via GET /repos/{owner}/{repo}/invitations and PATCH/DELETE /repos/{owner}/{repo}/invitations/{invitation_id}: a declared user's matching pending invitation converges, a stale permission is PATCHed in place (a declared custom role cannot be verified against an invitation - the standard-roles enum - so the invitation is kept with a note), an expired invitation is cancelled and re-sent, and undeclared pending invitations follow the section's one undeclared policy (email invitations, which no username can declare, are noted and left untouched); vocabulary mapping push<->write, pull<->read for check mode; custom org role names pass through; undeclared direct collaborators REMOVED by default (kept as notes under the wrapped `_undeclared: keep` form), owner never touched; new users get invitations. Snapshot reads the direct collaborators and the pending username invitations back as entries under the delete default, each role through the inverse mapping (write -> push, read -> pull, custom role names verbatim); the repository owner, email invitations, and expired invitations are left out with a note, and a live role no declaration plans as (a custom role named "push" or "pull") fails the collaborators section; the snapshot is written as partial without it. | +| [Team repository permissions](https://docs.github.com/en/rest/teams/teams) (org repos) | `teams` | PUT/DELETE /orgs/{org}/teams/{slug}/repos/{owner}/{repo}; probes GET /orgs/{owner} and no-ops with a note on personal accounts (404 only; 403/5xx still fail). Both modes probe each declared team's access with the v3.repository media type to read role_name, and the grant is issued only when the team lacks access or its live role diverges. The repository's team list (GET /repos/{owner}/{repo}/teams) names the undeclared teams: kept by default and noted (a team is often granted by the org for reasons outside one repository's file); under the wrapped `_undeclared: delete` form a team's DIRECT grant is revoked, while access granted at the organization or enterprise level is noted as beyond the repository's reach. A declared child team whose access comes through an undeclared parent's grant loses it with that revocation (the probe reads the inherited role as converged): declare the parent too, or keep. Snapshot lists the same teams and reads each team's role through the same probe, so a custom role reads back by name, and writes the wrapped form with the keep default spelled out; left out with a note: access granted at the organization or enterprise level, a probe 404 (no access, or a fine-grained token missing the grant; the note names both), a role the probe does not report, and a role no declaration plans as (a custom role named "push" or "pull"). A personal account snapshots nothing. | | [Milestones](https://docs.github.com/en/rest/issues/milestones) | `milestones` | POST/PATCH/DELETE /repos/{owner}/{repo}/milestones, matched by title, declared-keys-only (description/state untouched unless declared); undeclared milestones kept by default and surfaced as notes, because deleting a milestone DETACHES it from every issue carrying it - the wrapped `_undeclared: delete` form opts into exactly that. Snapshot reads every milestone, open and closed, back as entries under the keep default. | -| [Interaction limits](https://docs.github.com/en/rest/interactions/repos) | `interaction_limits` | GET/PUT/DELETE /repos/{owner}/{repo}/interaction-limits, plus the two routed keys below. Limits self-expire (expiry tops out at six_months), so apply re-arms the declared limit on EVERY run and check mode reports drift once it lapses - schedule apply more often than the chosen expiry to keep it armed. The declared expiry is write-only (GitHub reads back only the computed expires_at), so check verifies the limit value, not the duration. `interaction_limits: null` clears a live repo-level limit - the base limit only, never the cap or bypass list below. In a mode: merge fold, interaction_limits: null over a lower layer's declaration removes the key from the merged document instead, mirroring pages: null. A 409 means an organization- or user-level limit overrides the repository's; writes surface that as a note, not a failure, while check mode still reports a mismatched declaration as drift - the org is the place to change it. `pull_request_creation_cap` routes to GET/PATCH /repos/{owner}/{repo}/interaction-limits/pulls/creation-cap: persistent desired state with no self-expiry (enabled plus max_open_pull_requests 1-1000, read back verbatim), so check diffs it exactly and apply PATCHes only on divergence - no re-arm. Where the cap is unavailable the endpoints answer 405, which apply surfaces as a note (like the 409) and check as honest drift. `pull_request_creation_bypass` routes to GET/PUT/DELETE /repos/{owner}/{repo}/interaction-limits/pulls/bypass-list and reconciles: apply DELETEs only the undeclared logins and then PUTs only the missing ones ({users: [logins]}, compared case-insensitively; removals go first because the list holds at most 100 users, which also caps the declaration), never a wholesale replace; a declared empty list removes everyone. Snapshot reads back the repository-level limit (an inherited one is noted, not declared, and the expiry duration is not readable), the cap when the feature exists and is enabled, and the bypass list when it has members. | +| [Interaction limits](https://docs.github.com/en/rest/interactions/repos) | `interaction_limits` | GET/PUT/DELETE /repos/{owner}/{repo}/interaction-limits, plus the two routed keys below. Limits self-expire (expiry tops out at six_months), so apply re-arms the declared limit on EVERY run and check mode reports drift once it lapses - schedule apply more often than the chosen expiry to keep it armed. The declared expiry is write-only (GitHub reads back only the computed expires_at), so check verifies the limit value, not the duration. `interaction_limits: null` clears a live repo-level limit - the base limit only, never the cap or bypass list below. In a mode: merge fold, interaction_limits: null over a lower layer's declaration removes the key from the merged document instead, mirroring pages: null. A 409 means an organization- or user-level limit overrides the repository's; writes surface that as a note, not a failure, while check mode still reports a mismatched declaration as drift - the org is the place to change it. `pull_request_creation_cap` routes to GET/PATCH /repos/{owner}/{repo}/interaction-limits/pulls/creation-cap: persistent desired state with no self-expiry (enabled plus max_open_pull_requests 1-1000, read back verbatim), so check diffs it exactly and apply PATCHes only on divergence - no re-arm. Where the cap is unavailable the endpoints answer 405, which apply surfaces as a note (like the 409) and check as honest drift. `pull_request_creation_bypass` routes to GET/PUT/DELETE /repos/{owner}/{repo}/interaction-limits/pulls/bypass-list and reconciles: apply DELETEs only the undeclared logins and then PUTs only the missing ones ({users: [logins]}, compared case-insensitively; removals go first because the list holds at most 100 users, which also caps the declaration), never a wholesale replace; a declared empty list removes everyone. Snapshot reads back the repository-level limit (an inherited one is left out with a note, and the expiry duration is not readable), the cap when the feature exists and is enabled, and the bypass list when it has members. | | [Actions variables](https://docs.github.com/en/rest/actions/variables) | `actions_variables` | GET/POST /repos/{owner}/{repo}/actions/variables and PATCH/DELETE /repos/{owner}/{repo}/actions/variables/{name}: plain-text repository variables, upserted by name. Names are case-insensitive (GitHub stores them uppercased regardless of how they are entered), so matching and duplicate rejection compare uppercased names. Values read back in full, which is what makes exact check-mode diffing possible; undeclared variables are DELETED by default, kept as notes under the wrapped `_undeclared: keep` form. The list endpoint caps per_page at 30 (not the usual 100), which the page loop honors so a large inventory is never truncated. Secrets are write-only material and deliberately NOT this section: they live in the `actions_secrets` section above. Snapshot reads the variables back as name and value entries under the delete default. | | [Copilot agents variables](https://docs.github.com/en/rest/agents/variables) | `agents_variables` | GET/POST /repos/{owner}/{repo}/agents/variables and PATCH/DELETE /repos/{owner}/{repo}/agents/variables/{name}: same upsert-by-uppercase-name semantics, exact check-mode diffing, and 30-item per_page cap as `actions_variables`, over the Copilot agents variable store. (GET .../agents/organization-variables is the read-only org-inherited view, out of scope.) Undeclared variables are DELETED by default, kept as notes under the wrapped `_undeclared: keep` form. CAVEAT: GitHub auto-migrated values from the older `copilot` Actions environment into this store, so a repository can hold agents variables nobody created here - run `mode: check` first (it lists them as deletion drift) before the first apply, or declare `_undeclared: keep`. Snapshot reads the variables back as name and value entries under the delete default. | | [Webhooks](https://docs.github.com/en/rest/repos/webhooks) | `webhooks` | GET/POST /repos/{owner}/{repo}/hooks, PATCH/DELETE .../hooks/{hook_id}, and PATCH .../hooks/{hook_id}/config. At most ONE web hook per config.url (the natural key); two live hooks under one url fail loudly, and a changed url is a NEW hook (the old one turns _undeclared: kept and noted by default, deleted under `_undeclared: delete`). A legacy service hook (name other than "web") or a hook without a config.url is outside the section: plan neither matches nor deletes it, snapshot notes it. Config-field drift goes through the config sub-endpoint, which updates named fields without the general PATCH's whole-config replacement, so a live secret the file does not declare is never removed; events/active drift rides the general PATCH with no config key. config.secret is declared as a whole-value `$NAME` reference resolved from the step env at apply time ([secrets and vaults](docs/reference/secrets-and-vaults.md)); GitHub echoes a live secret as "********", so check mode verifies everything except the secret (a cannot-verify note) and apply re-sends the declared secret every run so rotations propagate. insecure_ssl compares number and string spellings as equal (GitHub stores strings); events compare as a set. Hook urls appear in drift and change lines on purpose: they are configuration, not credentials (the secret is the masked part). Snapshot reads the hooks back as entries under the keep default, a set secret as a `$SECRET_WEBHOOK_` reference with a note to export it. | diff --git a/docs/upgrading/v2-to-v3.md b/docs/upgrading/v2-to-v3.md index 84bd1936..68feb472 100644 --- a/docs/upgrading/v2-to-v3.md +++ b/docs/upgrading/v2-to-v3.md @@ -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_` | `$SECRET_WEBHOOK_` | 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) | @@ -218,12 +218,10 @@ 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 | |---|---|---| @@ -231,7 +229,9 @@ The other cannot-verify and omission notes (interaction_limits, teams, collabora | 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[].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` | `