Add a packaging gate and tag-triggered release validation - #243
Merged
Conversation
Every CI job installs the CLI editable from a full checkout, so nothing validated the artifact a user pip-installs. The first run of these checks against a built wheel found five defect classes sitting behind twelve green checks: - the lager-mcp console script targets lager.mcp.server, which lives under box/ and has never shipped in the wheel: ModuleNotFoundError on every pip install (issue #242) - the vendored elftools tree is missing construct/lib IN THE REPO, so 30 cli.elftools modules and the lager debug gdb ELF path cannot import anywhere, dev checkouts included (issue #240) - cli.impl.box_config and cli.impl.power.enable_disable import lager.* from the box tree the wheel does not ship (issue #241) - prompt_toolkit was undeclared: cli/terminal imports it unconditionally, install_requires never mentioned it. Fixed here -- the import walk proves cli.terminal now imports from a clean install. - setup.py ships inside the wheel as cli.setup (structural, harmless, documented in the baseline as a note; whether it IMPORTS depends on the venv's python -- 3.11 seeds setuptools, 3.12+ does not, which the gate's own first run demonstrated) PR Gate: Packaging (packaging.yml): build sdist+wheel, twine check, install the wheel AND the sdist each into a fresh venv, smoke the lager entry point, assert the reported version matches the source, and import-walk the installed package (tools/check_imports.py) against tools/packaging_import_baseline.txt. The baseline is two-sided: a new failure fails, and an entry that starts importing cleanly also fails, so the file only shrinks honestly. The walker refuses to run outside a venv and asserts the package resolved from inside it -- on this machine that guard immediately caught a shadowing global install. Release: Validate Tag (release-validation.yml): the same proof on every v* tag, plus lager --version must equal the tag, plus dist/ uploaded as a 90-day artifact. RELEASE_PROCESS.md steps 7-8 now download and twine-upload THAT artifact instead of rebuilding locally, so what ships is the bytes the validation proved. The lager-mcp entry point is deliberately not smoked until its issue is resolved; the workflow header says so.
danielrmerskine
force-pushed
the
de/packaging-gate
branch
from
August 13, 2026 00:41
9a035bf to
62072c3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nothing in CI validated the artifact a user pip-installs -- every job installs the CLI
editable from a full checkout. Running these checks against a built wheel for the first
time found five defect classes behind twelve green checks:
lager-mcpconsole script targetslager.mcp.server(box tree) --ModuleNotFoundErroron every pip installconstruct/libin the repo -- 30 modules + thelager debug gdbELF path cannot import anywherecli.impl.box_config/cli.impl.power.enable_disableimportlager.*from the box treeprompt_toolkitundeclared --cli/terminalimports it unconditionallysetup.pyships in the wheel ascli.setupPR Gate: Packaging(packaging.yml): build sdist+wheel,twine check, install thewheel and the sdist each into a fresh venv, smoke
lager --version/--help, assert thereported version equals the source's
__version__, and import-walk the installed package(
tools/check_imports.py) againsttools/packaging_import_baseline.txt.The baseline is TWO-SIDED, same pattern as the shellcheck exclusions: a failure not listed
fails the gate, and a listed module that starts importing cleanly also fails with "remove
it" -- the file can only shrink honestly. Every functional entry names its owning issue.
The walker refuses to run outside a venv and asserts the package resolved from inside it
(that guard caught a shadowing global install on its very first local run).
Release: Validate Tag(release-validation.yml): the same proof on everyv*tag,plus an installed-version==tag assert (the classic bumped-changelog-forgot-
__init__.pyrelease), plus
dist/uploaded as a 90-day artifact.RELEASE_PROCESS.mdsteps 7-8 nowsay: wait for green, download the artifact,
twine uploadthose exact bytes -- no localrebuild.
lager-mcpis deliberately not smoked until its issue is resolved (workflow headercomment).
Verified locally: full pipeline (build, twine check, wheel venv, sdist venv, import walk)
green end to end on this branch; the walk reconciles exactly 34 known failures against the
baseline in both venvs; all three checker failure arms fault-injected (new failure, stale
entry, package resolving from outside the venv); both workflows YAML-parse and every
embedded script passes
bash -n.