Skip to content

[v1.0] Gate releases on protected main lineage and independent approval #271

Description

@codeforester

Goal

Prove that every PyPI/GitHub release comes from reviewed main history and passes a meaningful approval boundary.

Background

The release validator checks only that the tag string matches VERSION and that a dated changelog section exists:

def validate_release_ref(version_path: Path, changelog_path: Path, tag: str) -> list[str]:
"""Return violations for a release ``tag`` and its source files."""
errors: list[str] = []
if not tag.startswith("v") or tag == "v":
return [f"release tag must be a v-prefixed version, got {tag!r}"]
version = tag[1:]
declared = version_path.read_text(encoding="utf-8").strip()
if declared != version:
errors.append(f"VERSION declares {declared!r}, but the release tag is {tag!r}")
lines = changelog_path.read_text(encoding="utf-8").splitlines()
heading = f"## [{version}] - "
heading_index = next(
(index for index, line in enumerate(lines) if line.startswith(heading)),
None,
)
if heading_index is None or RELEASE_HEADING.fullmatch(lines[heading_index]) is None:
errors.append(f"CHANGELOG.md is missing a dated release section for [{version}]")
return errors
next_section = next(
(index for index in range(heading_index + 1, len(lines)) if lines[index].startswith("## ")),
len(lines),
)
if not any(BULLET.match(line) for line in lines[heading_index + 1 : next_section]):
errors.append(f"CHANGELOG.md release section [{version}] has no release-note bullets")
return errors
. The package workflow publishes after those content checks:
- name: Validate release ref
env:
PUBLISH_TARGET: ${{ inputs.publish_target || '' }}
run: |
if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "v${{ steps.metadata.outputs.version }}" ]]; then
echo "Release tag must be v${{ steps.metadata.outputs.version }}; got $GITHUB_REF_NAME" >&2
exit 1
fi
if [[ "$PUBLISH_TARGET" == "pypi" && "$GITHUB_REF_TYPE" != "tag" ]]; then
echo "PyPI publication requires dispatching this workflow from the matching version tag." >&2
exit 1
fi
- name: Validate repository baseline
run: ./tests/validate.sh
- name: Validate changelog
run: python scripts/validate_changelog.py
- name: Validate tagged release notes
if: ${{ github.ref_type == 'tag' }}
env:
RELEASE_TAG: ${{ github.ref_name }}
run: python scripts/validate_release_ref.py
- name: Prepare clean artifact destination
run: |
git clean -ffdx
mkdir -p dist
- name: Install build and validation tools
run: python -m pip install --upgrade build twine
- name: Build sdist and wheel
run: python -m build --sdist --wheel --outdir dist
and
publish:
name: Publish reviewed distribution
needs: [build, smoke]
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: ${{ github.event_name == 'push' && 'pypi' || inputs.publish_target }}
url: ${{ github.event_name == 'push' && 'https://pypi.org/p/base-cli' || 'https://test.pypi.org/p/base-cli' }}
permissions:
contents: read
id-token: write
steps:
- name: Download reviewed distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: base-cli-dist-${{ github.run_id }}
path: dist
- name: Publish to TestPyPI
if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_target == 'testpypi' }}
uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74
with:
packages-dir: dist
repository-url: https://test.pypi.org/legacy/
- name: Publish to PyPI
if: ${{ (github.event_name == 'push' && github.ref_type == 'tag') || (github.event_name == 'workflow_dispatch' && inputs.publish_target == 'pypi') }}
uses: pypa/gh-action-pypi-publish@4bb033805d9e19112d8c697528791ff53f6c2f74
with:
packages-dir: dist
.

The release guide says production changes are merged to main and use an annotated-style tag, but CI does not verify the tag object type, exact tag target, or reachability from origin/main:

base-cli/docs/releasing.md

Lines 120 to 127 in 8a93d22

## Production release
1. Update `VERSION` and the changelog in a reviewed pull request.
2. Merge to `main` and create the matching `v${VERSION}` tag.
3. Approve the protected `pypi` environment. The workflow verifies the tag,
dated changelog section, builds and tests the artifact, then publishes the
exact artifact to PyPI via trusted publishing and creates the matching
GitHub Release.
. Live repository inspection also found main without branch protection; the PyPI environments allow the configured reviewer to approve their own deployment and administrators to bypass.

Scope

  • Verify tag type, target SHA, and main reachability before any publish, attestation, or release write permission.
  • Configure protected-branch required checks.
  • Make the production environment approval independent or explicitly document a time-bounded solo-maintainer exception.
  • Test the negative paths.

Acceptance Criteria

  • Lightweight tags, tags on unmerged commits, moved/mismatched tags, and shallow-history ambiguity fail closed.
  • The workflow fetches enough trusted history to prove ancestry.
  • Required checks and force-push/deletion policy protect main.
  • Production deployment prevents self-review when maintainer capacity permits; any temporary exception has an owner, expiry, audit trail, and linkage to [v1.0] Expand maintainer capacity and document project governance #252.
  • Publication, attestation, and GitHub Release creation all depend on the same provenance gate.
  • Release documentation matches enforcement.

Validation

Exercise annotated/main, lightweight, foreign-commit, rewritten-tag, dispatch, and rerun cases in tests or a safe rehearsal repository; read back branch and environment settings.

Non-Goals

Do not replace trusted publishing or existing artifact attestations.

Project Fields

  • Status: Backlog
  • Priority: P1
  • Area: Security
  • Initiative: v1.0 Readiness
  • Size: M

Ownership

Metadata

Metadata

Assignees

Labels

ciContinuous integration, tests, automation, or release workflowssecuritySecurity hardening or vulnerability work

Type

No type

Projects

Status
Backlog

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions