From 4486984f88c329c4abaec12290784c9ed36d6f3f Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 11 Sep 2026 15:33:17 -0700 Subject: [PATCH] Record the Release Gate's Branch-Name Refusal in the D2 Contract (#1550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #1531. The release task's `validate-release` gate refuses, ahead of its smoke exit, any `branch` input that `git check-ref-format --branch` rejects (`build-release-task.yml:139-142`). That refusal landed with the fix for the version step logging the branch unsanitized, and `WORKFLOW.md` D2 never recorded it, so the contract listed less than the gate asserts at entry. Nothing reachable trips it, since a documented caller passes `github.ref_name` or `github.base_ref || github.ref_name`, but D2's validate-at-entry guarantees exist to record exactly what the gate asserts. - `WORKFLOW.md`: a new D2.5 states the input (a release task taking a `branch` input, on any run), the output (the gate runs `git check-ref-format --branch` over that value and fails when git rejects it, ahead of D2.2's smoke exit, with an error echoing no part of the value), and what it prevents (a `::` or `##[` in a caller's value forming a workflow command wherever a later step prints the branch, on a smoke run as much as on a publish). - The `workflow-ci-contract` Skill carries section 4 whole as a generated include, so `scripts/build_dist.py` refreshed `d-guarantees.md` in the source tree and in both generated distributions. - `reports/canonical-review.json`: the whole-unit pass over the two canonical units this change moves. D2.5 is its own item rather than a clause of D2.2, which keeps each item asserting one thing and leaves D2.1 through D2.4 numbered as the audit reports and `publish-release.yml` already cite them. The S1, S4, and S10 rows of section 5 are unchanged: each describes a run whose branch value is a real ref name, which the refusal never reaches. ## Verification - `python3 scripts/build_dist.py --check`: generated skill distributions are current. - `python3 spec/validate.py`: 22 cataloged repos classify cleanly. - `python3 scripts/prose_lint.py . --diff HEAD` and `python3 scripts/repo_gate.py --check eol`: clean. - `python3 scripts/docker_lint.py`: 7 linters clean, markdownlint over 185 files and editorconfig-checker over 352. - Local strict review pass over the branch diff, and a carried-content pass over both canonical units, recorded. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_012u4N7gEcqT9QwTusr1qtUc ## Summary by CodeRabbit - **Documentation** - Added a workflow contract requirement to validate release branch names before proceeding. - Invalid branch names are rejected without exposing the submitted value in errors. - **Chores** - Updated recorded workflow contract review metadata and statuses. Co-authored-by: Claude Opus 5 (1M context) --- .../references/d-guarantees.md | 1 + .../.source-digests/workflow-ci-contract | 2 +- .../references/d-guarantees.md | 1 + .../references/d-guarantees.md | 1 + WORKFLOW.md | 1 + reports/canonical-review.json | 16 ++++++++-------- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.agents/skills/workflow-ci-contract/references/d-guarantees.md b/.agents/skills/workflow-ci-contract/references/d-guarantees.md index f1f371e1..0b8bd087 100644 --- a/.agents/skills/workflow-ci-contract/references/d-guarantees.md +++ b/.agents/skills/workflow-ci-contract/references/d-guarantees.md @@ -23,6 +23,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output - **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and a dependent whose `if:` carries no status-check function gets the implicit `success()` and skips with it. `dotnet-publish`, `build-nuget`, and `build-pypi` are such dependents of `validate-release`, so a job-level skip there would skip their smoke builds with it. *Prevents: a non-default leg published as stable, a build-metadata false-positive, and the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.* +- **D2.5 The release gate refuses a branch input git would not accept as a name.** Input: a release task taking a `branch` input, on any run. Output: the gate runs `git check-ref-format --branch` over that value and fails when git rejects it, ahead of D2.2's smoke exit, with an error echoing no part of the value. *Prevents: a `::` or `##[` in a caller's value forming a workflow command wherever a later step prints the branch, on a smoke run as much as on a publish.* ### D3 - Versioning and Classification diff --git a/.claude-plugin/fleet-skills/.source-digests/workflow-ci-contract b/.claude-plugin/fleet-skills/.source-digests/workflow-ci-contract index 7c7c78a8..c2dc11dd 100644 --- a/.claude-plugin/fleet-skills/.source-digests/workflow-ci-contract +++ b/.claude-plugin/fleet-skills/.source-digests/workflow-ci-contract @@ -1 +1 @@ -0399e2dde2dba6d3 +ef59059f32df5cea diff --git a/.claude-plugin/fleet-skills/skills/workflow-ci-contract/references/d-guarantees.md b/.claude-plugin/fleet-skills/skills/workflow-ci-contract/references/d-guarantees.md index f1f371e1..0b8bd087 100644 --- a/.claude-plugin/fleet-skills/skills/workflow-ci-contract/references/d-guarantees.md +++ b/.claude-plugin/fleet-skills/skills/workflow-ci-contract/references/d-guarantees.md @@ -23,6 +23,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output - **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and a dependent whose `if:` carries no status-check function gets the implicit `success()` and skips with it. `dotnet-publish`, `build-nuget`, and `build-pypi` are such dependents of `validate-release`, so a job-level skip there would skip their smoke builds with it. *Prevents: a non-default leg published as stable, a build-metadata false-positive, and the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.* +- **D2.5 The release gate refuses a branch input git would not accept as a name.** Input: a release task taking a `branch` input, on any run. Output: the gate runs `git check-ref-format --branch` over that value and fails when git rejects it, ahead of D2.2's smoke exit, with an error echoing no part of the value. *Prevents: a `::` or `##[` in a caller's value forming a workflow command wherever a later step prints the branch, on a smoke run as much as on a publish.* ### D3 - Versioning and Classification diff --git a/.github/skills/workflow-ci-contract/references/d-guarantees.md b/.github/skills/workflow-ci-contract/references/d-guarantees.md index f1f371e1..0b8bd087 100644 --- a/.github/skills/workflow-ci-contract/references/d-guarantees.md +++ b/.github/skills/workflow-ci-contract/references/d-guarantees.md @@ -23,6 +23,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output - **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and a dependent whose `if:` carries no status-check function gets the implicit `success()` and skips with it. `dotnet-publish`, `build-nuget`, and `build-pypi` are such dependents of `validate-release`, so a job-level skip there would skip their smoke builds with it. *Prevents: a non-default leg published as stable, a build-metadata false-positive, and the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.* +- **D2.5 The release gate refuses a branch input git would not accept as a name.** Input: a release task taking a `branch` input, on any run. Output: the gate runs `git check-ref-format --branch` over that value and fails when git rejects it, ahead of D2.2's smoke exit, with an error echoing no part of the value. *Prevents: a `::` or `##[` in a caller's value forming a workflow command wherever a later step prints the branch, on a smoke run as much as on a publish.* ### D3 - Versioning and Classification diff --git a/WORKFLOW.md b/WORKFLOW.md index 3f424b81..5b410c49 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -151,6 +151,7 @@ The required behaviors, organized by domain. Each is a **MUST**, and its `Output - **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and a dependent whose `if:` carries no status-check function gets the implicit `success()` and skips with it. `dotnet-publish`, `build-nuget`, and `build-pypi` are such dependents of `validate-release`, so a job-level skip there would skip their smoke builds with it. *Prevents: a non-default leg published as stable, a build-metadata false-positive, and the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.* +- **D2.5 The release gate refuses a branch input git would not accept as a name.** Input: a release task taking a `branch` input, on any run. Output: the gate runs `git check-ref-format --branch` over that value and fails when git rejects it, ahead of D2.2's smoke exit, with an error echoing no part of the value. *Prevents: a `::` or `##[` in a caller's value forming a workflow command wherever a later step prints the branch, on a smoke run as much as on a publish.* ### D3 - Versioning and Classification diff --git a/reports/canonical-review.json b/reports/canonical-review.json index 37ed10f2..f54e0ce2 100644 --- a/reports/canonical-review.json +++ b/reports/canonical-review.json @@ -771,11 +771,11 @@ }, { "unit": ".agents/skills/workflow-ci-contract/references/d-guarantees.md > The Behavioral Contract", - "digest": "sha256:cb59602471dff258ead049a85eb5d517df893eb6f1c81899ecd243ea0e2e8262", + "digest": "sha256:3fc23c33c6b6e12cc1c93ebf4e1b8c00cc41029a8b90cccf0c772ff32daed922", "reviewer": "agent-skill", - "findings": 0, - "hubCommit": "09971b8051dd11d3748999cc5182116dd42ffa4c", - "stamp": "2026-09-11T19:32:07Z" + "findings": null, + "hubCommit": "d2a7a170ecf5ebf75ff7574a6031f6b4b89ee485", + "stamp": "2026-09-11T22:21:12Z" }, { "unit": ".agents/skills/workflow-ci-contract/references/test-methodology.md > (preamble)", @@ -1035,11 +1035,11 @@ }, { "unit": "WORKFLOW.md > 4. Behavioral Contract: Expected Outcomes", - "digest": "sha256:88009557df8f3467addd53d216e67f2ef2d7e928f05e4b23932ece5dee1c713a", + "digest": "sha256:adf2009ef4e8dd1b78c8ec5f102f479146affdc9940cfa841831a7adb8e4e5ac", "reviewer": "agent-skill", - "findings": 0, - "hubCommit": "09971b8051dd11d3748999cc5182116dd42ffa4c", - "stamp": "2026-09-11T19:32:07Z" + "findings": null, + "hubCommit": "d2a7a170ecf5ebf75ff7574a6031f6b4b89ee485", + "stamp": "2026-09-11T22:21:12Z" }, { "unit": "WORKFLOW.md > 5. Test Methodology",