From 3464097fbfb9855af724e0e84931649064ed3549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eligio=20Mari=C3=B1o?= <22875166+gmeligio@users.noreply.github.com> Date: Sat, 9 May 2026 21:14:44 +0200 Subject: [PATCH] feat(renovate): manage GitHub Actions through gx.toml (#441) - Reconfigures Mend Renovate to manage GitHub Action versions exclusively through `.github/gx.toml` instead of editing workflow files directly - Disables Renovate's built-in `github-actions` manager; adds a custom regex manager reading specifiers from `gx.toml` with `github-tags` datasource and `npm` versioning (so `^6` and `~0.3.0` are honored) - Moves the monthly schedule (`* 0-3 1 * *`) to a rule scoped to `.github/gx.toml`; the existing `gx.yml` tidy job propagates Renovate's manifest edits to `gx.lock` and workflow files on the PR branch - Syncs the updated `actions-version-tracking` spec into `openspec/specs/` --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .github/renovate.json | 28 ++-- .../.openspec.yaml | 2 + .../.verify-passed | 1 + .../design.md | 133 ++++++++++++++++++ .../proposal.md | 28 ++++ .../specs/actions-version-tracking/spec.md | 35 +++++ .../tasks.md | 38 +++++ .../specs/actions-version-tracking/spec.md | 30 +++- 8 files changed, 280 insertions(+), 15 deletions(-) create mode 100644 openspec/changes/archive/2026-05-09-renovate-gx-integration/.openspec.yaml create mode 100644 openspec/changes/archive/2026-05-09-renovate-gx-integration/.verify-passed create mode 100644 openspec/changes/archive/2026-05-09-renovate-gx-integration/design.md create mode 100644 openspec/changes/archive/2026-05-09-renovate-gx-integration/proposal.md create mode 100644 openspec/changes/archive/2026-05-09-renovate-gx-integration/specs/actions-version-tracking/spec.md create mode 100644 openspec/changes/archive/2026-05-09-renovate-gx-integration/tasks.md diff --git a/.github/renovate.json b/.github/renovate.json index 06fe920..18ad056 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -9,22 +9,32 @@ ], "packageRules": [ { - "description": "Schedule Github Actions updates on the first day of the month", + "description": "Disable Renovate's built-in github-actions manager — gx owns workflow and composite-action files", + "matchManagers": ["github-actions"], + "enabled": false + }, + { + "description": "Schedule gx.toml-driven action major upgrades on the first day of the month", + "matchFileNames": [".github/gx.toml"], "groupName": "github-actions", - "matchDatasources": [ - "github-tags" - ], - "schedule": [ - "* 0-3 1 * *" - ] + "schedule": ["* 0-3 1 * *"] } ], "customManagers": [ { "customType": "regex", - "fileMatch": [ - "^Dockerfile$" + "description": "Read GitHub Action specifiers from gx.toml", + "managerFilePatterns": ["/^\\.github/gx\\.toml$/"], + "matchStrings": [ + "\"(?(?[^/\"]+/[^/\"]+)(?:/[^\"]+)?)\"\\s*=\\s*\"(?[^\"]+)\"" ], + "datasourceTemplate": "github-tags", + "versioningTemplate": "npm", + "extractVersionTemplate": "^v?(?.+)$" + }, + { + "customType": "regex", + "managerFilePatterns": ["/^Dockerfile$/"], "matchStrings": [ "#\\s*renovate:\\s*suite=(?.*?) depName=(?.*?)\\sARG .*?_VERSION=\"(?.*)\"\\s" ], diff --git a/openspec/changes/archive/2026-05-09-renovate-gx-integration/.openspec.yaml b/openspec/changes/archive/2026-05-09-renovate-gx-integration/.openspec.yaml new file mode 100644 index 0000000..0478d8f --- /dev/null +++ b/openspec/changes/archive/2026-05-09-renovate-gx-integration/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-09 diff --git a/openspec/changes/archive/2026-05-09-renovate-gx-integration/.verify-passed b/openspec/changes/archive/2026-05-09-renovate-gx-integration/.verify-passed new file mode 100644 index 0000000..b0aad4d --- /dev/null +++ b/openspec/changes/archive/2026-05-09-renovate-gx-integration/.verify-passed @@ -0,0 +1 @@ +passed diff --git a/openspec/changes/archive/2026-05-09-renovate-gx-integration/design.md b/openspec/changes/archive/2026-05-09-renovate-gx-integration/design.md new file mode 100644 index 0000000..8efae88 --- /dev/null +++ b/openspec/changes/archive/2026-05-09-renovate-gx-integration/design.md @@ -0,0 +1,133 @@ +## Context + +After `adopt-gx-for-actions` (#439), the repository has three coordinated artifacts: `.github/gx.toml` (manifest), `.github/gx.lock` (resolved versions and SHAs), and `.github/workflows/gx.yml` (CI lint + tidy on PR). However, the existing `.github/renovate.json` still drives version bumps using Renovate's built-in `github-actions` manager, which edits the workflow YAML directly. The current `gx.yml` `tidy` job then runs and pushes a follow-up commit to sync `.github/gx.lock`. This works, but: + +1. Manifest specifiers like `"^6"` and `"~0.3.0"` are not honored by Renovate — it walks past majors freely. +2. Two systems edit overlapping files; cross-PR races and noisy chase-commits are routine. +3. The "what versions are allowed" question has no canonical answer — `gx.toml` describes one constraint, Renovate operates on a different one, the lock records the resolution. + +The repository is hosted on the **Mend Renovate App**, which forbids `postUpgradeTasks` (those require self-hosted Renovate with `allowedPostUpgradeCommands`). The single-commit-PR option from self-hosted Renovate is therefore unavailable. This design takes the next-best path: scope Renovate to a single file (`gx.toml`), and let CI propagate. + +Verified beforehand: + +- `gx tidy` resolves manifest specifier changes and rewrites both `gx.lock` and workflow `uses:` SHAs in a single run (`~/Code/gx/src/tidy/command.rs:97-148`). Phase 1 (`sync_manifest_actions`) only adds/removes manifest entries and does not overwrite a specifier already present, so a Renovate edit (`^6` → `^6.0.2`) survives Phase 1 untouched and is consumed by Phase 3 (`lock_sync`) and Phase 4 (`compute_workflow_patches`). +- Renovate's built-in `github-actions` manager covers both `.github/workflows/**` and `.github/actions/**` (https://docs.renovatebot.com/modules/manager/github-actions/), so disabling it must be done by manager name, not file pattern. + +## Goals / Non-Goals + +**Goals:** + +- Make `.github/gx.toml` the only Renovate-editable file for GitHub Actions versions. +- Honor manifest specifiers (`^6`, `~0.3.0`) in Renovate's upgrade proposals so cross-major bumps require a human commit. +- Keep the existing `gx.yml` `tidy` job as the sole agent that rewrites `gx.lock` and workflow `uses:` references. +- Preserve the current monthly schedule (`* 0-3 1 * *`) for action upgrades. +- Preserve all existing non-actions Renovate rules (Dockerfile suite tracking, npm grouping, weekly cadence, etc.). + +**Non-Goals:** + +- Switching off the Mend Renovate App or migrating to self-hosted Renovate. +- Introducing `postUpgradeTasks` (incompatible with Mend App). +- Changing `gx`, `gx.yml`, or any workflow file behavior. +- Changing the schedule cadence or grouping for non-actions dependencies. +- Adding a new scheduled `gx upgrade` cron workflow (option A from the prior research; rejected here in favor of letting Renovate continue to be the upgrade trigger). + +## Decisions + +### Decision 1: Use a Renovate `customManagers` regex over `gx.toml`, not Renovate's TOML manager + +Renovate has no first-class manager for `gx.toml`. Two options: + +- **Custom regex manager (chosen)**: One `customManagers` entry with a regex over `gx.toml`. Direct, debuggable, well-supported. +- **Upstream a `gx` manager into Renovate**: A months-long external dependency. Out of scope. + +The regex must extract: + +- `depName` — full string from the manifest line (e.g., `github/codeql-action/upload-sarif`). Used in PR titles, dashboards, changelogs. +- `packageName` — first two slash-separated segments only (`github/codeql-action`). Used by the `github-tags` datasource to query the right repo. The non-capturing group `(?:/[^"]+)?` swallows the optional subpath. +- `currentValue` — the specifier (`^6`, `~0.3.0`). + +Final pattern: + +``` +"(?(?[^/"]+/[^/"]+)(?:/[^"]+)?"\s*=\s*"(?[^"]+)" +``` + +Datasource: `github-tags`. Versioning: `npm` (handles `^` and `~`). Extract template: `^v?(?.+)$` (strip `v` prefix from tags so npm versioning compares cleanly). + +Renovate docs (https://docs.renovatebot.com/configuration-options/#custommanagers) recommend "use only one method" per field but do not forbid two named captures from the same regex. We intentionally capture both `depName` and `packageName` because they differ for subpath actions; if Renovate rejects this combination at validation time, fall back to a `packageNameTemplate` Handlebars expression. + +### Decision 2: Disable Renovate's built-in `github-actions` manager via `matchManagers` + +Two options for stopping Renovate from editing workflow files: + +- **`matchManagers: ["github-actions"]` + `enabled: false` (chosen)** — disables the manager by name. Clean, explicit, future-proof if Renovate adds new file patterns to the manager. +- **`ignorePaths` covering `.github/workflows/**` and `.github/actions/**`** — file-pattern-based. Brittle if Renovate's coverage changes; also affects unrelated managers if any. + +Manager-based disabling is the documented idiomatic path. + +### Decision 3: Reuse the existing `gx.yml` `tidy` job; do not introduce a new workflow + +The `tidy` job in `.github/workflows/gx.yml` already runs `gx tidy` on every PR (when not from a fork) and pushes the result via the `VERIFIED_COMMIT_*` GitHub App. After Path 2, that job's role expands from "sync the lock to match Renovate's workflow edits" to "resolve Renovate's manifest edit into lock + workflow rewrites". Both are well within `gx tidy`'s capability and the existing workflow's permission scope. No workflow changes are needed. + +### Decision 4: Keep the schedule on the rule that targets `gx.toml` + +The current `renovate.json` puts `schedule: ["* 0-3 1 * *"]` on the `github-actions` package rule. After this change, that rule is gone. The schedule moves to a new rule keyed on `matchFileNames: [".github/gx.toml"]`, preserving the monthly-first-day cadence. Grouping name `github-actions` is preserved so existing PR-routing rules (if any in branch-protection or auto-merge configs) continue to apply. + +### Decision 5: Accept the two-commit PR shape + +Each Renovate upgrade PR will contain: + +1. Renovate's commit: 1-line edit in `gx.toml`. +2. gx-bot's commit (via `grafana/github-api-commit-action`, App-token-signed): updated `gx.lock` + workflow files + composite actions. + +A one-commit alternative requires `postUpgradeTasks`, which is unavailable on the Mend App. Two commits is acceptable: it matches the current observed PR shape (Renovate + chase-commit), and the "verified" App-token commit on top makes intent obvious in `git log`. + +### Decision 6: Do not change `.github/gx.toml` or `gx.lock` as part of this proposal + +This is a pure config refactor on the Renovate side. Manifest specifiers stay as currently committed. Future tightening of specifiers (e.g., switching `^6` to `~6.0.0` to forbid minor bumps) is a separate decision and a separate change. + +## Risks / Trade-offs + +- **Risk**: The two-named-capture regex (`depName` + `packageName` in the same `matchStrings`) may fail Renovate's config validation or runtime extraction. → Mitigation: dry-run validation step in tasks (`renovate-config-validator`); fallback design uses a Handlebars `packageNameTemplate` instead. +- **Risk**: `npm` versioning may misinterpret action tags that don't follow strict semver (e.g., `v6.0` instead of `v6.0.0`). → Mitigation: `extractVersionTemplate` strips the `v` prefix; if loose tags persist, Renovate will skip those bumps rather than corrupting them. `gx lint` would surface any resulting inconsistency. +- **Risk**: Lint (in `gx.yml`) runs in parallel with `tidy` and may fail on the first commit (manifest ahead of workflows). The PR shows a transient red check that flips green after `tidy` pushes its commit. → Mitigation: behavior is identical to the current chase-commit pattern; documented in tasks; out-of-scope to restructure `gx.yml`. +- **Risk**: An action used in workflows but missing from `gx.toml` would not be picked up by Renovate (since the only manager is now manifest-scoped). → Mitigation: `gx tidy` adds missing actions to the manifest on PR; `gx lint` flags unsynced manifest entries. A one-shot `gx tidy` locally before merging this proposal verifies completeness. +- **Trade-off**: Loses Renovate's ability to identify exact patch-level upgrades for actions whose tags don't follow semver. The lock will still be regenerated against the manifest specifier whenever upstream publishes a tag in range — just not driven by a Renovate PR for non-semver-compliant tags. Acceptable for the action set in this repo. +- **Trade-off**: Renovate's PR body will reference `depName` (full string with subpath); the github-tags lookup uses `packageName` (org/repo). The PR will list a `package: github/codeql-action`, dep: `github/codeql-action/upload-sarif`. Slight cosmetic asymmetry; not a correctness issue. + +## Migration Plan + +1. Local pre-flight: run `gx tidy` against the current tree; confirm zero diff. This validates that workflows, manifest, and lock are mutually consistent before changing Renovate. +2. Locally validate the new `renovate.json` with the Renovate config validator and a `--dry-run=full` against the local checkout. Confirm Renovate finds `gx.toml`, parses each line, and resolves expected `currentVersion` for at least three sample actions (one with subpath, one with caret, one with tilde). +3. Open a PR with the new `renovate.json`. The change is isolated to one file. +4. Watch the next Mend Renovate cycle. Confirm: + - No PRs editing `.github/workflows/**` or `.github/actions/**` are produced. + - Any new PR edits only `.github/gx.toml`. + - `gx.yml`'s `tidy` job adds the lock + workflow commit and `gx lint` passes on the head commit. + +**Rollback**: revert the `renovate.json` change. Behavior returns to the prior (workflow-editing) Renovate model. No data migration is required because no other artifacts are touched. + +## Automated Test Strategy + +This change has no application code; verification is configuration-level and observational. + +- **Critical path**: Renovate scans `gx.toml`, opens a PR with one `gx.toml` edit, `gx.yml` propagates lock + workflow updates, `gx lint` passes. +- **Pre-merge verification**: + - `npx --package renovate -- renovate-config-validator .github/renovate.json` — must pass. + - Local Renovate dry-run (`renovate --platform=local --dry-run=full`) — must list expected upgrades for `gx.toml` entries and zero upgrades from the now-disabled `github-actions` manager. + - Local `gx tidy` against the tree before opening the PR — must produce no diff (proves baseline consistency). +- **Post-merge verification (first Renovate cycle, time-bounded)**: confirm one Renovate PR opens, edits only `gx.toml`, `gx.yml` `tidy` succeeds, and `gx lint` passes on the merged head. +- **No new test infrastructure**. The existing `gx lint` job in `.github/workflows/gx.yml` is the on-going invariant check. + +## Observability + +- **Renovate-side failure surface**: Mend Renovate dashboard shows manager-extraction errors per repo. If the regex fails to match any line in `gx.toml`, Renovate's logs surface a warning and produce zero PRs for actions — visible as "no upgrades opened this cycle" on the dashboard. This is *silent* on the GitHub side; first observation is the absence of expected PRs after the cycle. +- **gx-side failure surface**: `gx tidy` failures in the `gx.yml` `tidy` job appear as failed CI checks on the PR. `gx lint` failures appear as failed CI checks on every commit. +- **Drift surface**: if a Renovate PR ever lands without the `tidy` follow-up commit (e.g., `tidy` job timed out, App token expired), `gx lint` fails on the PR head and merge is blocked. This is the load-bearing safety net. +- **What is logged**: nothing new. `gx.yml` already prints tidy/lint progress; Renovate's logs are visible via the Mend dashboard. +- **Can a failure be silent?**: yes — if Renovate's custom manager extracts zero matches, there is no GitHub-side signal until someone notices that monthly action upgrade PRs stopped arriving. Mitigation: the migration plan's local dry-run is the front-loaded check; running it before merge converts the silent failure into a loud, pre-merge one. + +## Open Questions + +- Does Renovate's config validator accept the two-named-capture regex (`depName` + `packageName` in the same `matchStrings`)? Tasks include the verification step; if validation fails, fall back to `packageNameTemplate` with a Handlebars conditional. +- Should the `github-actions` group label be preserved on the new rule (`groupName: "github-actions"`) for any downstream branch-protection or auto-merge config? Default: yes, unless evidence emerges that no rule depends on it. diff --git a/openspec/changes/archive/2026-05-09-renovate-gx-integration/proposal.md b/openspec/changes/archive/2026-05-09-renovate-gx-integration/proposal.md new file mode 100644 index 0000000..f3c949a --- /dev/null +++ b/openspec/changes/archive/2026-05-09-renovate-gx-integration/proposal.md @@ -0,0 +1,28 @@ +## Why + +Today Renovate edits workflow files directly and `gx tidy` chases each PR with a fixup commit to sync `.github/gx.lock`. The two tools race on the same files, manifest specifiers (`^6`, `~0.3.0`) declared in `.github/gx.toml` are ignored, and Renovate can therefore propose a cross-major upgrade that the manifest was meant to forbid. Pointing Renovate at the manifest instead of the workflows turns `gx.toml` into the single source of truth for which action versions are allowed, and lets gx own propagation to the lock and workflow files. + +## What Changes + +- Disable Renovate's built-in `github-actions` manager so it stops editing files under `.github/workflows/` and `.github/actions/`. +- Add a Renovate `customManagers` regex entry that reads action specifiers from `.github/gx.toml` using the `github-tags` datasource and `npm` versioning (so `^6` and `~0.3.0` are honored). +- Move the existing monthly schedule from the `github-actions` package rule to a new rule that targets `.github/gx.toml`. +- Document that Renovate-driven action upgrades arrive as a `gx.toml`-only edit and are completed in-PR by the existing `gx.yml` `tidy` job. +- **BREAKING** for the spec only: the requirement that Renovate PRs already carry the lock update on open is replaced by a requirement that `gx.yml`'s `tidy` job pushes the lock and workflow updates onto the Renovate PR branch before merge. + +## Capabilities + +### New Capabilities + +_None._ + +### Modified Capabilities + +- `actions-version-tracking`: the "Renovate-driven upgrades keep the lock in sync" requirement is replaced with one that scopes Renovate to `.github/gx.toml` and assigns lock + workflow propagation to `gx tidy` running in CI on the PR branch. + +## Impact + +- Affected files: `.github/renovate.json` (rewrite), `openspec/specs/actions-version-tracking/spec.md` (delta applied during archive). +- No code changes; no changes to `.github/workflows/gx.yml`, `.github/gx.toml`, or `.github/gx.lock`. +- Operational impact: monthly Renovate PRs will edit one TOML line; the existing `gx.yml` `tidy` job adds a follow-up commit on the same PR with the lock and workflow updates. Net commit count per upgrade PR is unchanged or lower than today. +- Safety property gained: Renovate cannot propose a major-version upgrade unattended. Crossing a major now requires a human to edit `gx.toml` (`^6` → `^7`), which becomes the review surface. diff --git a/openspec/changes/archive/2026-05-09-renovate-gx-integration/specs/actions-version-tracking/spec.md b/openspec/changes/archive/2026-05-09-renovate-gx-integration/specs/actions-version-tracking/spec.md new file mode 100644 index 0000000..3c7935b --- /dev/null +++ b/openspec/changes/archive/2026-05-09-renovate-gx-integration/specs/actions-version-tracking/spec.md @@ -0,0 +1,35 @@ +## MODIFIED Requirements + +### Requirement: Renovate-driven upgrades keep the lock in sync + +Renovate SHALL be configured to manage GitHub Action versions exclusively through `.github/gx.toml`, not by editing workflow files. The repository's CI SHALL propagate every Renovate-driven `gx.toml` edit through to `.github/gx.lock` and the workflow files on the same pull request branch before merge, so that when the PR is merged the manifest, lock, and workflow `uses:` SHAs are mutually consistent. + +#### Scenario: Renovate edits the manifest only + +- **WHEN** Renovate opens an upgrade PR for a GitHub Action +- **THEN** the only file modified by Renovate's commit is `.github/gx.toml` +- **AND** the modification is a change to the action's specifier (e.g., `"^6.0.1"` → `"^6.0.2"`, or `"^6"` → `"^6"` with no change if already the broadest in-major specifier) + +#### Scenario: gx.yml propagates the manifest edit on the PR branch + +- **WHEN** a PR contains a `.github/gx.toml` edit and the workflow `uses:` SHAs do not yet match the new specifier +- **THEN** the `gx.yml` workflow's `tidy` job runs `gx tidy`, regenerates `.github/gx.lock`, rewrites every affected `uses: /@ # vX.Y.Z` reference in `.github/workflows/**` and `.github/actions/**`, and pushes the resulting changes onto the PR branch as a single commit +- **AND** `gx lint` passes on the resulting head commit + +#### Scenario: Manifest specifier bounds the upgrade + +- **WHEN** a new major version of an action is published upstream and the manifest specifier is `^N` for the prior major +- **THEN** Renovate does not propose an upgrade that crosses the major boundary +- **AND** crossing the major requires a human commit that changes `.github/gx.toml` from `^N` to `^N+1`, after which `gx tidy` resolves the new major and rewrites lock and workflow files on the same PR + +#### Scenario: Renovate's built-in github-actions manager is disabled + +- **WHEN** Renovate evaluates this repository +- **THEN** the built-in `github-actions` manager is disabled by configuration +- **AND** no Renovate run produces a commit that edits any file under `.github/workflows/` or `.github/actions/` + +#### Scenario: Lock drift is detected before merge + +- **WHEN** for any reason a Renovate PR's head commit lacks the lock and workflow updates that match its `gx.toml` edit +- **THEN** the `gx lint` CI job fails the PR +- **AND** merge is blocked until `gx tidy` is run and committed diff --git a/openspec/changes/archive/2026-05-09-renovate-gx-integration/tasks.md b/openspec/changes/archive/2026-05-09-renovate-gx-integration/tasks.md new file mode 100644 index 0000000..ef8814a --- /dev/null +++ b/openspec/changes/archive/2026-05-09-renovate-gx-integration/tasks.md @@ -0,0 +1,38 @@ +## 1. Pre-flight + +- [x] 1.1 Run `gx tidy` against the current tree and confirm zero diff (proves the manifest, lock, and workflows are mutually consistent before changing Renovate) +- [x] 1.2 Confirm Mend Renovate App is the bot in use by checking the author of the most recent Renovate PR + +## 2. Rewrite `.github/renovate.json` + +- [x] 2.1 Add a `packageRules` entry that disables Renovate's built-in `github-actions` manager (`matchManagers: ["github-actions"]` + `enabled: false`) +- [x] 2.2 Remove the existing `github-actions` package rule (the one with `matchDatasources: ["github-tags"]` and the monthly schedule) +- [x] 2.3 Add a `customManagers` regex entry targeting `^\\.github/gx\\.toml$` with named captures `depName`, `packageName`, `currentValue`, datasource `github-tags`, versioning `npm`, and `extractVersionTemplate: "^v?(?.+)$"` +- [x] 2.4 Add a new `packageRules` entry keyed on `matchFileNames: [".github/gx.toml"]` with `groupName: "github-actions"` and the existing monthly schedule `["* 0-3 1 * *"]` +- [x] 2.5 Preserve the existing Dockerfile `customManagers` entry untouched +- [x] 2.6 Preserve the existing `extends` array untouched +- [x] 2.7 Apply Renovate's `fileMatch` → `managerFilePatterns` migration (regex wrapped in `/.../`) on both `customManagers` entries to silence the validator's deprecation notice on first run + +## 3. Local validation + +- [x] 3.1 Run `npx --package renovate -- renovate-config-validator .github/renovate.json` and confirm exit 0 +- [x] 3.2 Run `renovate --platform=local --dry-run=full` (with `LOG_LEVEL=debug`) and confirm: (a) zero upgrades from the `github-actions` manager, (b) `actions/checkout` is extracted with `packageName=actions/checkout` and a current version, (c) `github/codeql-action/upload-sarif` is extracted with `packageName=github/codeql-action`, (d) `plexsystems/container-structure-test-action` is extracted with the `~0.3.0` specifier +- [x] 3.3 If validation fails on the two-named-capture regex, fall back to a `packageNameTemplate` Handlebars conditional that returns the first two slash-separated segments of `depName`, and re-run 3.1 and 3.2 + +## 4. Open the PR + +- [ ] 4.1 Commit the `renovate.json` change on a topic branch and open a PR +- [ ] 4.2 Confirm `gx.yml` `lint` and `tidy` jobs pass on the PR (no functional change yet, so they should be green) +- [ ] 4.3 Merge after review + +## 5. Post-merge observation (next Renovate cycle) + +- [ ] 5.1 On the next monthly Renovate cycle, confirm any opened upgrade PR edits only `.github/gx.toml` +- [ ] 5.2 Confirm `gx.yml`'s `tidy` job pushes a follow-up commit on the PR branch with `gx.lock` and workflow updates +- [ ] 5.3 Confirm `gx lint` passes on the head commit of the Renovate PR +- [ ] 5.4 If no Renovate PR appears within the expected window, check the Mend dashboard for manager-extraction errors against `gx.toml` and resolve before archiving + +## 6. Archive + +- [ ] 6.1 Run `/opsx:verify` to validate that the shipped behavior matches the specs +- [ ] 6.2 Run `/opsx:archive` to fold the spec delta into `openspec/specs/actions-version-tracking/spec.md` diff --git a/openspec/specs/actions-version-tracking/spec.md b/openspec/specs/actions-version-tracking/spec.md index 46fd181..d56028f 100644 --- a/openspec/specs/actions-version-tracking/spec.md +++ b/openspec/specs/actions-version-tracking/spec.md @@ -68,17 +68,35 @@ The repository's CI SHALL run `gx lint` on every pull request and SHALL fail whe ### Requirement: Renovate-driven upgrades keep the lock in sync -Renovate-generated upgrade pull requests for GitHub Actions SHALL leave `.github/gx.lock` consistent with the workflow SHAs they introduce, either by running `gx tidy` as a `postUpgradeTask` in `renovate.json` or via an equivalent CI workflow that pushes a fixup commit to the PR before merge. +Renovate SHALL be configured to manage GitHub Action versions exclusively through `.github/gx.toml`, not by editing workflow files. The repository's CI SHALL propagate every Renovate-driven `gx.toml` edit through to `.github/gx.lock` and the workflow files on the same pull request branch before merge, so that when the PR is merged the manifest, lock, and workflow `uses:` SHAs are mutually consistent. -#### Scenario: Renovate bumps a SHA and the lock is updated +#### Scenario: Renovate edits the manifest only -- **WHEN** Renovate opens a PR that bumps `actions/checkout` from one SHA to another -- **THEN** the same PR contains a corresponding update to `.github/gx.lock` -- **AND** `gx lint` passes on the PR +- **WHEN** Renovate opens an upgrade PR for a GitHub Action +- **THEN** the only file modified by Renovate's commit is `.github/gx.toml` +- **AND** the modification is a change to the action's specifier (e.g., `"^6.0.1"` → `"^6.0.2"`, or `"^6"` → `"^6"` with no change if already the broadest in-major specifier) + +#### Scenario: gx.yml propagates the manifest edit on the PR branch + +- **WHEN** a PR contains a `.github/gx.toml` edit and the workflow `uses:` SHAs do not yet match the new specifier +- **THEN** the `gx.yml` workflow's `tidy` job runs `gx tidy`, regenerates `.github/gx.lock`, rewrites every affected `uses: /@ # vX.Y.Z` reference in `.github/workflows/**` and `.github/actions/**`, and pushes the resulting changes onto the PR branch as a single commit +- **AND** `gx lint` passes on the resulting head commit + +#### Scenario: Manifest specifier bounds the upgrade + +- **WHEN** a new major version of an action is published upstream and the manifest specifier is `^N` for the prior major +- **THEN** Renovate does not propose an upgrade that crosses the major boundary +- **AND** crossing the major requires a human commit that changes `.github/gx.toml` from `^N` to `^N+1`, after which `gx tidy` resolves the new major and rewrites lock and workflow files on the same PR + +#### Scenario: Renovate's built-in github-actions manager is disabled + +- **WHEN** Renovate evaluates this repository +- **THEN** the built-in `github-actions` manager is disabled by configuration +- **AND** no Renovate run produces a commit that edits any file under `.github/workflows/` or `.github/actions/` #### Scenario: Lock drift is detected before merge -- **WHEN** for any reason a Renovate PR lands without the lock update +- **WHEN** for any reason a Renovate PR's head commit lacks the lock and workflow updates that match its `gx.toml` edit - **THEN** the `gx lint` CI job fails the PR - **AND** merge is blocked until `gx tidy` is run and committed