feat: expose the package version at runtime (VERSION + __version__) - #37
Merged
Conversation
Adds `VERSION` (TypeScript) and `__version__` (Python) so a consumer can read which version of this package produced a document instead of hand-copying a version string into its own constant. WHY A consumer that records extraction provenance needs the producing module's version. Until now the package offered no way to obtain it, so hand-copying was the only option available -- and a hand-copied version is a claim about the runtime rather than evidence of it. One downstream consumer did exactly that and its copy went stale, declaring 0.5.0 while 0.6.0 was current, with nothing able to detect the drift. Asking consumers to keep a copy in sync is not a fix; it is the same failure deferred. Making the version readable removes the need. The IL already has a producer-provenance field (`produced_by`). This gives consumers the value that belongs in it. WHY A LITERAL AND NOT A READ OF package.json The default TypeScript entry must stay importable in browser and WASM hosts; `scripts/check-ts-universal-entry.mjs` fails the build if it reaches a Node built-in. So `VERSION` is an embedded literal, the same trade the embedded prompt fragments already make -- and it carries the same obligation an embedded copy always carries: a test that fails when it drifts from its source. DRIFT COVERAGE The version is now written in four places (two manifests, two runtime constants). `packages/ts/tests/test_version.ts` and `tests/python/test_version.py` tie all four together, including across languages: the two surfaces ship as one product at one version, and nothing enforced that before -- they were two hand-edited numbers that happened to agree. Proven by mutation rather than asserted. Each of the four locations was mutated in turn and a named test went red; both suites return green on restore: mutate ts/package.json -> pytest RED, vitest RED (cross-language tie) mutate python pyproject -> pytest RED, vitest RED (cross-language tie) mutate ts src/version.ts -> vitest RED mutate python __init__.py -> pytest RED The Python suite also compares `importlib.metadata` against the source constant. That reads what pip actually installed, which is a genuinely different source than the literal, so it catches a stale editable install or a bump that was never reinstalled -- something the other assertions cannot see. It skips rather than fails when no distribution is installed, since importing from a source tree is legitimate. A note for whoever runs the mutation proof next: clear `__pycache__` between steps. Bytecode invalidation compares source mtime at one-second granularity plus size, and two same-length version edits inside the same second are indistinguishable to it -- a stale `.pyc` served a mutated value after the file had been restored. The masking is silent and it cuts both ways, so a mutation result gathered without clearing the cache proves nothing. scripts/bump-version.sh Four hand-edited numbers is the shape that drifts, and this commit takes it from two to four, so the tooling comes with it. The script updates all four, rejects anything that is not a bare semver triple (a range or a full specifier would each be a plausible paste and each would corrupt recorded provenance), and verifies afterward that the four agree rather than reporting success for having run. It deliberately does not commit, tag, or push: under the dev/main model a tag belongs to the release ceremony on main, not to whatever branch is checked out when someone bumps a number. Premium boundary: extract is OSS. A package's own version is public by definition; no premium, identity, or org semantics are involved.
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.
Adds
VERSION(TypeScript) and__version__(Python) so a consumer can read which version of this package produced a document, instead of hand-copying a version string into its own constant.Why
A consumer that records extraction provenance needs the producing module's version. Until now the package offered no way to obtain it, so hand-copying was the only option available — and a hand-copied version is a claim about the runtime rather than evidence of it. One downstream consumer did exactly that and its copy went stale, declaring
0.5.0while0.6.0was current, with nothing able to detect the drift.Asking consumers to keep a copy in sync is not a fix; it is the same failure deferred. Making the version readable removes the need for the copy.
The IL already has a producer-provenance field (
produced_by). This gives consumers the value that belongs in it.Why a literal, not a read of
package.jsonThe default TypeScript entry has to stay importable in browser and WASM hosts, and
scripts/check-ts-universal-entry.mjsfails the build if it reaches a Node built-in. SoVERSIONis an embedded literal — the same trade the embedded prompt fragments already make, and it carries the same obligation: an embedded copy needs a test that fails when it drifts from its source.check:universalpasses (10 modules scanned), so the constant did not compromise browser/WASM import.Drift coverage, proven by mutation
The version is now written in four places (two manifests, two runtime constants).
packages/ts/tests/test_version.tsandtests/python/test_version.pytie all four together, including across languages — the two surfaces ship as one product at one version, and nothing enforced that before. They were two hand-edited numbers that happened to agree.Each location was mutated in turn and a named test went red; both suites green on restore:
packages/ts/package.jsonpackages/python/pyproject.tomlpackages/ts/src/version.tspackages/python/.../__init__.pyCoverage is asymmetric and worth stating rather than implying symmetry: a drifted TS constant is caught only by vitest, a drifted Python constant only by pytest. Complete across the two suites together (which is what CI runs), but running one suite alone leaves one location unguarded.
The Python suite also compares
importlib.metadataagainst the source constant. That reads what pip actually installed — a genuinely different source than the literal — so it catches a stale editable install or a bump that was never reinstalled, which none of the other assertions can see. It skips rather than fails when no distribution is installed, since importing from a source tree is legitimate.Note for whoever runs the mutation proof next
Clear
__pycache__between steps. Bytecode invalidation compares source mtime at one-second granularity plus size, and two same-length version edits inside the same second are indistinguishable to it — a stale.pycserved a mutated value after the file had been restored. The masking is silent and it cuts both ways, so a mutation result gathered without clearing the cache proves nothing. My first matrix was invalid for exactly this reason and was re-run clean.scripts/bump-version.shFour hand-edited numbers is the shape that drifts, and this PR takes it from two to four, so the tooling ships with it rather than after it.
The script updates all four, rejects anything that is not a bare semver triple (a range like
^0.7.0or a full specifier are each a plausible paste, and each would corrupt recorded provenance), and verifies afterward that the four agree rather than reporting success for having run. Tested end to end:0.6.0 → 0.7.0, all four moved, reverted cleanly.It deliberately does not commit, tag, or push — under the dev/main model a tag belongs to the release ceremony on
main, not to whatever branch happens to be checked out when someone bumps a number.One honest note on the flow: after a bump,
test_installed_distribution_agrees_...goes red until youpip install -e packages/python, because it comparesimportlib.metadataagainst the source constant. That is the check working — it is the only assertion that can see a stale install — so the script prints the reinstall step first, ahead of running the suites.Verification
tsc --noEmitcheck:universalcheck-no-network(ts, py)Issue tracking
The originating issue lives in a private repository, so it is deliberately not linked here — a reference to it would name a private repo in a permanently public PR body, which is the same disclosure this project's pre-push leak scan blocks in source. It is tracked internally and will be closed manually when this merges.
Premium boundary
extractis OSS. A package's own version is public by definition; no premium, identity, or org semantics are involved.