From baa5dd6bfcf83220ffe9f9e3e0987bf9e5cc6f33 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:35:29 +0000 Subject: [PATCH 1/2] chore(main): release 3.0.0 --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 205 +++++++++++++++++++++++++ README.md | 6 +- docs/operate/check-mode.md | 4 +- docs/operate/layering.md | 4 +- docs/operate/multi-repo.md | 2 +- docs/operate/snapshot.md | 10 +- docs/playbooks/drift-attestation.md | 2 +- docs/playbooks/fleet-baseline-rings.md | 8 +- docs/playbooks/incident-freeze.md | 2 +- docs/playbooks/preview-blast-radius.md | 4 +- docs/playbooks/trust-tiers.md | 4 +- docs/reference/secrets-and-vaults.md | 4 +- docs/start/examples.md | 2 +- docs/start/getting-started.md | 6 +- docs/start/migrating-from-probot.md | 2 +- docs/upgrading/README.md | 2 +- package.json | 2 +- src/flows/snapshot.ts | 2 +- 20 files changed, 241 insertions(+), 36 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f673336e..7c9cd9bd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -17,8 +17,8 @@ body: id: version attributes: label: Action version - description: The ref you pin in `uses:` (a tag like `v2`, or a commit sha). # x-release-please-major - placeholder: Vivswan/github-settings-as-code@v2 # x-release-please-major + description: The ref you pin in `uses:` (a tag like `v3`, or a commit sha). # x-release-please-major + placeholder: Vivswan/github-settings-as-code@v3 # x-release-please-major validations: required: true - type: input diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 895bf0e3..d4f6f299 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.0.0" + ".": "3.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 442db8f8..aa5dc1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,210 @@ # Changelog +## [3.0.0](https://github.com/Vivswan/github-settings-as-code/compare/v2.0.0...v3.0.0) (2026-09-24) + + +### ⚠ BREAKING CHANGES + +* **release:** publish next from the release-PR hook and drop the post-green verdict +* **github:** GitHubClient.tryRequest() and tryGraphql() resolve to ClientAnswer, whose third arm is `{ failed }`, and ArtifactUploader.upload() resolves to `{ uploaded: true } | { failed }`. docs/upgrading/v2-to-v3.md section 58 names the fix. +* **engine:** CheckOptions and ApplyOptions lose secretSource; pass it to validateSettings. A literal secret value in a section the sections input excludes now fails validation with exit 1 and zero requests. +* **sections:** SectionModule.plan() and snapshot() resolve to Result<..., SectionFailure> instead of the plan or snapshot; docs/upgrading/v2-to-v3.md section 56 names the fix. +* **engine:** a higher layer's null no longer deletes a lower key; it is written as the value, and a null a key does not admit fails validation naming the legal values. Drop a keyed entry with `_remove: true`, or clear a list with `_layering: replace`. +* **library:** `SectionModule.plan()` no longer accepts a raw section value. Pass the section off a validated document: `validateSettings(doc)` (or `mergeSettings`, or `snapshotRepository`) returns `settings`, and `settings.labels` is the `ValidatedInput<"labels">` that `labels.plan()` takes. +* **engine:** the problem codes settings-unknown-directives and settings-unknown-sections are deleted; settings-malformed-sections carries their lines in its issues. A switch on Problem.code naming either code stops compiling; drop the case. +* **engine:** a settings file whose excluded sections carry a declaration the file-only checks refuse (duplicates, malformed lists, an unreadable deploy key) now fails before any write, naming the path; fix the declaration. A custom list module built with the library implements the `validate` hook. +* **repository:** a repository section declaring a GET-only key (has_downloads, has_pages, custom_properties, ids, urls, counts), an unknown security_and_analysis sub-key, a commit message without its title or in an illegal squash pair, a malformed topic, or a PATCH toggle as null or a quoted boolean (has_wiki: "true", has_wiki: with no value) now fails to parse; the error names the key and the fix. +* **branches:** a branches entry whose protection.restrictions mapping omits users or teams no longer parses; declare both lists ([] when none), or restrictions: null to lift the push restriction. +* **rulesets:** a rulesets entry fails at parse, before any request, when its enforcement is not active, evaluate, or disabled; when a bypass actor breaks the spec's actor_id or bypass_mode rules; when ref_name carries "~" outside ~ALL or ~DEFAULT_BRANCH or a character git refuses in a ref name ("^", ":", "\", space, "..", "@{", control characters); or when a known rule type carries parameters the spec rejects. The message names the entry, key, and accepted values. An unknown rule type still passes through. +* **deploy-keys:** a `deploy_keys[].key` that is not one line of ` [comment]` with the algorithm one of ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com now fails the settings-file parse (a DSA key is refused naming the date GitHub dropped DSA), and a pasted private key is refused there without the material being echoed; declare the contents of the .pub file. +* **engine:** a higher layer's environments, branches, or workflows no longer replaces the lower list under the default layering: deep; write _layering: replace on the section's wrapper, or layering: replace on the run, to keep the old outcome. +* **secret-scanning:** a secret_scanning_custom_patterns entry whose pattern, start_delimiter, end_delimiter, must_match, or must_not_match value fails the syntax check now fails the run at parse with exit 1, naming the field; previously it reached GitHub's bulk create. +* **rulesets:** the parsed SettingsFile ruleset entry (the one sectionModule("rulesets").plan takes) requires target and enforcement, so a typed literal without them fails to compile. Parse the document through validateSettings, or declare both keys. +* **branches:** a required_status_checks.checks item with a key other than context or app_id is refused; remove the key. +* **branches:** a settings file declaring required_status_checks without strict or without contexts/checks, a required_approving_review_count outside 0-6, a scalar where those two mappings go, a GET-only protection key (name, enabled, enforcement_level, url, *_url at any depth), or an actor object where restrictions, dismissal_restrictions, or bypass_pull_request_allowances take login/slug strings now fails to parse before any request; the error names the key and the fix. +* **check-suite-preferences:** check_suite_preferences.auto_trigger_checks[].app_id must be a positive integer and may appear once; a settings file carrying 0, a fraction, or a repeated app_id now fails at parse with a message naming the entries and the fix. +* **setup:** a settings file declaring schedule, updated_at, a language in the GET's spelling (javascript, typescript, rust), or a string runner_label without runner_type: labeled now fails to parse with a message naming the fix. +* **actions:** a settings file that declares selected_actions_url, artifact_and_log_retention.maximum_allowed_days, oidc_customization_sub.sub_claim_prefix, a selected_actions key outside the three documented ones, a mistyped selected_actions value (github_owned_allowed: "true", patterns_allowed: "docker/*"; main's record of unknown values accepted them), sha_pinning_required as a string ("true"; main passed it through as an undeclared key), an approval_policy outside GitHub's three, a malformed or repeated OIDC claim key, include_claim_keys beside use_default: true, or a fractional or non-positive retention or cache limit now fails to parse before any request; the error names the key and the fix. +* **sections:** a settings file that omits a live field of a declared ruleset or environment now fails apply instead of writing over it; declare the field to keep the value, or declare it empty (`bypass_actors: []`, `reviewers: []`, `deployment_branch_policy: null`) to remove it on purpose +* **engine:** `_layering: merge` and `layering: merge` no longer exist and fail as unknown values (the set is `replace`, `shallow`, `deep`). `mode: merge` is `mode: render`, `merged-file` is `rendered-file`, the CLI subcommand `merge` is `render`, the result word `merged` is `rendered`, and the problem codes `input-merge-only`, `input-rejected-in-merge`, `input-merged-file-missing`, `merged-file-is-layer`, and `merged-file-unwritable` carry the render spelling. Every list section now unions by its key under the default, where only labels and rulesets did. +* **secrets:** a settings file whose secret or variable name has a hyphen, a space, a leading digit, or a GITHUB_ prefix in any case, or whose variable value is over 49152 bytes of UTF-8, now fails to parse before any request instead of reaching GitHub. Rename the entry to ASCII letters, digits, and underscores, or shorten the value; the problem line names the entry's path. +* **sections:** `collaborators[].permission` and `teams[].permission` refuse "read", "write", mis-cased standard permissions, empty values, and whitespace at either end; declare "pull", "push", or the lowercase form. `teams[].name` refuses anything outside `[A-Za-z0-9._-]` and a value with no letter or digit; declare the team's slug. +* **webhooks:** a settings file whose webhook names an event GitHub does not deliver to repository webhooks, a content_type other than "json" or "form", an insecure_ssl other than "0", "1", 0, or 1, or a url that is not an absolute URL now fails to parse with exit 1 instead of failing at apply time. +* **interaction-limits:** interaction_limits accepts only limit, expiry, pull_request_creation_cap, and pull_request_creation_bypass; limit and expiry must be GitHub's enum values and max_open_pull_requests a whole number from 1 to 1000. A settings file carrying anything else now fails at parse with a message naming the key and the rule. +* **pages:** a pages declaration carrying url, html_url, status, custom_404, protected_domain_state, pending_domain_unverified_at, or https_certificate fails validation before any request; remove the key. pages.source.path accepts only / or /docs; any other path fails validation before any request. +* **milestones:** `due_on` takes a calendar day `YYYY-MM-DD` (or a UTC timestamp `YYYY-MM-DDTHH:MM:SSZ`, read for its day); `due_on: null` and offset timestamps such as `2026-01-15T00:00:00+02:00` are refused with an error naming the day form. A snapshot emits a due date as `YYYY-MM-DDT12:00:00Z`. +* **labels:** labels[].color must be six hex digits with the leading "#" optional, and labels[].description is capped at 100 characters; a settings file carrying either now fails at parse with a message naming the entry and the rule. +* **engine:** a higher `pages: null` now means "site off". A repository that used `pages: null` over a fleet Pages block to opt out of the fleet's declaration has its live Pages site deleted on the next apply, and `interaction_limits: null` over a fleet limit clears the live limit the same way. The way to opt out of a fleet Pages block without disabling the site no longer exists. +* **environments:** environment declarations that parsed before are refused at parse time: wait_timer outside 0..43200 or not an integer, more than six reviewers, deployment_branch_policy with both flags true or both false, prevent_self_review without reviewers, and a deployment_branch_policies type outside branch and tag; the exported DeploymentBranchPolicyConfig.type narrows to "branch" | "tag". A fractional wait_timer (2.5): write a whole number of minutes. prevent_self_review: true without reviewers: declare a reviewer, or write prevent_self_review: false. deployment_branch_policy with both flags false: write deployment_branch_policy: null. deployment_branch_policies[].type outside branch | tag (wildcard): write branch or tag. +* **sections:** a GET or GraphQL body off GitHub's documented shape now fails the section with `returned a body outside the documented shape` naming the endpoint and field, where before it flowed into the comparison or a section's own message; a reading section without snapshot() no longer compiles; the teams and custom_properties owner probe runs ahead of their own validation. +* **sections:** teams, workflows, the environment listing and its pins, the protected-branch listing, the GraphQL protection rules, and the protection-rule Apps now fail plan and snapshot when GitHub holds two items under one identity, naming both; before, one was picked silently. The protection-rule Apps list is read at plan for an existing environment and after the PUT for one the run creates. +* **sections:** the teams, collaborators, and inherited interaction-limit snapshot notes read `