Skip to content

fix(release): bump to 0.4.0, and make the tag and the version agree - #93

Merged
dmarx merged 1 commit into
mainfrom
fix/version-matches-the-tag
Aug 16, 2026
Merged

fix(release): bump to 0.4.0, and make the tag and the version agree#93
dmarx merged 1 commit into
mainfrom
fix/version-matches-the-tag

Conversation

@dmarx

@dmarx dmarx commented Aug 16, 2026

Copy link
Copy Markdown
Owner

0.4.0 is not on PyPI — the publish failed. pip install luria==0.4.0 reports available versions 0.1.0, 0.1.1, 0.1.2, 0.2.0, 0.3.0.

What happened

The 0.4.0 tag points at f61282e (the #92 merge), whose pyproject.toml still says version = "0.3.0". So python -m build produced luria-0.3.0-py3-none-any.whl, and PyPI rejected it:

400 File already exists ('luria-0.3.0-py3-none-any.whl', with blake2_256 hash 'ec35b7…')

after the GitHub release was already published. Both existing gates passed on the way through: twine check dist/* validates metadata, not identity, and the cold-install smoke test exercises luria init/index/new/lint on a wheel that installs perfectly well — it's simply the wrong version.

Why it's a DP-5 instance

The version lives in two places — pyproject.toml and the git tag — with nothing making them agree. That is the hand-maintained projection DP-5 says will drift, and it drifted on the first release after the pattern was set. Nicely, it's the same principle #710 downstream was just annotated with.

Rung 2 is what this PR does — guard the property rather than trust the copy:

- name: The built version is the released version
  if: github.event_name == 'release'
  env:
    TAG: ${{ github.event.release.tag_name }}
  run: |
    built=$(ls dist/*.whl | head -1 | sed -E 's/.*luria-([^-]+)-py3.*/\1/')
    if [ "$built" != "${TAG#v}" ]; then … exit 1; fi

It runs in build, so it fails before the publish job and its environment gate are reached.

Rung 1 would be better — derive the version from the tag with hatch-vcs, so there is only one source. Deliberately not bundled here: the publish checkout is actions/checkout@v4 at the default fetch-depth: 1 with no tags fetched, so switching without also setting fetch-depth: 0 would resolve to a 0.1.dev… version and fail the release a second time. Worth its own PR.

Verified

Fired the guard against all three cases before trusting it (DP-6):

dist tag result
luria-0.3.0-…whl 0.4.0 exit 1 — the real failure
luria-0.4.0-…whl 0.4.0 pass
luria-0.4.0-…whl v0.4.0 pass

421 tests pass; luria lint clean.

To actually ship 0.4.0

The 0.4.0 tag points at a commit without the bump, so re-running the failed workflow rebuilds the same 0.3.0 wheel. After merging, either delete the 0.4.0 tag and release and re-cut them at the new main (safe — nothing was ever published under it), or cut 0.4.1 if you'd rather never move a tag. Your call; I haven't touched the tag.


Generated by Claude Code

The 0.4.0 release failed to publish. `pyproject.toml` still said 0.3.0, so
`python -m build` produced a 0.3.0 wheel and PyPI rejected it as a duplicate —
after the GitHub release was already published. `twine check` and the cold
install smoke test both passed, because neither validates identity.

The version lived in two places with nothing making them agree, which is the
hand-maintained projection DP-5 says will drift. It drifted on the first
release after the convention was set.

Bumps the version, and adds a build-job assertion that the built wheel matches
the release tag (a leading `v` is tolerated) before the publish job runs. That
is DP-5's rung 2 — guard the property. Rung 1 is deriving the version from the
tag via hatch-vcs, which also needs `fetch-depth: 0` on the publish checkout;
left as a follow-up rather than bundled into a release unblock.

Guard verified against all three cases: tag 0.4.0 with a 0.3.0 wheel exits 1,
matching versions pass, and a `v`-prefixed tag passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AWc5urqmvaJUhcvy1VWLM
@dmarx
dmarx merged commit 0a80de9 into main Aug 16, 2026
6 checks passed
dmarx pushed a commit that referenced this pull request Aug 16, 2026
#93 named the ladder and shipped rung 2: assert the built version equals the
release tag. It deferred rung 1 — deriving the version — because switching to
hatch-vcs without also fetching tags would have failed the release a second
time by resolving to a `0.1.dev` fallback.

This is rung 1, with the tag fetch that makes it safe.

`pyproject.toml` declares `dynamic = ["version"]` with hatch-vcs reading
`git describe`, so there is one source for the version rather than two that
can disagree. The publish checkout takes `fetch-depth: 0`. No VCS version
file is generated: nothing in the package reads its own version, and writing
one out would reintroduce the hand-maintained copy this removes.

#93's assertion stays. Deriving prevents the drift; the guard is what makes
a recurrence loud rather than a PyPI rejection reached after the environment
gate. The two are complementary, and DP-5's own ladder puts derive above
guard rather than instead of it.

A downstream data point for why the quiet failure matters: an adopting
project ran `pip install luria` (0.3.0) while its CI pinned
`dmarx/luria/actions/generate@main`. Local lint and CI lint were executing
different code with nothing reporting it, because `pip install --upgrade
luria` is a no-op while the release is missing.

Verified: a clean checkout on the 0.4.0 tag builds
`luria-0.4.0-py3-none-any.whl` — no `.devN`, no `+g` local segment, which
matters because PyPI rejects local segments outright. A working-tree build
produces `0.4.1.dev3+g0a80de972`, correctly unuploadable. 425 tests pass and
`pip install -e .` still works.

The ADR keeps its temporary code, as #88 did — `luria concretize` assigns
the real number where merges serialize.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpK3k2WhH9Pnc3i6TjLMVv
dmarx pushed a commit that referenced this pull request Aug 16, 2026
release tag. It deferred rung 1 — deriving the version — because switching to
hatch-vcs without also fetching tags would have failed the release a second
time by resolving to a `0.1.dev` fallback.

This is rung 1, with the tag fetch that makes it safe.

`pyproject.toml` declares `dynamic = ["version"]` with hatch-vcs reading
`git describe`, so there is one source for the version rather than two that
can disagree. The publish checkout takes `fetch-depth: 0`. No VCS version
file is generated: nothing in the package reads its own version, and writing
one out would reintroduce the hand-maintained copy this removes.

a recurrence loud rather than a PyPI rejection reached after the environment
gate. The two are complementary, and DP-5's own ladder puts derive above
guard rather than instead of it.

A downstream data point for why the quiet failure matters: an adopting
project ran `pip install luria` (0.3.0) while its CI pinned
`dmarx/luria/actions/generate@main`. Local lint and CI lint were executing
different code with nothing reporting it, because `pip install --upgrade
luria` is a no-op while the release is missing.

Verified: a clean checkout on the 0.4.0 tag builds
`luria-0.4.0-py3-none-any.whl` — no `.devN`, no `+g` local segment, which
matters because PyPI rejects local segments outright. A working-tree build
produces `0.4.1.dev3+g0a80de972`, correctly unuploadable. 425 tests pass and
`pip install -e .` still works.

The ADR keeps its temporary code, as #88 did — `luria concretize` assigns
the real number where merges serialize.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpK3k2WhH9Pnc3i6TjLMVv
dmarx pushed a commit that referenced this pull request Aug 16, 2026
dmarx added a commit that referenced this pull request Aug 16, 2026
#94)

* fix(release): derive the version from the tag, completing #93's rung 1

release tag. It deferred rung 1 — deriving the version — because switching to
hatch-vcs without also fetching tags would have failed the release a second
time by resolving to a `0.1.dev` fallback.

This is rung 1, with the tag fetch that makes it safe.

`pyproject.toml` declares `dynamic = ["version"]` with hatch-vcs reading
`git describe`, so there is one source for the version rather than two that
can disagree. The publish checkout takes `fetch-depth: 0`. No VCS version
file is generated: nothing in the package reads its own version, and writing
one out would reintroduce the hand-maintained copy this removes.

a recurrence loud rather than a PyPI rejection reached after the environment
gate. The two are complementary, and DP-5's own ladder puts derive above
guard rather than instead of it.

A downstream data point for why the quiet failure matters: an adopting
project ran `pip install luria` (0.3.0) while its CI pinned
`dmarx/luria/actions/generate@main`. Local lint and CI lint were executing
different code with nothing reporting it, because `pip install --upgrade
luria` is a no-op while the release is missing.

Verified: a clean checkout on the 0.4.0 tag builds
`luria-0.4.0-py3-none-any.whl` — no `.devN`, no `+g` local segment, which
matters because PyPI rejects local segments outright. A working-tree build
produces `0.4.1.dev3+g0a80de972`, correctly unuploadable. 425 tests pass and
`pip install -e .` still works.

The ADR keeps its temporary code, as #88 did — `luria concretize` assigns
the real number where merges serialize.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpK3k2WhH9Pnc3i6TjLMVv

* docs: regenerate views after rebase onto #93's concretization

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpK3k2WhH9Pnc3i6TjLMVv

---------

Co-authored-by: Claude <noreply@anthropic.com>
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