Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ on:
required: false
type: boolean
default: false
waive_v179_qualification:
description: "Owner-approved waiver for the retained v1.7.9 candidate at c6871b9b only"
required: false
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -980,18 +985,19 @@ jobs:
with:
python-version: "3.11"
- name: Enforce and record the release-specific qualification waiver
if: inputs.waive_v176_qualification || inputs.waive_v178_qualification
if: inputs.waive_v176_qualification || inputs.waive_v178_qualification || inputs.waive_v179_qualification
env:
RELEASE_TAG: ${{ inputs.release_tag }}
WAIVE_V176: ${{ inputs.waive_v176_qualification }}
WAIVE_V178: ${{ inputs.waive_v178_qualification }}
WAIVE_V179: ${{ inputs.waive_v179_qualification }}
GH_ACTOR: ${{ github.actor }}
GH_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
shell: bash
run: |
set -euo pipefail
case "$WAIVE_V176:$WAIVE_V178:$RELEASE_TAG" in
true:false:v1.7.6|false:true:v1.7.8) ;;
case "$WAIVE_V176:$WAIVE_V178:${WAIVE_V179:-false}:$RELEASE_TAG" in
true:false:false:v1.7.6|false:true:false:v1.7.8|false:false:true:v1.7.9) ;;
*) printf 'Waiver must select exactly one authorized release.\n' >&2; exit 1 ;;
esac
{
Expand Down Expand Up @@ -1145,7 +1151,7 @@ jobs:
cp dist/*.whl dist/*.tar.gz verified-dist/

- name: Require signed full-product qualification before PyPI repair
if: ${{ !inputs.waive_v176_qualification && !inputs.waive_v178_qualification }}
if: ${{ !inputs.waive_v176_qualification && !inputs.waive_v178_qualification && !inputs.waive_v179_qualification }}
env:
RELEASE_TAG: ${{ inputs.release_tag }}
ENGRAPHIS_RELEASE_QUALIFICATION: ${{ secrets.ENGRAPHIS_RELEASE_QUALIFICATION }}
Expand All @@ -1160,10 +1166,11 @@ jobs:
--commit "$ENGRAPHIS_REPAIR_COMMIT" --tag "$RELEASE_TAG"

- name: Disclose the qualification waiver before PyPI repair
if: inputs.waive_v176_qualification || inputs.waive_v178_qualification
if: inputs.waive_v176_qualification || inputs.waive_v178_qualification || inputs.waive_v179_qualification
env:
WAIVE_V176: ${{ inputs.waive_v176_qualification }}
WAIVE_V178: ${{ inputs.waive_v178_qualification }}
WAIVE_V179: ${{ inputs.waive_v179_qualification }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ inputs.release_tag }}
Expand All @@ -1172,9 +1179,10 @@ jobs:
run: |
set -euo pipefail
# Each exception covers one retained candidate, not future reuse of its tag.
case "$WAIVE_V176:$WAIVE_V178:$RELEASE_TAG:$ENGRAPHIS_REPAIR_COMMIT" in
true:false:v1.7.6:6a441a75c8dd159607fa3933da83f600864b9146|\
false:true:v1.7.8:dce68e1602e580cd51b71e26db2ab04238df7df4) ;;
case "$WAIVE_V176:$WAIVE_V178:${WAIVE_V179:-false}:$RELEASE_TAG:$ENGRAPHIS_REPAIR_COMMIT" in
true:false:false:v1.7.6:6a441a75c8dd159607fa3933da83f600864b9146|\
false:true:false:v1.7.8:dce68e1602e580cd51b71e26db2ab04238df7df4|\
false:false:true:v1.7.9:c6871b9bf506eec6cebe96beee1ac252429a7b99) ;;
*) printf 'Waiver does not match an authorized source candidate.\n' >&2; exit 1 ;;
esac
{
Expand Down Expand Up @@ -1217,7 +1225,7 @@ jobs:
--version "${RELEASE_TAG#v}" --retries 18 --delay 10

- name: Require signed full-product qualification before GitHub repair
if: ${{ !inputs.waive_v176_qualification && !inputs.waive_v178_qualification }}
if: ${{ !inputs.waive_v176_qualification && !inputs.waive_v178_qualification && !inputs.waive_v179_qualification }}
env:
RELEASE_TAG: ${{ inputs.release_tag }}
ENGRAPHIS_RELEASE_QUALIFICATION: ${{ secrets.ENGRAPHIS_RELEASE_QUALIFICATION }}
Expand All @@ -1235,7 +1243,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ inputs.release_tag }}
WAIVE_QUALIFICATION: ${{ inputs.waive_v176_qualification || inputs.waive_v178_qualification }}
WAIVE_QUALIFICATION: ${{ inputs.waive_v176_qualification || inputs.waive_v178_qualification || inputs.waive_v179_qualification }}
GH_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
shell: bash
run: |
Expand All @@ -1245,7 +1253,7 @@ jobs:
promote_latest=true
if [ "$WAIVE_QUALIFICATION" = "true" ]; then
# A retained older repair must not displace a newer public Latest.
# Both authorized candidates already have a public release history;
# The candidate comparison uses the existing public release history;
# failed lookups or unknown tag formats stop before any release write.
current_latest="$(gh release view --repo "$GH_REPO" --json tagName --jq .tagName)"
promote_latest="$(python - "$RELEASE_TAG" "$current_latest" <<'PY'
Expand Down
30 changes: 26 additions & 4 deletions docs/RELEASE_QUALIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ full-product qualification. Passing the public build jobs is necessary but does
not replace the mandatory private readiness evidence. The workflow fails closed
when qualification configuration is missing, malformed, expired or inconsistent
with the selected source and distribution bytes. The owner-authorized repair
waivers for the retained v1.7.6 and v1.7.8 candidates are documented below.
waivers for the retained v1.7.6, v1.7.8 and v1.7.9 candidates are documented below.

The public verifier is `scripts/verify_release_qualification.py`. It verifies
Ed25519 signatures using `cryptography==50.0.0` in release jobs. It contains no
Expand Down Expand Up @@ -115,9 +115,9 @@ for these retained release candidates:
| `waive_v176_qualification` | `v1.7.6` | `6a441a75c8dd159607fa3933da83f600864b9146` |
| `waive_v178_qualification` | `v1.7.8` | `dce68e1602e580cd51b71e26db2ab04238df7df4` |

Select exactly one waiver input together with its matching `release_tag`. Both
inputs default to false. Combining them, selecting the wrong version, or reusing
a tag for another commit fails before any public write. The v1.7.8 waiver follows
Select exactly one waiver input together with its matching `release_tag`. All
waiver inputs default to false. Combining them, selecting the wrong version, or
reusing a tag for another commit fails before any public write. The v1.7.8 waiver follows
the owner's explicit instruction to remove publication blockers after integrating
and reviewing the beneficial local work. It reuses the distributions and evidence
from the successful automated validations of that tagged source.
Expand All @@ -133,6 +133,28 @@ do not mark any unverified gate as passing. All ordinary tag publications and
repairs outside these exact candidates still require a valid owner-signed
qualification.

## Owner-authorized retained v1.7.9 repair

On 2026-10-01, after reviewing the prepared exception and required public
disclosure, the repository owner explicitly approved this retained-candidate
repair and publication. This approval does not qualify the full product or
authorize exceptions for future candidates.

The `waive_v179_qualification` input defaults to false and is bound only
to `v1.7.9` at `c6871b9bf506eec6cebe96beee1ac252429a7b99`. It cannot be combined
with either earlier waiver or applied to a different tag or source commit.
The retained tag run's automated validation jobs passed; its publisher failed
because the signed full-product qualification receipt was absent. The repair
reuses the exact retained wheel, source archive and public evidence from that run.

This exception waives only the signed full-product qualification
requirement for those retained bytes. Protected environment review, retained
artifact verification and PyPI file identity checks remain required. Public
GitHub release disclosure must succeed before the first PyPI write and must
state that mandatory full-product acceptance gates remain unverified. Ordinary
tag publication and repair outside the explicitly selected exception still
require signed qualification.

## Public installed evidence

New release evidence requires all six installed surface cells: MCP and dashboard
Expand Down
81 changes: 53 additions & 28 deletions tests/test_release_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,11 @@ def test_publication_writes_require_qualification_except_scoped_release_waivers(
root = Path(__file__).resolve().parents[1]
workflow = yaml.safe_load((root / ".github/workflows/release.yml").read_text(encoding="utf-8"))
dispatch = workflow.get("on", workflow.get(True, {})).get("workflow_dispatch", {})
waiver_condition = "inputs.waive_v176_qualification || inputs.waive_v178_qualification"
for input_name in ("waive_v176_qualification", "waive_v178_qualification"):
waiver_condition = (
"inputs.waive_v176_qualification || inputs.waive_v178_qualification"
" || inputs.waive_v179_qualification"
)
for input_name in ("waive_v176_qualification", "waive_v178_qualification", "waive_v179_qualification"):
waiver_input = dispatch.get("inputs", {}).get(input_name, {})
assert waiver_input.get("type") == "boolean"
assert waiver_input.get("default") is False
Expand All @@ -200,15 +203,18 @@ def test_publication_writes_require_qualification_except_scoped_release_waivers(
assert step.get("if") == waiver_condition
assert "verified-dist/*" not in step["run"]
assert "release-evidence/*" not in step["run"]
assert "true:false:v1.7.6:6a441a75c8dd159607fa3933da83f600864b9146" in step["run"]
assert "false:true:v1.7.8:dce68e1602e580cd51b71e26db2ab04238df7df4" in step["run"]
assert "true:false:false:v1.7.6:6a441a75c8dd159607fa3933da83f600864b9146" in step["run"]
assert "false:true:false:v1.7.8:dce68e1602e580cd51b71e26db2ab04238df7df4" in step["run"]
assert "false:false:true:v1.7.9:c6871b9bf506eec6cebe96beee1ac252429a7b99" in step["run"]
assert step["env"]["WAIVE_V176"] == "${{ inputs.waive_v176_qualification }}"
assert step["env"]["WAIVE_V178"] == "${{ inputs.waive_v178_qualification }}"
assert step["env"]["WAIVE_V179"] == "${{ inputs.waive_v179_qualification }}"
continue
if "scripts.verify_release_qualification" in step.get("run", ""):
if name == "github-release-repair":
assert step.get("if") == (
"${{ !inputs.waive_v176_qualification && !inputs.waive_v178_qualification }}"
"${{ !inputs.waive_v176_qualification && !inputs.waive_v178_qualification"
" && !inputs.waive_v179_qualification }}"
)
else:
assert "if" not in step
Expand Down Expand Up @@ -238,9 +244,10 @@ def test_publication_writes_require_qualification_except_scoped_release_waivers(
waiver_guard = next(step for step in repair_steps
if step.get("name") == "Enforce and record the release-specific qualification waiver")
assert waiver_guard.get("if") == waiver_condition
assert "true:false:v1.7.6|false:true:v1.7.8" in waiver_guard["run"]
assert "true:false:false:v1.7.6|false:true:false:v1.7.8|false:false:true:v1.7.9" in waiver_guard["run"]
assert waiver_guard["env"]["WAIVE_V176"] == "${{ inputs.waive_v176_qualification }}"
assert waiver_guard["env"]["WAIVE_V178"] == "${{ inputs.waive_v178_qualification }}"
assert waiver_guard["env"]["WAIVE_V179"] == "${{ inputs.waive_v179_qualification }}"
disclosure = next(step for step in repair_steps
if step.get("name") == "Disclose the qualification waiver before PyPI repair")
publication = next(step for step in repair_steps
Expand All @@ -258,7 +265,7 @@ def test_publication_writes_require_qualification_except_scoped_release_waivers(

@pytest.mark.skipif(os.name == "nt", reason="release workflow executes in Linux bash")
@pytest.mark.parametrize("existing,edit_fails", [(False, False), (True, False), (True, True)])
@pytest.mark.parametrize("tag", ["v1.7.6", "v1.7.8"])
@pytest.mark.parametrize("tag", ["v1.7.6", "v1.7.8", "v1.7.9"])
def test_waiver_disclosure_cannot_follow_github_publication(tmp_path, existing, edit_fails, tag):
yaml = pytest.importorskip("yaml")
bash = shutil.which("bash")
Expand Down Expand Up @@ -412,6 +419,7 @@ def test_waiver_repair_preserves_newer_latest(tmp_path, latest, lookup_fails, ex
@pytest.mark.parametrize("tag,commit", [
("v1.7.6", "6a441a75c8dd159607fa3933da83f600864b9146"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4"),
("v1.7.9", "c6871b9bf506eec6cebe96beee1ac252429a7b99"),
])
def test_public_waiver_notice_precedes_pypi_even_if_later_repair_fails(
tmp_path, existing, edit_fails, draft, tag, commit,
Expand Down Expand Up @@ -454,6 +462,7 @@ def test_public_waiver_notice_precedes_pypi_even_if_later_repair_fails(
"ENGRAPHIS_REPAIR_COMMIT": commit,
"WAIVE_V176": str(tag == "v1.7.6").lower(),
"WAIVE_V178": str(tag == "v1.7.8").lower(),
"WAIVE_V179": str(tag == "v1.7.9").lower(),
"GH_RUN_URL": "https://example.test/run/1", "GH_CALLS": str(calls_path),
"EXISTING": str(existing).lower(), "EDIT_FAILS": str(edit_fails).lower(),
"DRAFT": str(draft).lower()}
Expand All @@ -475,18 +484,26 @@ def test_public_waiver_notice_precedes_pypi_even_if_later_repair_fails(


@pytest.mark.skipif(os.name == "nt", reason="release workflow executes in Linux bash")
@pytest.mark.parametrize("tag,commit,v176,v178", [
("v1.7.7", "6a441a75c8dd159607fa3933da83f600864b9146", "true", "false"),
("v1.7.6", "a" * 40, "true", "false"),
("v1.7.6", "", "true", "false"),
("v1.7.8", "a" * 40, "false", "true"),
("v1.7.8", "", "false", "true"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "true", "false"),
("v1.7.6", "6a441a75c8dd159607fa3933da83f600864b9146", "false", "true"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "true", "true"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "false", "false"),
@pytest.mark.parametrize("tag,commit,v176,v178,v179", [
("v1.7.7", "6a441a75c8dd159607fa3933da83f600864b9146", "true", "false", "false"),
("v1.7.6", "a" * 40, "true", "false", "false"),
("v1.7.6", "", "true", "false", "false"),
("v1.7.8", "a" * 40, "false", "true", "false"),
("v1.7.8", "", "false", "true", "false"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "true", "false", "false"),
("v1.7.6", "6a441a75c8dd159607fa3933da83f600864b9146", "false", "true", "false"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "true", "true", "false"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "false", "false", "false"),
("v1.7.9", "a" * 40, "false", "false", "true"),
("v1.7.9", "", "false", "false", "true"),
("v1.7.8", "dce68e1602e580cd51b71e26db2ab04238df7df4", "false", "false", "true"),
("v1.7.9", "c6871b9bf506eec6cebe96beee1ac252429a7b99", "false", "true", "true"),
("v1.7.9", "c6871b9bf506eec6cebe96beee1ac252429a7b99", "true", "false", "true"),
("v1.7.9", "c6871b9bf506eec6cebe96beee1ac252429a7b99", "false", "false", "false"),
])
def test_waiver_rejects_a_different_retained_candidate_before_any_public_write(tmp_path, tag, commit, v176, v178):
def test_waiver_rejects_a_different_retained_candidate_before_any_public_write(
tmp_path, tag, commit, v176, v178, v179,
):
yaml = pytest.importorskip("yaml")
bash = shutil.which("bash")
if bash is None:
Expand All @@ -507,23 +524,30 @@ def test_waiver_rejects_a_different_retained_candidate_before_any_public_write(t
"RUNNER_TEMP": str(tmp_path), "RELEASE_TAG": tag,
"ENGRAPHIS_REPAIR_COMMIT": commit, "GH_REPO": "test/repo",
"WAIVE_V176": v176, "WAIVE_V178": v178,
"WAIVE_V179": v179,
"GH_CALLS": str(calls_path)})
assert result.returncode != 0
assert not calls_path.exists()


@pytest.mark.skipif(os.name == "nt", reason="release workflow executes in Linux bash")
@pytest.mark.parametrize("tag,v176,v178,allowed", [
("v1.7.6", "true", "false", True),
("v1.7.8", "false", "true", True),
("v1.7.6", "true", "true", False),
("v1.7.8", "true", "true", False),
("v1.7.8", "true", "false", False),
("v1.7.6", "false", "true", False),
("v1.7.9", "false", "true", False),
("v1.7.8", "false", "false", False),
@pytest.mark.parametrize("tag,v176,v178,v179,allowed", [
("v1.7.6", "true", "false", "false", True),
("v1.7.8", "false", "true", "false", True),
("v1.7.9", "false", "false", "true", True),
("v1.7.6", "true", "true", "false", False),
("v1.7.8", "true", "true", "false", False),
("v1.7.8", "true", "false", "false", False),
("v1.7.6", "false", "true", "false", False),
("v1.7.9", "false", "true", "false", False),
("v1.7.8", "false", "false", "false", False),
("v1.7.9", "false", "false", "false", False),
("v1.7.9", "true", "false", "true", False),
("v1.7.9", "false", "true", "true", False),
("v1.7.9", "true", "true", "true", False),
("v1.7.8", "false", "false", "true", False),
])
def test_waiver_input_guard_rejects_ambiguous_or_unapproved_requests(tmp_path, tag, v176, v178, allowed):
def test_waiver_input_guard_rejects_ambiguous_or_unapproved_requests(tmp_path, tag, v176, v178, v179, allowed):
yaml = pytest.importorskip("yaml")
bash = shutil.which("bash")
if bash is None:
Expand All @@ -538,6 +562,7 @@ def test_waiver_input_guard_rejects_ambiguous_or_unapproved_requests(tmp_path, t
result = subprocess.run([bash, str(script)], cwd=tmp_path, capture_output=True, text=True,
timeout=20, env={**os.environ, "RELEASE_TAG": tag,
"WAIVE_V176": v176, "WAIVE_V178": v178,
"WAIVE_V179": v179,
"GH_ACTOR": "test-actor", "GH_RUN_URL": "https://example.test/run/1",
"GITHUB_STEP_SUMMARY": str(summary)})
assert (result.returncode == 0) is allowed, result.stderr
Expand Down
Loading