Skip to content

t71: gate publishing on the complete quality suite#3

Merged
OriNachum merged 1 commit into
mainfrom
fix/t71-release-hardening
Jul 19, 2026
Merged

t71: gate publishing on the complete quality suite#3
OriNachum merged 1 commit into
mainfrom
fix/t71-release-hardening

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

What

publish gated on a single test job running pytest. Every other gate that
defines whether shell-cli is correct — black, isort, flake8, bandit,
markdownlint-cli2, and the teken cli doctor --strict rubric gate — lived
exclusively in tests.yml. Any of them could be red while a release went out.

That matters more than usual here because PyPI is append-only: a bad version
can never be replaced, only yanked, and a yanked version stays installable by
exact pin. colleague is about to depend on these releases.

Changes

Two new jobs in publish.yml, with both publish paths (publish and
test-publish) now depending on [test, gates, smoke]:

  • gates mirrors the full lint job from tests.yml. The duplication is
    deliberate and commented as such: a job defined in another workflow file
    cannot appear in needs:, and letting publish trust that a separate
    workflow happened to pass is precisely the hole being closed.
  • smoke builds the wheel, installs it into a clean venv, asserts the
    artifact declares zero runtime dependencies, and runs import shell plus
    the shell console script. uv sync installs from the source tree, so a
    green test suite proves nothing about the built artifact — its importability,
    its entry-point wiring, or its declared metadata.

The zero-dependency assertion reads installed wheel metadata, not the
checkout, so a dependency added to the built package cannot slip past a
source-level import guard.

Verification

The smoke job was live-tested locally against a real built wheel before being
committed, not just written:

Successfully built dist/shell_cli-0.8.0-py3-none-any.whl
OK - the built wheel declares no runtime dependencies.
import shell OK
{"nick": "shell-cli", "version": "0.8.0", "backend": "unknown", "model": "unknown"}

Full local gate suite green: 49 tests, black, isort, flake8, bandit (0 medium /
0 high), markdownlint, teken cli doctor --strict.

Also

docs/release-runbook.md — the gate chain, the yank-and-fix-forward procedure
(including the honest limits of yanking), colleague's >=0.8,<0.9 pin policy,
the requirement that colleague's dependency PR update
test_base_dependency_is_exactly_agentfront and its docstring in one commit,
and the security fix's exemption from the migration-proposal gate.

Context

First task PR from the converged guarded-local-operations-plane plan
(t71). It merges first by design — every subsequent shell-cli merge publishes,
so the hardening has to precede them. The plan and spec artifacts themselves
land in the next PR (t72).

  • shell-cli (Claude)

`publish` needed only a `test` job running pytest. black, isort, flake8,
bandit, markdownlint and the teken rubric gate all lived exclusively in
tests.yml, so any of them could be red while a release went out to PyPI —
which is append-only, so a bad version can only be yanked, never replaced.

Add two jobs and make both publish paths depend on them:

- `gates` mirrors the full lint suite from tests.yml. Duplicated on
  purpose: a job in another workflow file cannot appear in `needs:`, and
  letting `publish` trust that a separate workflow happened to pass is the
  hole being closed.
- `smoke` builds the wheel, installs it into a clean venv, asserts the
  artifact declares zero runtime dependencies, and runs `import shell`
  plus the `shell` console script. `uv sync` installs from the source
  tree, so a green suite proves nothing about the built artifact.

The zero-dependency assertion runs against installed wheel metadata rather
than the checkout, so a dependency added to the built package cannot slip
past the source-level import guard.

Also add docs/release-runbook.md: the gate chain, the yank-and-fix-forward
procedure (including that a yanked version stays installable by exact pin,
so yanking bounds future installs and recalls nothing), colleague's
>=0.8,<0.9 pin policy, the requirement that colleague's dependency PR
update test_base_dependency_is_exactly_agentfront and its docstring in one
commit, and the security fix's exemption from the migration-proposal gate.

Covers t71 (plan: guarded-local-operations-plane). Merges first — every
subsequent shell-cli merge publishes.
@OriNachum

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jul 19, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@sonarqubecloud

Copy link
Copy Markdown

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Gate PyPI publishing on full quality suite + built-wheel smoke test

✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Gate publish/test-publish on pytest + full lint/rubric suite to prevent bad PyPI releases.
• Add wheel smoke test that installs the built artifact and validates metadata/entrypoints.
• Document release/yank procedures and bump version/changelog for the hardening release.
Diagram

graph TD
  A(["Trigger: push/PR"]) --> B["publish.yml workflow"] --> C["test job (pytest)"]
  B --> D["gates job (lint+rubs)"]
  B --> E["smoke job (wheel verify)"]
  C --> F(("All required pass")) --> G["publish/test-publish"] --> H[("PyPI/TestPyPI")]
  D --> F
  E --> F
  subgraph Legend
    direction LR
    _evt(["Event"]) ~~~ _job["Job"] ~~~ _join(("Join")) ~~~ _idx[("Package index")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Consolidate into a single workflow (tests + publish in one file)
  • ➕ Eliminates duplication/drift between tests.yml lint job and publish.yml gates job
  • ➕ Allows a single DAG with explicit needs for all quality checks
  • ➖ Bigger workflow file; mixes PR/test concerns with publish concerns
  • ➖ May require careful trigger/condition refactoring to keep PR behavior identical
2. Factor shared steps into a composite action (e.g., .github/actions/quality-gates)
  • ➕ Keeps explicit needs in publish.yml while reducing step duplication across workflows
  • ➕ Single source of truth for tool versions/commands
  • ➖ Adds a maintained internal action surface
  • ➖ Still duplicates job wiring (but not the step contents)
3. Rely on branch protection “required status checks” instead of duplicating gates
  • ➕ No duplication in workflow code
  • ➕ Central policy enforcement for merges
  • ➖ Does not guarantee publish.yml won’t run if configuration changes or on non-protected refs
  • ➖ Harder to reason about from the workflow alone; can be bypassed if policies drift

Recommendation: Current approach is sound given GitHub Actions’ inability to needs: a job from another workflow; duplicating the gate job closes the exact hole described. To reduce long-term drift risk, consider extracting the shared lint/rubric steps into a composite action while keeping publish.yml’s explicit needs chain.

Files changed (4) +266 / -3

Documentation (2) +157 / -0
CHANGELOG.mdAdd 0.8.0 release notes for publish hardening and runbook +11/-0

Add 0.8.0 release notes for publish hardening and runbook

• Introduces a 0.8.0 entry describing the new publish gating, smoke test coverage, and the new release runbook. Captures the behavioral change that releases cannot proceed with any quality gate failing.

CHANGELOG.md

release-runbook.mdDocument release gates, yanking limits, and downstream pin policy +146/-0

Document release gates, yanking limits, and downstream pin policy

• Adds a runbook explaining the automatic release path, the three-job gate chain (test/gates/smoke), and why smoke testing the built artifact is required. Documents a yank-and-fix-forward procedure and clarifies the downstream (colleague) pin/upgrade boundary and related guard-test update requirement.

docs/release-runbook.md

Other (2) +109 / -3
publish.ymlGate publish on full quality suite and add built-wheel smoke test +108/-2

Gate publish on full quality suite and add built-wheel smoke test

• Adds a dedicated 'gates' job that mirrors the full lint/rubric suite from tests.yml, explicitly documenting the intentional duplication. Introduces a 'smoke' job that builds the wheel, installs it into a clean venv, asserts the installed wheel declares zero runtime dependencies, and exercises the 'shell' entry point. Updates both 'publish' and 'test-publish' to require 'needs: [test, gates, smoke]'.

.github/workflows/publish.yml

pyproject.tomlBump project version to 0.8.0 +1/-1

Bump project version to 0.8.0

• Updates the project version from 0.7.2 to 0.8.0 to ship the release-hardening changes.

pyproject.toml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 10 rules
✅ Skills: version-bump

Grey Divider


Remediation recommended

1. Smoke uses system Python 🐞 Bug ☼ Reliability
Description
In smoke, the clean venv is created with python3 -m venv, which is not guaranteed to be the same
interpreter as uv python install 3.12, so the smoke test may run under an unintended Python
version as runner images evolve. This undermines the workflow’s explicit Python version control and
can cause inconsistent smoke-test behavior across environments.
Code

.github/workflows/publish.yml[R99-108]

+      - run: uv python install 3.12
+
+      - name: Build the wheel
+        run: uv build
+
+      - name: Install the built wheel into a clean environment
+        run: |
+          python3 -m venv /tmp/smoke
+          /tmp/smoke/bin/python -m pip install --upgrade pip
+          /tmp/smoke/bin/python -m pip install dist/*.whl
Evidence
publish.yml installs Python 3.12 using uv, but the smoke venv is created with python3, which
does not necessarily point to the uv-managed interpreter. Since the project declares
requires-python = ">=3.12", the workflow should consistently run smoke under an explicitly
selected 3.12 interpreter for determinism.

.github/workflows/publish.yml[97-109]
pyproject.toml[1-8]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The `smoke` job installs Python 3.12 via `uv python install 3.12`, but then creates `/tmp/smoke` with the runner’s `python3`, which is not necessarily the uv-managed 3.12 interpreter. This makes the smoke test less deterministic.

### Issue Context
- Project requires Python >=3.12.
- Workflow intends to standardize on Python 3.12.

### Fix
Create the venv using the uv-managed 3.12 interpreter (or `uv venv`) so the venv is guaranteed to use 3.12.

Example fix (one option):
```yaml
- name: Install the built wheel into a clean environment
 run: |
   UV_PY=$(uv python find 3.12)
   "$UV_PY" -m venv /tmp/smoke
   /tmp/smoke/bin/python -m pip install --upgrade pip
   /tmp/smoke/bin/python -m pip install dist/*.whl
```
Or, if preferred:
```yaml
   uv venv /tmp/smoke --python 3.12
```

### Fix Focus Areas
- .github/workflows/publish.yml[99-109]
- pyproject.toml[1-8]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +99 to +108
- run: uv python install 3.12

- name: Build the wheel
run: uv build

- name: Install the built wheel into a clean environment
run: |
python3 -m venv /tmp/smoke
/tmp/smoke/bin/python -m pip install --upgrade pip
/tmp/smoke/bin/python -m pip install dist/*.whl

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Smoke uses system python 🐞 Bug ☼ Reliability

In smoke, the clean venv is created with python3 -m venv, which is not guaranteed to be the same
interpreter as uv python install 3.12, so the smoke test may run under an unintended Python
version as runner images evolve. This undermines the workflow’s explicit Python version control and
can cause inconsistent smoke-test behavior across environments.
Agent Prompt
### Issue description
The `smoke` job installs Python 3.12 via `uv python install 3.12`, but then creates `/tmp/smoke` with the runner’s `python3`, which is not necessarily the uv-managed 3.12 interpreter. This makes the smoke test less deterministic.

### Issue Context
- Project requires Python >=3.12.
- Workflow intends to standardize on Python 3.12.

### Fix
Create the venv using the uv-managed 3.12 interpreter (or `uv venv`) so the venv is guaranteed to use 3.12.

Example fix (one option):
```yaml
- name: Install the built wheel into a clean environment
  run: |
    UV_PY=$(uv python find 3.12)
    "$UV_PY" -m venv /tmp/smoke
    /tmp/smoke/bin/python -m pip install --upgrade pip
    /tmp/smoke/bin/python -m pip install dist/*.whl
```
Or, if preferred:
```yaml
    uv venv /tmp/smoke --python 3.12
```

### Fix Focus Areas
- .github/workflows/publish.yml[99-109]
- pyproject.toml[1-8]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant