papeete-version
Computes one actor's version — a semver core from its own git tag, a ciType-driven label, and a short SHA — for the Papeete ecosystem.
papeete-version compute FOLDER... --name NAME --label CITYPE [--feature-name F]
papeete-version match-version FOLDER... --name NAME --label CITYPE --version Q
[--feature-name F] [--current-version V]
pip install papeete-version
What it computes
{semver}-{label}-{shortSha} # alpha, beta, feature
{semver} # prod — GA is semver-only, no suffix
| Part | Comes from |
|---|---|
semver |
the X.Y.Z core of the actor's own nearest <name>/vX.Y.Z git tag — namespaced per actor, since one repo can hold several |
label |
--label, a ciType: alpha, beta, prod, or feature. alpha/beta print themselves as the label; feature prints --feature-name instead (a named label per feature branch); prod IS GA and drops the label and shortSha entirely |
shortSha |
the most recent commit that touched the folder (omitted for prod) |
git tag archivist/v0.1.0 # once, before the first computation
papeete-version compute path/to/archivist --name archivist --label alpha
# 0.1.0-alpha-a1b2c3d
papeete-version compute path/to/archivist --name archivist --label feature --feature-name my-branch
# 0.1.0-my-branch-a1b2c3d
papeete-version compute path/to/archivist --name archivist --label prod
# 0.1.0
No Docker, no manifest file read, no network — compute is a pure git computation over the
folder you point it at. An actor with no matching tag yet, or no commit history at all, gets a
clear, fatal error rather than a fabricated placeholder — see
ADR-PV-0001 for why, and for
this package's own extraction story.
match-version — folding a query against the live state
match-version is not a registry lookup — there's still nowhere this package persists a computed
version (see the open question below). Instead it's a pure fold: it recomputes the actor's live
version from git exactly like compute does, then resolves --version against it:
latest— always the live version;--current-versionis irrelevant.- a short SHA — the live version, only if its own short SHA matches exactly.
- an npm-style range (
^1.2.3,~1.2,1.x,1.2.3,>=1.0.0, ...) — the live version if its semver core satisfies the range; otherwise--current-version, carried forward unchanged, if that satisfies it instead. Neither satisfying is a hard failure, same as everywhere else in this package.
papeete-version match-version path/to/archivist --name archivist --label alpha \
--version "^1.0.0" --current-version "1.5.0-alpha-abc0000"
# 1.5.0-alpha-abc0000 (live git state doesn't satisfy ^1.0.0, current-version does)
--current-version is the caller's own state, passed back in each call — there is still no
persisted history of computed versions inside this package itself. Enumerating every version
ever computed for an actor, or resolving one back to what it came from, remains the open question
below.
Retrieving a version from a registry — not built yet
match-version folds a query against live git state and a caller-supplied prior result; it does
not read from anywhere that stores every version ever computed. Whether such a store should exist
at all, and if so what it looks like — enumerate every tagged version for an actor, resolve an
existing version string back to what it came from, or something else — is still real, wanted, and
deliberately undecided rather than bolted on here as an afterthought — see the open question in
ADR-PV-0001.
Rules — a precise spec for programmatic/LLM callers
The sections above are the narrative explanation; this section is the exact, unambiguous contract. Where the two ever disagree, this section is normative — file an issue, it means the prose above went stale.
Commands and arguments
| Command | Argument | Required | Values |
|---|---|---|---|
compute, match-version |
FOLDER... (positional, 1+) |
yes | path(s) to a git working tree; one line of output per folder, in order |
compute, match-version |
--name |
yes | any string; normalized via normalize_name() (lowercased, spaces → -) before being used to build the tag-match pattern <normalized-name>/v* |
compute, match-version |
--label |
yes | exactly one of alpha, beta, prod, feature — any other value is a CLI-level error (argparse rejects it before any git or logic runs) |
compute, match-version |
--feature-name |
conditionally | required if and only if --label feature; ignored (may be omitted) for every other --label value |
match-version |
--version |
yes | one of: the literal string latest; a hex string of 7–40 characters ([0-9a-f]{7,40}), treated as a short SHA; or an npm-style range (grammar below) |
match-version |
--current-version |
no | a previously computed version string (e.g. 1.5.0-alpha-abc0000) or a bare X.Y.Z; default: none |
compute algorithm
- If
--labelis not one ofalpha/beta/prod/feature→ error (ValueError, caught by the CLI as exit code 2, message on stderr prefixedFAIL). - If
--label featureand--feature-nameis empty/absent → error, same as above. semver = X.Y.Zfrom the nearest git tag reachable fromHEADmatching<normalize_name(name)>/v*, itsX.Y.Zsuffix taken verbatim. No matching tag, or a tag whose suffix isn't plain\d+\.\d+\.\d+→ error.- If
--label prod→ printsemveralone. Stop. (This is the GA case: no label, no shortSha, ever.) - Otherwise,
label_out = feature_name if label == "feature" else label. shortSha= the abbreviated hash (git log -1 --format=%h -- .) of the most recent commit touchingFOLDER(not the whole repo'sHEAD). No commit history for the folder → error.- Print
{semver}-{label_out}-{shortSha}.
match-version algorithm
Recomputes live = compute(...) (steps 1–7 above, non-fatal errors here still propagate as
normal compute errors), then:
if version == "latest":
return live
if version matches [0-9a-f]{7,40}:
return live if live's own shortSha == version or live's shortSha startswith(version)
else ERROR
# otherwise `version` is an npm-style range
if npm_range.satisfies(semver_core(live), version):
return live
elif current_version is not None and npm_range.satisfies(semver_core(current_version), version):
return current_version
else:
ERROR
semver_core(v) = the X.Y.Z before the first - in a version string (or the whole string if
there is no -). There is no other fallback: a version this algorithm can't stand behind (via
live or current_version) is never fabricated, averaged, or guessed.
npm-range grammar accepted by --version (and nowhere else)
| Form | Example | Meaning |
|---|---|---|
| exact | 1.2.3 |
== 1.2.3 |
| X-range, major only | 1, 1.x, 1.X, 1.* |
>=1.0.0 <2.0.0 |
| X-range, major.minor | 1.2, 1.2.x |
>=1.2.0 <1.3.0 |
| any | *, x, X |
always true |
| tilde | ~1.2.3 |
>=1.2.3 <1.3.0 |
| tilde, partial | ~1.2, ~1 |
>=1.2.0 <1.3.0 / >=1.0.0 <2.0.0 |
| caret | ^1.2.3 |
>=1.2.3 <2.0.0 |
caret, 0.x major |
^0.2.3 |
>=0.2.3 <0.3.0 |
caret, 0.0.x |
^0.0.3 |
>=0.0.3 <0.0.4 |
| comparator | >=1.2.3, <=, >, <, =1.2.3 |
as written, against one X.Y.Z |
Not accepted, will raise an error: hyphen ranges (1.2.3 - 2.3.4), || unions, multiple
space-separated comparators combined. See src/papeete_version/npm_range.py if one of these
becomes a real need — it isn't one yet, so it isn't implemented.
Errors and exit codes
| Failure | Mechanism | Exit code | Where the message goes |
|---|---|---|---|
--label not in alpha/beta/prod/feature |
argparse choices rejection |
2 | stderr, argparse's own invalid choice format |
Any other failure (feature with no --feature-name, no matching tag, no commit history, shortSha/range mismatch) |
Python ValueError caught in cli.main() |
2 | stderr, format FAIL {message} |
| Success | — | 0 | stdout, one computed/matched version string per folder, one per line |
Invariants an LLM can rely on
- Output is always either a bare
X.Y.Z(only for--label prod) or exactlyX.Y.Z-<label>-<shortSha>— never any other shape, never partially filled in. computeandmatch-versionare pure functions of git state (+ the caller-supplied--current-versionformatch-version) — no network calls, no files written, no state persisted between invocations by this package itself.- Nothing here reads a declared
version:field from any manifest — the semver core always comes from a git tag, never from a file.
Where this came from
Ported from papeete-actor's build.py
(ADR-PA-0022, ADR-PA-0023), which still carries its own copy of this logic for now —
papeete-actor is not yet wired to depend on this package. That cutover is a deliberate, later
decision, not part of standing this repo up.
Versioning
The tool's own version (this package, on PyPI) and the version strings it computes for other
actors are unrelated numbers. papeete-version --version prints the former.
Releasing
Tag-triggered, via PyPI Trusted Publishing (OIDC). No API token is stored anywhere — GitHub mints a short-lived OIDC token per run and PyPI trades it for an upload token. There is nothing to rotate and nothing to leak.
git tag v0.1.0 && git push origin v0.1.0 # .github/workflows/release.yml does the rest
One-time setup — reused from papeete-actor's recipe
1. A pending publisher on PyPI — not yet registered. The project doesn't exist on PyPI yet, so it's registered from the publisher side rather than by a first manual upload. At https://pypi.org/manage/account/publishing/, as a GitHub pending publisher:
| Field | Value |
|---|---|
| PyPI Project Name | papeete-version |
| Owner | papeete-hub |
| Repository name | papeete-version |
| Workflow name | release.yml |
| Environment name | pypi |
All five must match exactly — PyPI checks the OIDC claims against them and rejects the upload
otherwise. release.yml already declares permissions: id-token: write and
environment: pypi, which is what makes those claims present. This step needs a human with a PyPI
account and can't be done from the repo itself.
2. The pypi GitHub environment. No secrets in it — it exists so the OIDC claim carries an
environment name for PyPI to match. Protection rules are not set and are worth considering,
because a release is irreversible: PyPI never allows re-uploading a version, even after a delete.
Required reviewers, and restricting deployments to tags matching v*, are the two that earn their
keep.
A private repo is fine. Trusted Publishing authenticates the workflow, not the source, so nothing here needs to be public for the package to be.
After the first successful release PyPI converts the pending publisher into a normal one automatically; there is no second setup step.
Nothing has been published yet. papeete-version is unclaimed on PyPI and the release lane has
never run.
What a release asserts
The workflow builds, installs the wheel into a clean venv, and computes a version for a throwaway tagged git repo before publishing — so a build whose entry point is broken fails the release instead of shipping something that can't actually compute anything.
Licence
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file papeete_version-0.1.0.tar.gz.
File metadata
- Download URL: papeete_version-0.1.0.tar.gz
- Upload date:
- Size: 18.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
470c7f922b904660c9559fadafe91a4f10997939889b64d88515e1864a9cc9b7
|
|
| MD5 |
dbaa023e418efa70e599bc4f8c9150de
|
|
| BLAKE2b-256 |
cfbda73afc3ff5d6a2094613c9cbd1e9de0f579946b55e94b09d4dd5721a9c5f
|
File details
Details for the file papeete_version-0.1.0-py3-none-any.whl.
File metadata
- Download URL: papeete_version-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b9166416f48b1f4e2f2d760c365a6ff755e6ea00a1213e31a96fc2615af1ebc
|
|
| MD5 |
268d989f0adf6cfa5ab31f217bc75267
|
|
| BLAKE2b-256 |
50c8c3322cc1fae2f66db4b604358886dd6b7f82486d364863dbf861c837d0bd
|