fix: declare one version, in one place per package, enforced by CI (closes #31) - #35
Merged
Merged
Conversation
Closes #31. /health reported 0.0.0 against a v0.1.1 tag. The endpoint was never the bug - it reads app.__version__ correctly. The bug was that eight sites each declared a version and nothing held them equal, so bumping was a manual, silent, partial operation. Every Python package declared it twice, in the pyproject and in a dunder, with no link between the two. Provx versions as one product. That is not a new position: both tags are whole-product milestones, CHANGELOG carries one SemVer stream, and REPOSITORY_STRATEGY names a per-package version matrix as the cost it rejected polyrepo to avoid. The one internal edge, backend -> provx-sdk, is deliberately unpinned and path-installed, so independent versions would have meant inventing a pin matrix to solve a problem the repo does not have. The source of truth is each package's own __init__.py, read by hatchling via dynamic = ["version"]. Not a root VERSION file and not git describe: each package must stay buildable from its own directory - the image copies packages/adapters alone and pipx takes packages/cli alone, and neither build context contains a .git. An air-gapped build has no git metadata either, and for this product that is a supported case. Equality across packages is therefore enforced by an assertion rather than by file layout. assert_version_consistency.py checks two things, the second being how the first would quietly come undone: that every site agrees, and that each pyproject still delegates rather than having regrown a static version. It is unconditional in ci-required, like license-check - a skew is a property of the tree, not of the paths a PR touched. 17-vector truth table, run by the gate before it asserts anything real; blocking vectors must emit ::error::, so a crash cannot pass as a refusal. release-check asserts a tag matches the code, in its own workflow because ci.yml's paths-filter has no base ref on a tag push. make version-set writes every site at once; its writer lives inside the assertion script, because a second copy of the list of version sites is the bug being fixed. provx --version answers from the package without contacting a server, retiring a __version__ that was exported and read by nobody. Signed-off-by: Solomon Nii Amu Darku <snad1@users.noreply.github.com>
8 tasks
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.
Note
This supersedes #33, which merged into the wrong base. #33 was stacked on
docs/user-quickstart; that branch merged tomainas #32 first, and because the branchstill existed afterwards GitHub never retargeted #33 — so it merged into an already-merged
feature branch and never reached
main. The commit here is a cherry-pick of that samecommit onto
main, and the resulting tree is byte-identical to the one CI passed on #33(
git diff origin/docs/user-quickstart HEADis empty). Re-verified on this base:make lintclean,
make testgreen at 516 backend + 11 frontend + all four self-test truth tables.What & why
Closes #31.
/healthreported0.0.0against av0.1.1tag. The endpoint was never the bug — it readsapp.__version__correctly. The bug was that eight sites each declared a version and nothingheld them equal, so bumping was a manual, silent, partial operation. Every Python package declared
it twice, in the pyproject and in a dunder, with no link between the two.
Provx versions as one product. Not a new position — the one already implied everywhere: both
tags are whole-product milestones,
CHANGELOG.mdcarries a single SemVer stream, ROADMAP §7 /CONTRIBUTING §1 / PROJECT_SETUP describe one release train, and
REPOSITORY_STRATEGY.mdnames aper-package "version matrix" as the cost it rejected polyrepo to avoid. The only internal
dependency edge (backend →
provx-sdk) is deliberately unpinned and path-installed, so independentversions would have meant inventing a pin matrix to solve a problem the repo does not have.
Source of truth is each package's own
__init__.py, read by hatchling viadynamic = ["version"]. Deliberately not a rootVERSIONfile and notgit describe: eachpackage must stay buildable from its own directory —
backend/Dockerfilecopiespackages/adaptersalone into the image,pipxtakespackages/clialone, and neither buildcontext contains a
.git. An air-gapped build has no git metadata either, and for this productthat is a supported case, not an edge one. So equality across packages is enforced by an
assertion rather than by file layout — the same shape as
assert_gate_results.sh,assert_workflow_gating.py, andcheck_licenses.py.What ships:
version-consistency, a new required gate. Checks two things, the second being how thefirst would quietly come undone: that every declaration site agrees, and that each pyproject
still delegates rather than having regrown a static
version =. Unconditional likelicense-check— a skew is a property of the tree, not of the paths a PR touched.vectors must also emit an
::error::line, so a crash cannot pass as a refusal — the fail-openshape
assert_workflow_gating.test.shlearned to catch, carried forward rather thanrediscovered.
assert_gate_results.test.shgrew to the 11-gate required set with both newdirections covered.
release-check— a tag push asserts the tag matches the code. Its own workflow, becauseci.ymlopens with a paths-filter job that has no base ref to diff on a tag push. Honest aboutits limits: a detector, not a gate, since a tag already pushed cannot be blocked.
make version-set VERSION=x.y.zwrites all five sites at once. Its writer lives inside theassertion script, because a second copy of the list of version sites is the bug being fixed.
provx --version, answering from the package without contacting a server — it worksprecisely when the server is unreachable, which is when an operator is asking. Retires a
__version__that was defined, exported in__all__, and read by nobody.Two things found along the way and not fixed here, each noted in STATUS with a land-with
condition: the CHANGELOG's pre-existing
[Unreleased]bullets were not re-filed into thebackfilled
[0.1.0]/[0.1.1]sections (deciding which shipped in which tag would bereconstruction, and a changelog that guesses is worse than one visibly incomplete — stated in the
file itself), and CONTRIBUTING §1/§3 promise
release-please-style version automation that wasnever built. This PR made the version correct and enforced; it did not make it automatic. Filed
separately.
Refs #29, #30.
Definition of Done
passive. No adapter, no egress, no target-facing code path touched. Nothinghere can send a request to anything.
truth table driving the real script over mutated copies of the real tree.
make testincluding the lab and CI self-tests ran green.docs/STATUS.mdgains a## Versioningsection recording thedecision, the two constraints that forced it, and its reversal path;
CHANGELOG.mdgains theentry plus the backfilled release sections.
network, no
git describe, same verdict on every machine including air-gapped(PX-DETERMINISM). No new dependency (PX-FREE) — hatchling was already all three build
backends, and the script is stdlib +
tomllib.Safety classification
passive(read-only, safe in test/production)How I tested
The decisive check is the image build, because it is exactly where
hatch-vcswould havefailed —
pip install --no-deps /opt/provx-sdkwith no.gitin the build context:That last line is the design proven at the packaging layer: the distribution built inside the image
carries the version read from the dunder.
Negative proof, not just positive — one site skewed by hand on the live tree:
make version-set VERSION=0.9.9then back to0.1.1round-trips to a clean tree.Gates:
make lintclean (ruff, ruff format, mypy --strict over 55 files, eslint, prettier, tsc).make testgreen — 516 backend tests (513 + 3 new), 11 frontend, and all four CI self-testtruth tables including the new 17-vector one.
actionlint 1.7.7(the versionci.ymlpins) cleanover all three workflows including the new
release-check.yml.