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
8 changes: 0 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,6 @@ jobs:
"::error::Release tag must point to a commit object"
)

verification = tag_object.get("verification") or {}
if not verification.get("verified"):
reason = verification.get("reason", "unknown")
raise SystemExit(
"::error::Release tag signature is not verified by GitHub "
f"(reason: {reason})"
)

commit_sha = str(target_object.get("sha", ""))
if len(commit_sha) != 40:
raise SystemExit("::error::Resolved release commit SHA is malformed")
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
already proved a detached bootstrap commit containing the full final payload. That removes the
ambiguity around how to create `release/X.Y.Z` after bootstrap-path verification while keeping
the PR diff against `origin/main` as the authoritative scope checkpoint.
- **Release publication now enforces annotated tags without depending on a separately configured GitHub-verified tag signature.**
The publish workflow and release runbook now agree that `vX.Y.Z` must be an immutable annotated
tag object, and the runbook includes the one allowed recovery path for replacing an accidentally
pushed lightweight tag before any public release object or assets exist.
- **Release-tag immutability checks now live with the release architecture contract instead of the Python support owner.**
The repository validator that owns supported-interpreter truth now limits itself to Python
matrix wiring, while the publish workflow's annotated-tag requirements are enforced by the
release architecture tests that own publication semantics.
- **Runtime boundaries, release contracts, and validation semantics now fail closed earlier and more explicitly.**
Streamed resource loading now enforces bounded input before allocation, filesystem loaders now
apply bounded no-follow reads, custom-function and parsing diagnostics redact payloads by
Expand Down
22 changes: 20 additions & 2 deletions docs/RELEASE_PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,11 @@ Do not create the tag until the exact merged `main` commit you intend to tag has

## Step 6: Tag, Publish Workflow, And Asset Convergence

Create and push the version tag only after Step 5 is green:
Create and push the version tag only after Step 5 is green. Use an annotated tag object, not a
lightweight tag:

```bash
git tag vX.Y.Z
git tag -a vX.Y.Z -m "Release X.Y.Z"
git push origin vX.Y.Z
```

Expand Down Expand Up @@ -340,6 +341,23 @@ the PyPI job fails — repair the workflow on `main`, merge the fix, and then re
`workflow_dispatch` for the existing tag. Do not delete, move, or recreate the tag to retrigger
publication.

If the publish workflow fails immediately because the tag is the wrong object type and GitHub
Release assets were never created, fix the contract first, then replace the tag exactly once with
an annotated tag on the intended release commit:

```bash
gh release view vX.Y.Z --json tagName,isDraft,isPrerelease,publishedAt,url,assets || true
git tag -d vX.Y.Z
git push --delete origin vX.Y.Z
git tag -a vX.Y.Z -m "Release X.Y.Z"
git push origin vX.Y.Z
```

This recovery is allowed only when both of these are true:

- the failed publish run exited before any public release object or assets were created;
- the replacement tag points to the same intended release commit you already verified in Step 5.

If GitHub Release assets need manual convergence after the workflow, use:

```bash
Expand Down
14 changes: 9 additions & 5 deletions scripts/python_support_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,20 @@ def _validate_workflows(root: Path, errors: list[str]) -> None:
errors=errors,
)

# Premise:
# Python support owns interpreter-version truth for the publication workflow,
# but release-tag immutability and tag-object semantics are a separate
# release-contract concern.
#
# Reason:
# Keeping this validator scoped to Python-version wiring prevents unrelated
# release-tag policy changes from breaking the Python support owner while
# the release architecture tests remain responsible for tag semantics.
publish_markers = (
"release-contract:",
"fromJSON(needs.release-contract.outputs.supported-json)",
"needs.release-contract.outputs.release-commit",
"needs.release-contract.outputs.freethreaded-version",
"Resolve immutable annotated release tag",
"/git/ref/tags/",
"/git/tags/",
"Release tags must be annotated tag objects",
"Release tag signature is not verified by GitHub",
)
for marker in publish_markers:
_expect(
Expand Down
16 changes: 16 additions & 0 deletions tests/test_architecture_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,19 @@ def test_release_workflows_do_not_depend_on_node20_compatibility_shims() -> None
assert publish_workflow.count(
"actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"
) >= 3


def test_publish_workflow_requires_annotated_tags_without_signature_verification_gate() -> None:
"""The publish workflow should require annotated tags but not an external signing setup."""
publish_workflow = (REPO_ROOT / ".github" / "workflows" / "publish.yml").read_text(
encoding="utf-8"
)

assert "Resolve immutable annotated release tag" in publish_workflow
assert "/git/ref/tags/" in publish_workflow
assert "/git/tags/" in publish_workflow
assert "Release tags must be annotated tag objects" in publish_workflow
assert 'ref_object.get("type") != "tag"' in publish_workflow
assert "Release tag must point to a commit object" in publish_workflow
assert "Release tag signature is not verified by GitHub" not in publish_workflow
assert 'verification = tag_object.get("verification")' not in publish_workflow
10 changes: 10 additions & 0 deletions tests/test_documentation_tooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,16 @@ def test_release_protocol_artifact_leak_check_uses_base_tooling() -> None:
assert "| rg " not in text


def test_release_protocol_requires_annotated_tags_and_documents_lightweight_recovery() -> None:
"""Release instructions should require annotated tags and bound the wrong-tag recovery path."""
text = (REPO_ROOT / "docs" / "RELEASE_PROTOCOL.md").read_text(encoding="utf-8")

assert 'git tag -a vX.Y.Z -m "Release X.Y.Z"' in text
assert "wrong object type" in text
assert "git push --delete origin vX.Y.Z" in text
assert "the failed publish run exited before any public release object or assets were created" in text


def test_atheris_inventory_readme_matches_target_manifest() -> None:
"""The published Atheris inventory should stay aligned with the live target registry."""
readme = (REPO_ROOT / "fuzz_atheris" / "README.md").read_text(encoding="utf-8")
Expand Down