From 8a71766a38635469e01ecce76626b1872d951311 Mon Sep 17 00:00:00 2001 From: Maria Carter Date: Thu, 10 Sep 2026 20:24:19 +0200 Subject: [PATCH 1/3] Kit: third-party actions float on their major tag; the reusables stay SHA-pinned A SHA in templates/ is a pin nothing bumps (Dependabot scans .github/workflows/ only) while every fleet repo's Dependabot bumps its deployed copy the day a release ships, so the fleet always ran ahead of the kit and each wave rolled it back (foundrae-blackridge #174). The CLI binary already floats to current stable at every run, so a pinned action wrapper bought no supply-chain safety the CLI download had not already given away. --- CLAUDE.md | 7 ++++--- README.md | 13 +++++++----- docs/claude-yml-wave-plan.md | 2 +- docs/fleet-operations.md | 6 +++--- docs/reusable-conversion-scope.md | 2 +- templates/github/claude.yml | 28 ++++++++++++++++--------- templates/github/lint.yml | 2 +- templates/github/shopify-tool-smoke.yml | 2 +- tools/fleet-pin-audit.sh | 4 ++-- 9 files changed, 39 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 300905b..928c061 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,9 +91,10 @@ caveats: README "Release + repin order"; wave mechanics and fleet counts: `docs/ the lead-in that un-scopes the tripwire from them) is kit-side and must survive the re-copy. Parity is checked with whitespace collapsed (the kit flattens one paragraph break). - `DRIVER_AGENTS_REF` lives in `claude.yml` **and** `shopify-tool-smoke.yml`; same SHA in both, or the - smoke test verifies a revision the implementer never runs. Dependabot cannot bump it (raw SHA in - `env:`), nor any action pin inside `templates/` (it scans only `.github/workflows/`) — check - `actions/checkout` and `claude-code-action` against the reusables' pins at every tag. + smoke test verifies a revision the implementer never runs. Dependabot cannot bump it: a raw SHA in + `env:`, and it scans only `.github/workflows/`, never `templates/`. That is also why the kit's + whole-file workflows reference third-party actions by major tag (`@v1`, `@v7`) — a SHA there is a + pin nothing bumps while every fleet repo's Dependabot bumps its copy. The reusables stay SHA-pinned. - `dependabot-validate` stub `name:` stays byte-identical (`Dependabot validate`) — `-report`'s `workflow_run` name-matches it. The job always runs and branches internally; never `if:`-skip it. - Never `pull_request_target`. Never set `anthropic_api_key` (overrides OAuth, bills at API rates). diff --git a/README.md b/README.md index 265bf56..dbe7a5b 100644 --- a/README.md +++ b/README.md @@ -347,10 +347,13 @@ Waved to all 21 pairs on 2026-08-02; fleet uniform, 108 pins, zero stale. - Wave mechanics, the guards worth keeping, and what the pin audit cannot see: [`docs/fleet-operations.md`](docs/fleet-operations.md). -**Template pins are manual.** `.github/dependabot.yml` uses `directory: "/"`, which only scans -`.github/workflows/` — nothing will ever bump an action pin inside `templates/`. Check -`templates/github/claude.yml`'s `actions/checkout` + `claude-code-action` pins against the -reusables' whenever you cut a tag. The same applies to `DRIVER_AGENTS_REF` — which appears in **two** +**Template pins are manual — so the kit's third-party actions float.** `.github/dependabot.yml` uses +`directory: "/"`, which only scans `.github/workflows/` — nothing will ever bump a pin inside +`templates/`, while every fleet repo's Dependabot bumps its deployed copy the day a release ships, so +a SHA there guaranteed the fleet ran ahead of the kit and each wave rolled it back. Since v1.16.0 the +three whole-file kit workflows reference `actions/checkout@v7`, `actions/upload-artifact@v7` and +`anthropics/claude-code-action@v1`; Dependabot leaves a major-tag ref alone until a new major exists. +The reusables keep SHA pins, which Dependabot handles here. `DRIVER_AGENTS_REF` stays manual — it appears in **two** kit files, `claude.yml` and `shopify-tool-smoke.yml`, and must carry the same pin in both or the smoke test verifies a revision the implementer never runs — and to the `VERSION` + `SHA256` pair in `lint.yml`, which must be bumped together or the checksum check fails the job. @@ -444,7 +447,7 @@ number, same-repo head). **Never use `pull_request_target`.** ## Consuming it (caller stubs) Install the matching stubs from **this repo's `templates/github/`** into a repo's `.github/workflows/`. -Pin every `uses:` to an **immutable commit SHA** (decided 2026-06-17); a bot (Renovate/Dependabot) bumps the +Pin every `uses:` of this repo's reusables to an **immutable commit SHA** (decided 2026-06-17); a bot (Renovate/Dependabot) bumps the SHAs. The `dependabot-validate` stub's `name:` MUST stay byte-identical (`Dependabot validate`) across all repos — the `dependabot-report` stub's `workflow_run` trigger name-matches it exactly, and a drift silently disables the human-ping. diff --git a/docs/claude-yml-wave-plan.md b/docs/claude-yml-wave-plan.md index d2b1c76..f2747dc 100644 --- a/docs/claude-yml-wave-plan.md +++ b/docs/claude-yml-wave-plan.md @@ -29,7 +29,7 @@ un-started Plan B work; it is the ride-along list for the wave after `v1.15.0`. | **Figma wrapper wiring** | `claude_args` gains `--mcp-config '{"mcpServers":{"figma":{…}}}'` as inline JSON (never a file path — dropped in tag mode), `--allowedTools` gains the read-only `mcp__figma__*` tools, and `lint.yml`'s quote gate moves **4 → 6** in the same commit. Wiring and tool list: [`figma-mcp-in-ci.md`](figma-mcp-in-ci.md). | The read-only REST-backed wrapper exists in driver-agents and answers one call from a throwaway Actions run. Not `mcp.figma.com`, so that doc's re-open tripwire does not apply. | | **Figma caveat rewrite** | CAVEAT 2 above `--allowedTools` says no rail can read a design; once the wrapper is wired it says what the implementer can read (node JSON, rendered PNGs) and that writes are never offered. | Same as above. | | **Macroscope CLI in the run** | A setup step installs the CLI with the Claude Code plugin and the issue prompt runs `/macroscope:autoloop` before the pre-review. | A non-interactive credential. The installer takes `--tools claude --yes`, but auth is a browser wizard under `~/.macroscope` and reviews bill agent credits; nothing in the docs or installer offers a token path. Ask Macroscope; same shape as the Figma blocker until answered. | -| **WebSearch / WebFetch** | Re-add to `--allowedTools`. | anthropics/claude-code-action#690 ships a fix (open as of 2026-07-28). The v1.15.0 pin bump does not change this. | +| **WebSearch / WebFetch** | Re-add to `--allowedTools`. | anthropics/claude-code-action#690 ships a fix (open as of 2026-07-28). The kit floats on `v1` since v1.16.0, so the fix arrives on its own; the caveat comment is what gets removed. | | **Model** | None planned; `--model fable --effort xhigh` stays. | The Fable billing answer above; fallback is `--model opus` (MODEL NOTE in `claude.yml`). | Anything that only touches the kit and none of these gates can ride the next reusable tag instead — diff --git a/docs/fleet-operations.md b/docs/fleet-operations.md index 405cb81..dc09a35 100644 --- a/docs/fleet-operations.md +++ b/docs/fleet-operations.md @@ -201,9 +201,9 @@ Two things worth knowing about check 3: correctly-waved repo is *supposed* to have. Second, trailing blank lines and the final newline: the three pairs waved without a final newline are otherwise identical, and permanently-red rows for a byte nobody can act on is how a detector stops being read. Internal blank lines *are* - compared. Everything else that differs is reported, third-party action pins - included: a repo whose Dependabot bumped `actions/checkout` past the kit's pin is drift worth - seeing, and it means the kit is behind, not that the repo is wrong. + compared. Everything else that differs is reported, third-party action refs + included: a repo whose Dependabot moved `actions/checkout@v7` to `@v8` ahead of the kit is drift + worth seeing, and it means the kit is behind, not that the repo is wrong. - **`DriverDigital/workflows` itself is skipped.** Its `.github/workflows/` holds the *reusables*, which share basenames with the stubs that call them — so comparing it against `templates/` would report four phantom drifts — the three Dependabot stubs plus `lint.yml`, whose kit copy is a diff --git a/docs/reusable-conversion-scope.md b/docs/reusable-conversion-scope.md index 7dae29f..84da120 100644 --- a/docs/reusable-conversion-scope.md +++ b/docs/reusable-conversion-scope.md @@ -65,7 +65,7 @@ and `DRIVER_AGENTS_REF` stays hand-edited fleet-wide for as long as `shopify-too file (open decision 1). What conversion removes is the *logic* that a wave could hand-carry a subset of — which is the specific failure that produced the Avara incident. -Secondary win: `actions/checkout` (`templates/github/claude.yml:130`), `claude-code-action` (`:310`) and +Secondary win (moot since v1.16.0 — the kit floats these on major tags): `actions/checkout` (`templates/github/claude.yml:130`), `claude-code-action` (`:310`) and `actions/upload-artifact` (`:496`, added by v1.8.0) move out of `templates/` and into `.github/workflows/`, which `.github/dependabot.yml` (`directory: "/"`) actually scans — converting **three** documented manual pins into bot-managed ones. diff --git a/templates/github/claude.yml b/templates/github/claude.yml index 3e929ec..c85df0b 100644 --- a/templates/github/claude.yml +++ b/templates/github/claude.yml @@ -126,7 +126,7 @@ jobs: # with "Unrecognized named-value: runner" — actions/runner#2204). SHOPIFY_STORE_NAME: "" steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 with: fetch-depth: 0 # Keep the DEFAULT persist-credentials — do NOT set it to false. On the @claude-on-a-PR @@ -281,8 +281,9 @@ jobs: # 1. UNPINNED CLI: each action release bakes in a PINNED Claude Code version. We'd rather # track the current CLI without bumping the action just to move it # forward — `install.sh` with no version arg installs the latest STABLE build (auto-advances, - # never pinned; not the more bleeding-edge `latest` channel). The action SHA is still bumped - # periodically (Dependabot) so its bundled Agent SDK stays close to the CLI it drives. + # never pinned; not the more bleeding-edge `latest` channel). The action floats on its `v1` + # major tag (below) for the same reason: its bundled Agent SDK stays as close as it can to + # the CLI it drives. # 2. RESILIENT INSTALL: the action's own install retries ONCE, no backoff, and does NOT fail the # job when the download fails — it proceeds and the SDK throws a misleading "native binary not # found". A transient `curl (35) Recv failure: Connection reset by peer` fetching the binary @@ -290,7 +291,14 @@ jobs: # attempt with `timeout`, retries 5x with backoff, and fails LOUDLY if the binary is still # missing, so a real outage is an honest red job — not a confusing SDK error with the # tracking comment stuck on "working…". - # The action SHA stays PINNED (supply-chain safety); only the CLI binary floats (to current stable). + # Both float on purpose (v1.16.0): the CLI binary to current stable, the action on its `v1` tag. + # A pinned action SHA bought no supply-chain safety the unpinned CLI download had not already + # given away, and it was the one pin in this file nothing bumps — Dependabot scans + # `.github/workflows/`, never `templates/` — while every fleet repo's Dependabot bumped its own + # copy the day a release shipped, so the fleet always ran ahead of the kit and each wave rolled + # it back (foundrae-blackridge #174). Dependabot leaves a major-tag ref alone until a new major + # exists. The trade: a regression in a new action release reaches every repo on its next run, + # and holding the fleet on a known version means pinning a SHA here and waving. # Path is fixed because runs-on is ubuntu-latest (GitHub-hosted) → $HOME is always /home/runner. - name: Install Claude Code (latest stable, resilient) run: | @@ -307,8 +315,8 @@ jobs: [ -x "$HOME/.local/bin/claude" ] || { echo "::error::Claude Code install failed after 5 attempts"; exit 1; } "$HOME/.local/bin/claude" --version - id: claude - uses: anthropics/claude-code-action@c81e3bc69d1b18badbb63ba39581218f02421678 # v1.0.201 - # anthropics/claude-code-action#1499 (open; the `break` is still in v1.0.220): the action stops + uses: anthropics/claude-code-action@v1 + # anthropics/claude-code-action#1499 (open; the `break` was still in v1.0.220 on 2026-09-10): the action stops # reading the SDK stream at the FIRST result message, so a run that backgrounds subagents and # ends its turn to wait for them exits green with the work orphaned. Avara #195 pushed its # branch and never reached `gh pr create`. With this set, the background option is not offered @@ -400,9 +408,9 @@ jobs: # install + use the inline-comment poster — naming it is what makes the action install that MCP # server (src/mcp/install-mcp-server.ts); omitting it is exactly what silently broke posting on # the reviewer rail. CAVEAT: WebSearch/WebFetch are HARDCODED default-disallowed by the action - # (anthropics/claude-code-action#690, still open) — no claude_args override at this SHA, so - # they're omitted (deep web research lives on the executor leg's bare `claude -p`). Re-add only if - # #690 ships a fix and this SHA is bumped. + # (anthropics/claude-code-action#690, still open) — no claude_args override as of v1.0.220 + # (2026-09-10), so they're omitted. Re-add when #690 ships a fix: the action floats on `v1`, so + # the fix arrives on its own and this comment is the only gate. # CAVEAT 2: Figma MCP cannot run on this rail — mcp.figma.com needs an interactive OAuth consent # that neither CI credential can give, and no --allowedTools edit changes that. Triage reads the # design and puts what the implementer needs in the issue body instead @@ -526,7 +534,7 @@ jobs: # tooling (env empty) and on runs where the tool never fired (if-no-files-found: ignore). - name: Upload Shopify audit artifact if: ${{ always() && env.SHOPIFY_AUDIT_DIR != '' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7 with: name: shopify-audit-${{ env.SHOPIFY_STORE_NAME }}-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ env.SHOPIFY_AUDIT_DIR }} diff --git a/templates/github/lint.yml b/templates/github/lint.yml index 9f7ca06..ca632e6 100644 --- a/templates/github/lint.yml +++ b/templates/github/lint.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 # Pinned release + checksum instead of the upstream `rhysd/actionlint` action: the org # enforces a selected-actions allowlist, so a dependency-free download is one less thing to diff --git a/templates/github/shopify-tool-smoke.yml b/templates/github/shopify-tool-smoke.yml index 45bddf6..2b24fb5 100644 --- a/templates/github/shopify-tool-smoke.yml +++ b/templates/github/shopify-tool-smoke.yml @@ -105,7 +105,7 @@ jobs: # collector. run_attempt in the name avoids the immutable-artifact collision on re-runs. - name: Upload Shopify audit artifact if: ${{ always() && env.SHOPIFY_AUDIT_DIR != '' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7 with: name: shopify-audit-${{ env.SHOPIFY_STORE_NAME }}-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ env.SHOPIFY_AUDIT_DIR }} diff --git a/tools/fleet-pin-audit.sh b/tools/fleet-pin-audit.sh index 7d06031..b4ece39 100755 --- a/tools/fleet-pin-audit.sh +++ b/tools/fleet-pin-audit.sh @@ -44,8 +44,8 @@ LATEST="$(set -o pipefail; gh api "repos/$ORG/workflows/tags" --paginate --jq '. LATEST_TAG="${LATEST%% *}"; LATEST_SHA="${LATEST#* }"; LATEST_SHA8="${LATEST_SHA:0:8}" # EXACTLY TWO normalizations, both deliberate. Everything else that differs is reported — third-party -# action pins included: a consumer repo whose Dependabot bumped `actions/checkout` past the kit's pin -# is drift worth seeing, since it means the kit is behind, not that the repo is wrong. +# action refs included: a consumer repo whose Dependabot moved `actions/checkout@v7` to `@v8` ahead of +# the kit is drift worth seeing, since it means the kit is behind, not that the repo is wrong. # # 1. SHOPIFY_STORE_NAME — the one difference a correctly-waved repo is SUPPOSED to have. The kit # ships it empty; Avara carries "avara". Anchored to a line that STARTS with the key, so the ten From caf93c1c79acaedf47571c9be79134efa253f9fd Mon Sep 17 00:00:00 2001 From: Maria Carter Date: Thu, 10 Sep 2026 20:32:48 +0200 Subject: [PATCH 2/3] review: narrow the premise, state the marginal supply-chain cost, drop the forward-dated tag, update HANDOFF and the two docs the first pass missed; ignore minor/patch bumps of the action in the kit's Dependabot block --- CLAUDE.md | 3 ++- README.md | 11 ++++++----- docs/HANDOFF.md | 12 +++++++----- docs/claude-yml-wave-plan.md | 2 +- docs/fleet-operations.md | 4 +++- docs/reusable-conversion-scope.md | 11 +++++------ templates/github/README.md | 5 +++-- templates/github/claude.yml | 22 ++++++++++++++-------- templates/github/dependabot.yml | 5 +++++ 9 files changed, 46 insertions(+), 29 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 928c061..26c721b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -94,7 +94,8 @@ caveats: README "Release + repin order"; wave mechanics and fleet counts: `docs/ smoke test verifies a revision the implementer never runs. Dependabot cannot bump it: a raw SHA in `env:`, and it scans only `.github/workflows/`, never `templates/`. That is also why the kit's whole-file workflows reference third-party actions by major tag (`@v1`, `@v7`) — a SHA there is a - pin nothing bumps while every fleet repo's Dependabot bumps its copy. The reusables stay SHA-pinned. + pin nothing bumps while a fleet repo's Dependabot bumps its copy when the action releases. The + reusables stay SHA-pinned. - `dependabot-validate` stub `name:` stays byte-identical (`Dependabot validate`) — `-report`'s `workflow_run` name-matches it. The job always runs and branches internally; never `if:`-skip it. - Never `pull_request_target`. Never set `anthropic_api_key` (overrides OAuth, bills at API rates). diff --git a/README.md b/README.md index dbe7a5b..0cea03b 100644 --- a/README.md +++ b/README.md @@ -349,11 +349,12 @@ Waved to all 21 pairs on 2026-08-02; fleet uniform, 108 pins, zero stale. **Template pins are manual — so the kit's third-party actions float.** `.github/dependabot.yml` uses `directory: "/"`, which only scans `.github/workflows/` — nothing will ever bump a pin inside -`templates/`, while every fleet repo's Dependabot bumps its deployed copy the day a release ships, so -a SHA there guaranteed the fleet ran ahead of the kit and each wave rolled it back. Since v1.16.0 the -three whole-file kit workflows reference `actions/checkout@v7`, `actions/upload-artifact@v7` and -`anthropics/claude-code-action@v1`; Dependabot leaves a major-tag ref alone until a new major exists. -The reusables keep SHA pins, which Dependabot handles here. `DRIVER_AGENTS_REF` stays manual — it appears in **two** +`templates/`, while a fleet repo running the `github-actions` updater bumps its deployed copy when the +action itself releases, so a SHA there guaranteed the fleet ran ahead of the kit and each wave rolled +it back (foundrae-blackridge #174). The three whole-file kit workflows therefore reference +`actions/checkout@v7`, `actions/upload-artifact@v7` and `anthropics/claude-code-action@v1`, which +Dependabot leaves alone until a new major exists. The reusables keep SHA pins, which Dependabot +handles here. `DRIVER_AGENTS_REF` stays manual — it appears in **two** kit files, `claude.yml` and `shopify-tool-smoke.yml`, and must carry the same pin in both or the smoke test verifies a revision the implementer never runs — and to the `VERSION` + `SHA256` pair in `lint.yml`, which must be bumped together or the checksum check fails the job. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 0337996..045f0c9 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -33,13 +33,15 @@ Avara #195 diagnosis gets confirmed. - **The first real ticket through v1.15.0.** The guard step should stay quiet; if it fires, the failure note lands on the issue and the transcript is in the run log (`show_full_output`). -- **foundrae-blackridge@staging** will drift again: its Dependabot bumped `claude-code-action` to - 1.0.210 on 2026-09-02 (#174) and the wave brought it back to the kit's 1.0.201. The audit reports - that as the kit being behind, which is the correct reading. +- **Major bumps of the kit's floated actions.** `actions/checkout@v7`, `actions/upload-artifact@v7` + and `anthropics/claude-code-action@v1` float in the kit; when a new major ships, a fleet repo's + Dependabot moves ahead and the audit reads the kit as behind until the kit's major is bumped. + Below a major boundary the foundrae-blackridge #174 drift-and-rollback cannot recur. - **The cooldown exemption** is unverified live until a tag lands and a repo carrying the kit block bumps the same day; vite-plugin-shopify-clean is the one to watch at the next tag. -- **WebSearch/WebFetch** stay off: the caveat's condition (#690 fixed **and** the pin bumped) is half - met by this release; #690 was still open at 2026-07-28. Re-check at the next pin bump. +- **WebSearch/WebFetch** stay off until #690 ships a fix (still open at 2026-07-28); the action + floats on `v1`, so the fix arrives on its own and the caveat comment in `claude.yml` is what gets + removed. - A human `@claude` (tag mode) still gets the action's own co-author text; the nine-item quality standard is global to `--append-system-prompt` — both unchanged. diff --git a/docs/claude-yml-wave-plan.md b/docs/claude-yml-wave-plan.md index f2747dc..4d8c1d7 100644 --- a/docs/claude-yml-wave-plan.md +++ b/docs/claude-yml-wave-plan.md @@ -29,7 +29,7 @@ un-started Plan B work; it is the ride-along list for the wave after `v1.15.0`. | **Figma wrapper wiring** | `claude_args` gains `--mcp-config '{"mcpServers":{"figma":{…}}}'` as inline JSON (never a file path — dropped in tag mode), `--allowedTools` gains the read-only `mcp__figma__*` tools, and `lint.yml`'s quote gate moves **4 → 6** in the same commit. Wiring and tool list: [`figma-mcp-in-ci.md`](figma-mcp-in-ci.md). | The read-only REST-backed wrapper exists in driver-agents and answers one call from a throwaway Actions run. Not `mcp.figma.com`, so that doc's re-open tripwire does not apply. | | **Figma caveat rewrite** | CAVEAT 2 above `--allowedTools` says no rail can read a design; once the wrapper is wired it says what the implementer can read (node JSON, rendered PNGs) and that writes are never offered. | Same as above. | | **Macroscope CLI in the run** | A setup step installs the CLI with the Claude Code plugin and the issue prompt runs `/macroscope:autoloop` before the pre-review. | A non-interactive credential. The installer takes `--tools claude --yes`, but auth is a browser wizard under `~/.macroscope` and reviews bill agent credits; nothing in the docs or installer offers a token path. Ask Macroscope; same shape as the Figma blocker until answered. | -| **WebSearch / WebFetch** | Re-add to `--allowedTools`. | anthropics/claude-code-action#690 ships a fix (open as of 2026-07-28). The kit floats on `v1` since v1.16.0, so the fix arrives on its own; the caveat comment is what gets removed. | +| **WebSearch / WebFetch** | Re-add to `--allowedTools`. | anthropics/claude-code-action#690 ships a fix (open as of 2026-07-28). The kit floats on `v1`, so the fix arrives on its own; the caveat comment is what gets removed. | | **Model** | None planned; `--model fable --effort xhigh` stays. | The Fable billing answer above; fallback is `--model opus` (MODEL NOTE in `claude.yml`). | Anything that only touches the kit and none of these gates can ride the next reusable tag instead — diff --git a/docs/fleet-operations.md b/docs/fleet-operations.md index dc09a35..88ca4b2 100644 --- a/docs/fleet-operations.md +++ b/docs/fleet-operations.md @@ -170,7 +170,9 @@ carried `a54c91e` in a pin line, so no diff base produced a matching `-` line an have rejected all five files on target #1. **Sed the pin; don't patch it.** **3. Per-repo state that must survive.** `SHOPIFY_STORE_NAME` in `claude.yml` and -`shopify-tool-smoke.yml`, and any Dependabot-bumped action pins. Surveyed at v1.11.0: the fleet's +`shopify-tool-smoke.yml` — nothing else: the kit's third-party actions float on major tags, and a +repo's Dependabot moving one to a new major is drift for the wave to roll back, not state to keep. +Surveyed at v1.11.0: the fleet's `claude.yml` copies were byte-identical to the kit except Avara's store handle, and there was no Dependabot drift — but survey, don't assume. diff --git a/docs/reusable-conversion-scope.md b/docs/reusable-conversion-scope.md index 84da120..58cf532 100644 --- a/docs/reusable-conversion-scope.md +++ b/docs/reusable-conversion-scope.md @@ -37,8 +37,8 @@ Three things moved. None of them flips the status on its own. - **The business case shrank.** "A wave is re-copying the whole file while hand-preserving per-repo edits" stopped being true at v1.13.0 — `tools/fleet-wave.sh` does exactly that mechanically and `fleet-pin-audit.sh` compares whole-file content. What is left of the case: churn (`claude.yml` changed - in 6 of the last 8 releases), moving three action pins somewhere Dependabot can see them, and one - central `DRIVER_AGENTS_REF`. Effort is unchanged at **20–27h**. + in 6 of the last 8 releases) and one central `DRIVER_AGENTS_REF` — the three action refs left the case + when the kit floated them on major tags. Effort is unchanged at **20–27h**. --- @@ -65,10 +65,9 @@ and `DRIVER_AGENTS_REF` stays hand-edited fleet-wide for as long as `shopify-too file (open decision 1). What conversion removes is the *logic* that a wave could hand-carry a subset of — which is the specific failure that produced the Avara incident. -Secondary win (moot since v1.16.0 — the kit floats these on major tags): `actions/checkout` (`templates/github/claude.yml:130`), `claude-code-action` (`:310`) and -`actions/upload-artifact` (`:496`, added by v1.8.0) move out of `templates/` and into `.github/workflows/`, -which `.github/dependabot.yml` (`directory: "/"`) actually scans — converting **three** documented manual pins -into bot-managed ones. +Secondary win, gone: the three third-party action refs in `templates/github/claude.yml` float on major +tags now, so nothing there is a manual pin for the conversion to move; `DRIVER_AGENTS_REF` is the one +central pin left. --- diff --git a/templates/github/README.md b/templates/github/README.md index 1064de5..914544e 100644 --- a/templates/github/README.md +++ b/templates/github/README.md @@ -114,8 +114,9 @@ Requested, approved → Ready for QA) were retired with the review leg at v1.12. entry already → keep it and set its `interval` to `daily`. Dependabot scans the default branch only, so a repo carrying the kit on other branches (Palmers) needs one entry per branch with `target-branch:` set. - **Re-copying into a repo that already has the kit?** Preserve that repo's own Dependabot action - pins — re-copy the workflow bodies, but don't clobber pins Dependabot has since bumped there. + **Re-copying into a repo that already has the kit?** Let the wave do it + (`tools/fleet-wave.sh --only `): whole-file, with `SHOPIFY_STORE_NAME` the one per-repo + value it preserves. **And check for an existing `.github/workflows/lint.yml`** — a repo that hand-rolled its own would be silently clobbered by the kit's; it is the one kit *workflow* name likely to already exist. diff --git a/templates/github/claude.yml b/templates/github/claude.yml index c85df0b..6312b33 100644 --- a/templates/github/claude.yml +++ b/templates/github/claude.yml @@ -291,14 +291,20 @@ jobs: # attempt with `timeout`, retries 5x with backoff, and fails LOUDLY if the binary is still # missing, so a real outage is an honest red job — not a confusing SDK error with the # tracking comment stuck on "working…". - # Both float on purpose (v1.16.0): the CLI binary to current stable, the action on its `v1` tag. - # A pinned action SHA bought no supply-chain safety the unpinned CLI download had not already - # given away, and it was the one pin in this file nothing bumps — Dependabot scans - # `.github/workflows/`, never `templates/` — while every fleet repo's Dependabot bumped its own - # copy the day a release shipped, so the fleet always ran ahead of the kit and each wave rolled - # it back (foundrae-blackridge #174). Dependabot leaves a major-tag ref alone until a new major - # exists. The trade: a regression in a new action release reaches every repo on its next run, - # and holding the fleet on a known version means pinning a SHA here and waving. + # Both float on purpose: the CLI binary to current stable, the action on its `v1` tag, which + # Anthropic re-points on every release. The action SHA was the one pin in this file nothing + # bumps — Dependabot scans `.github/workflows/`, never `templates/` — while a fleet repo running + # the github-actions updater bumped its deployed copy when the action released, so the fleet + # ran ahead of the kit and each wave rolled it back (foundrae-blackridge #174). Dependabot + # respects the precision a ref chose and leaves `@v1` alone until a v2 exists; a short race + # right after a release (before the tag moves) and one unanswered regression report are the + # known exceptions, and a stray bump is drift the next wave rolls back — the kit's + # dependabot.yml also ignores minor/patch bumps of this action outright. What the pin bought: + # `v1` is a mutable tag a bot repoints, and the CLI install below is already unpinned from the + # same vendor, so the SHA covered one of two channels into a job holding the OAuth token — a + # small reduction, not none. The trade: a regression in a new action release reaches every + # repo on its next run, and holding the fleet on a known version means pinning a SHA here and + # waving. # Path is fixed because runs-on is ubuntu-latest (GitHub-hosted) → $HOME is always /home/runner. - name: Install Claude Code (latest stable, resilient) run: | diff --git a/templates/github/dependabot.yml b/templates/github/dependabot.yml index a48d834..87f948f 100644 --- a/templates/github/dependabot.yml +++ b/templates/github/dependabot.yml @@ -18,6 +18,11 @@ updates: default-days: 3 exclude: - "DriverDigital/workflows*" + # The kit's claude.yml floats the action on `v1`, which Dependabot leaves alone by design; this + # closes the reported edge where it bumps a major-tag ref to a patch anyway. Majors still flow. + ignore: + - dependency-name: "anthropics/claude-code-action" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] assignees: - "mcarter-astronautdev" groups: From 7e4a41738635fefc369f653ed4b63f37406eae2c Mon Sep 17 00:00:00 2001 From: Maria Carter Date: Thu, 10 Sep 2026 21:20:20 +0200 Subject: [PATCH 3/3] Reusables and this repo's CI float on major tags too; Dependabot proposes majors only, here and in the kit --- .github/dependabot.yml | 14 ++++++++++---- .github/workflows/dependabot-auto-merge.yml | 3 ++- .github/workflows/dependabot-report.yml | 4 ++-- .github/workflows/dependabot-validate.yml | 4 ++-- .github/workflows/lint.yml | 2 +- .github/workflows/pr-first-review.yml | 4 ++-- .github/workflows/ticketed-review.yml | 4 ++-- CLAUDE.md | 2 +- README.md | 7 ++++--- templates/github/claude.yml | 2 +- templates/github/dependabot.yml | 7 ++++--- 11 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a2faf1f..6067293 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,9 @@ -# Keeps the SHA-pinned actions inside these reusable workflows current. -# Dependabot reads the `# vX` comment on each `uses:` pin, watches for new -# releases, and opens grouped PRs that bump the SHA (and the comment). +# The actions inside these reusable workflows float on their major tag (@v7, @v1), so every run +# picks up minor and patch releases on its own and Dependabot has nothing to propose below a +# major. Policy (Maria, 2026-09-10): majors are the only action bumps that get a PR anywhere — +# a breaking change is fixed after the fact rather than every patch being shepherded fleet-wide. # -# Two groups, by risk: +# Two groups, by risk, for those major bumps: # • github-owned (actions/*) — GitHub-maintained, low-risk → # AUTO-MERGED by dependabot-auto-merge.yml # • claude-code-action (anthropics/*) — holds AGENTS_GH_PAT → a human reviews @@ -16,6 +17,11 @@ updates: directory: "/" # scans .github/workflows/ schedule: interval: "weekly" + # Minor and patch bumps never get a PR: a floating ref already has them, and this closes the + # reported Dependabot edge that rewrites a major-tag ref into a pinned patch. + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] groups: github-owned: patterns: diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index f661877..b51601b 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -4,7 +4,8 @@ name: Dependabot auto-merge (GitHub-owned actions) # (actions/checkout, actions/upload-artifact, actions/download-artifact — all # GitHub-maintained, low-risk). The `claude-code-action` group is a SEPARATE # Dependabot group and is deliberately NOT auto-merged: it holds AGENTS_GH_PAT, -# so a human reviews those bumps. +# so a human reviews those bumps. Since the actions float on their major tag, +# the only PRs either group produces are major bumps. # # Uses only the built-in `gh` CLI (no external action) so it is not subject to # the allowed-actions allowlist. The approval counts because the repo has diff --git a/.github/workflows/dependabot-report.yml b/.github/workflows/dependabot-report.yml index 04bb07d..acd4652 100644 --- a/.github/workflows/dependabot-report.yml +++ b/.github/workflows/dependabot-report.yml @@ -78,7 +78,7 @@ jobs: id: download if: steps.provenance.outputs.report == 'true' continue-on-error: true # a real-outcome run with no artifact must still ping a human, not error the job - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@v8 with: name: dependabot-validation path: _artifacts @@ -115,7 +115,7 @@ jobs: # to initiate it does not widen the trust surface. continue-on-error: a verdict-generation hiccup # must never drop the human-ping in the steps below. continue-on-error: true - uses: anthropics/claude-code-action@c81e3bc69d1b18badbb63ba39581218f02421678 # v1.0.201 + uses: anthropics/claude-code-action@v1 env: CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" # anthropics/claude-code-action#1499; see templates/github/claude.yml with: diff --git a/.github/workflows/dependabot-validate.yml b/.github/workflows/dependabot-validate.yml index 70121bf..d381784 100644 --- a/.github/workflows/dependabot-validate.yml +++ b/.github/workflows/dependabot-validate.yml @@ -48,7 +48,7 @@ jobs: - name: Checkout PR head (UNTRUSTED — no credentials persisted) if: steps.gate.outputs.run == 'true' - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -189,7 +189,7 @@ jobs: - name: Upload validation artifact if: ${{ always() && steps.gate.outputs.run == 'true' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7 with: name: dependabot-validation path: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b2034de..6884a4b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7 # Pinned release + checksum instead of the upstream `rhysd/actionlint` action: the org # enforces a selected-actions allowlist, so a dependency-free download is one less thing to diff --git a/.github/workflows/pr-first-review.yml b/.github/workflows/pr-first-review.yml index 3087aa7..40688be 100644 --- a/.github/workflows/pr-first-review.yml +++ b/.github/workflows/pr-first-review.yml @@ -117,7 +117,7 @@ jobs: - name: Checkout PR head (same-repo / trusted) if: ${{ steps.guard.outputs.same_repo == 'true' && steps.ticket.outputs.has_ticket == 'false' }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -148,7 +148,7 @@ jobs: - name: Run /code-review (comments only — execution-free, read-only tools) id: review if: ${{ steps.guard.outputs.same_repo == 'true' && steps.ticket.outputs.has_ticket == 'false' }} - uses: anthropics/claude-code-action@c81e3bc69d1b18badbb63ba39581218f02421678 # v1.0.201 + uses: anthropics/claude-code-action@v1 env: CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" # anthropics/claude-code-action#1499; see templates/github/claude.yml with: diff --git a/.github/workflows/ticketed-review.yml b/.github/workflows/ticketed-review.yml index 05a3be1..874a2fc 100644 --- a/.github/workflows/ticketed-review.yml +++ b/.github/workflows/ticketed-review.yml @@ -158,7 +158,7 @@ jobs: - name: Checkout the PR head (read-only review) if: steps.resolve.outputs.skip == 'false' && steps.round.outputs.over == 'false' - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@v7 with: ref: ${{ steps.resolve.outputs.head_sha }} fetch-depth: 0 @@ -200,7 +200,7 @@ jobs: # Don't abort the job on a review error — the decide step turns an errored review into an # honest handoff (never a false "clean"). The review-step OUTCOME is what it reads. continue-on-error: true - uses: anthropics/claude-code-action@c81e3bc69d1b18badbb63ba39581218f02421678 # v1.0.201 + uses: anthropics/claude-code-action@v1 env: CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" # anthropics/claude-code-action#1499; see templates/github/claude.yml with: diff --git a/CLAUDE.md b/CLAUDE.md index 26c721b..1324ab9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,7 +95,7 @@ caveats: README "Release + repin order"; wave mechanics and fleet counts: `docs/ `env:`, and it scans only `.github/workflows/`, never `templates/`. That is also why the kit's whole-file workflows reference third-party actions by major tag (`@v1`, `@v7`) — a SHA there is a pin nothing bumps while a fleet repo's Dependabot bumps its copy when the action releases. The - reusables stay SHA-pinned. + reusables float the same way; majors are the only action bumps that get a PR anywhere. - `dependabot-validate` stub `name:` stays byte-identical (`Dependabot validate`) — `-report`'s `workflow_run` name-matches it. The job always runs and branches internally; never `if:`-skip it. - Never `pull_request_target`. Never set `anthropic_api_key` (overrides OAuth, bills at API rates). diff --git a/README.md b/README.md index 0cea03b..ad43f89 100644 --- a/README.md +++ b/README.md @@ -353,8 +353,8 @@ Waved to all 21 pairs on 2026-08-02; fleet uniform, 108 pins, zero stale. action itself releases, so a SHA there guaranteed the fleet ran ahead of the kit and each wave rolled it back (foundrae-blackridge #174). The three whole-file kit workflows therefore reference `actions/checkout@v7`, `actions/upload-artifact@v7` and `anthropics/claude-code-action@v1`, which -Dependabot leaves alone until a new major exists. The reusables keep SHA pins, which Dependabot -handles here. `DRIVER_AGENTS_REF` stays manual — it appears in **two** +Dependabot leaves alone until a new major exists. The reusables float the same way, so the only action +bump that ever gets a PR — here or in a fleet repo — is a major. `DRIVER_AGENTS_REF` stays manual — it appears in **two** kit files, `claude.yml` and `shopify-tool-smoke.yml`, and must carry the same pin in both or the smoke test verifies a revision the implementer never runs — and to the `VERSION` + `SHA256` pair in `lint.yml`, which must be bumped together or the checksum check fails the job. @@ -408,7 +408,8 @@ Two files in `.github/workflows/` are **this repo's own CI**, not products — t and never ship to the fleet: `lint.yml` (actionlint + shellcheck over the reusables *and* the kit, so a broken workflow can't reach consumer repos) and `dependabot-auto-merge.yml` (auto-merges this repo's own `github-owned` Dependabot bumps; the `claude-code-action` group is deliberately excluded, so those land by -hand). +hand). Both are quiet below a major: every third-party action here and in the kit floats on its major +tag, so a run already has each minor and patch, and `.github/dependabot.yml` ignores everything but majors. **`actionlint` is a required status check on `main`** (set 2026-08-02) — before that, `lint.yml` could report red without being able to block. Note the name collision: this repo's own `lint.yml` and the kit's diff --git a/templates/github/claude.yml b/templates/github/claude.yml index 6312b33..150e2ac 100644 --- a/templates/github/claude.yml +++ b/templates/github/claude.yml @@ -299,7 +299,7 @@ jobs: # respects the precision a ref chose and leaves `@v1` alone until a v2 exists; a short race # right after a release (before the tag moves) and one unanswered regression report are the # known exceptions, and a stray bump is drift the next wave rolls back — the kit's - # dependabot.yml also ignores minor/patch bumps of this action outright. What the pin bought: + # dependabot.yml also ignores every minor/patch action bump outright. What the pin bought: # `v1` is a mutable tag a bot repoints, and the CLI install below is already unpinned from the # same vendor, so the SHA covered one of two channels into a job holding the OAuth token — a # small reduction, not none. The trade: a regression in a new action release reaches every diff --git a/templates/github/dependabot.yml b/templates/github/dependabot.yml index 87f948f..5a0fc22 100644 --- a/templates/github/dependabot.yml +++ b/templates/github/dependabot.yml @@ -18,10 +18,11 @@ updates: default-days: 3 exclude: - "DriverDigital/workflows*" - # The kit's claude.yml floats the action on `v1`, which Dependabot leaves alone by design; this - # closes the reported edge where it bumps a major-tag ref to a patch anyway. Majors still flow. + # Actions float on their major tag in the kit, so a run already has every minor and patch; + # majors are the only action bumps that get a PR. This also closes the reported Dependabot edge + # that rewrites a major-tag ref into a pinned patch, which would stop that copy floating. ignore: - - dependency-name: "anthropics/claude-code-action" + - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] assignees: - "mcarter-astronautdev"