<?xml version="1.0" encoding="UTF-8"?>
<gsl-prompt id="20250808T184936+0000" type="chore">

<gsl-header>

# Implement Tag-Creation–Triggered Deployments for Python Package Releases
</gsl-header>

<gsl-block>

<gsl-purpose>
<gsl-label>

## 1. Purpose
</gsl-label>
<gsl-description>

Establish a robust, modern Python package deployment workflow where releases are triggered by Git tag creation (e.g., `v1.2.3`). Ensure that the version in `pyproject.toml` always matches the Git tag, and that deployments to PyPI only occur when this is true. Remove all legacy automation that relied on commit-message triggers or post-deploy version bumps.
</gsl-description>
</gsl-purpose>

<gsl-inputs>
<gsl-label>

## 2. Inputs
</gsl-label>
<gsl-description>

| Input | Notes |
| --- | --- |
| Minimalist `ci-cd.yml` | Skeleton workflow with test and build jobs only |
| `tag-triggered-deployments.md` | Implementation instructions and rationale |
| `pyproject.toml` | Source of truth for release version |
| GitHub Actions | For CI/CD automation |
| PyPI API token | For package deployment |
| [Build](https://pypa-build.readthedocs.io/) / [Twine](https://twine.readthedocs.io/) | Tools for packaging and uploading |

</gsl-description>
</gsl-inputs>

<gsl-outputs>
<gsl-label>

## 3. Outputs
</gsl-label>
<gsl-description>

- A CI/CD pipeline that only deploys on matching Git tag/version in `pyproject.toml`
- Automated version validation in the deploy job
- Legacy commit-message or post-tag deploy triggers removed
- Updated documentation reflecting the new workflow
</gsl-description>
</gsl-outputs>

<gsl-workflows>
<gsl-label>

## 4. Workflows
</gsl-label>
<gsl-description>

- Expand `ci-cd.yml` to trigger jobs on both `main` branch pushes and `v*.*.*` tags
- Add a deploy job triggered by tags, with dependency on the build job
- In deploy job:
  - Check out code
  - Set up Python environment and dependencies (`build`, `twine`, `toml`)
  - Validate that the version in `pyproject.toml` matches the Git tag
  - Build the package
  - Upload to PyPI if and only if validation passes
- Remove any jobs/scripts relying on commit-message triggers or post-tag version bumping
- Update all related documentation and onboarding material to reference the tag-triggered approach only
</gsl-description>
</gsl-workflows>

<gsl-acceptance-tests>
<gsl-label>

## 5. Acceptance Tests
</gsl-label>

<gsl-acceptance-test id="1">
<gsl-performed-action>

1. When a user bumps the version in `pyproject.toml`, merges it to `main`, and creates a tag in `v1.2.3` format that matches the new version.
</gsl-performed-action>
<gsl-expected-result>

    Then the deploy job runs, validates the version, and the package is uploaded to PyPI.
</gsl-expected-result>
</gsl-acceptance-test>

<gsl-acceptance-test id="2">
<gsl-performed-action>

2. When a user creates a release tag (e.g., `v1.2.3` format) that does not match the version in `pyproject.toml`.
</gsl-performed-action>
<gsl-expected-result>

    Then the deploy job fails with a clear error and nothing is uploaded to PyPI.
</gsl-expected-result>
</gsl-acceptance-test>

<gsl-acceptance-test id="3">
<gsl-performed-action>

3. When a user pushes to `main` without creating a tag.
</gsl-performed-action>
<gsl-expected-result>

    Then only test and build jobs run; no deploy to PyPI occurs.
</gsl-expected-result>
</gsl-acceptance-test>
</gsl-acceptance-tests>

</gsl-block>
</gsl-prompt>
