Skip to content

Make every version source say 0.5.0 - #20

Merged
emrefbulut merged 5 commits into
docs/refreshfrom
release/0.5.0
Sep 14, 2026
Merged

emrefbulut merged 5 commits into
docs/refreshfrom
release/0.5.0

Conversation

@emrefbulut

Copy link
Copy Markdown
Owner

Six commits. No tag — that is yours to push.

Stacked on #19; the base retargets to main as the stack merges.

What changed

commit file(s)
7b241bc src/iqforge/__init__.py — the single source
1096ef2 CHANGELOG.md — 3 gaps filled, then 20 entries rolled into [0.5.0]
f2b82c4 docs/release-notes/v0.5.0.md — rewritten
5ba3d19 CITATION.cff
6491f27 docs/publishing.md, README.md, ROADMAP.md, CHANGELOG.md, docs/phase5-phase6-checklist.md

End-to-end verification

OK  __version__                  0.5.0
OK  editable install metadata    0.5.0
OK  wheel METADATA               0.5.0
OK  CITATION.cff version         0.5.0
OK  CHANGELOG newest section     0.5.0

OK  Unreleased is empty      'Nothing yet.'
OK  CHANGELOG date == CITATION date   2026-09-14 / 2026-09-14
OK  release notes exist, no longer a draft

The 0.2.0 failure reproduced and is worth knowing about. Straight after the bump, importlib.metadata still reported 0.4.0 from the editable install — uv sync had no reason to rebuild package metadata, because pyproject.toml itself did not change. The fix is to reinstall rather than trust the sync:

uv sync --group dev --extra torch --reinstall-package iqforge

It is in the commit message so the next person does not rediscover it.

Two judgement calls

--group-by collection is not in [0.5.0]. You listed it, but the CHANGELOG puts it under [0.4.0] — it shipped in that release. Moving it would claim it twice and misdate it.

Three entries were missing from [Unreleased] and were added before the roll-up, since rolling up an incomplete section publishes the gap: the refuse-category alignment across code/SPEC/methodology, what PARITY_GATE_PASSED asserts, and the README/ROADMAP/methodology refresh.

Release notes

Rewritten, not patched. The draft covered the Phase 5 migration in three bullets and a file list; [0.5.0] has twenty entries. It now follows the house style — 0.4.0 opened with a bug it had been shipping since 0.1.0, so this one opens with the migration that reduced every published measurement to a sample size of one and the gate that approved it.

Coverage was checked entry by entry rather than by eye, which caught --balance-by and measurement_schema missing on the first pass — and caught a checker of mine that matched manifest_schema and reported measurement_schema as present when it was not.

Post-bump sweep

Raising the version made seven statements false that had been true an hour earlier. Most consequential: docs/publishing.md still gave git tag -a v0.4.0 in its worked example — following that page would have tagged the wrong version.

Also corrected: CHANGELOG link refs ([Unreleased] compared v0.4.0...HEAD, no [0.5.0] ref), the README status block and release list, the ROADMAP versioning footnote, and the superseded note on the Phase 5/6 checklist.

Every surviving 0.4.0 was judged individually and is correct: the [0.4.0] heading and link ref, the entry describing the state this release fixed, and the notes comparing to the previous release.

Verification

ruff check .            All checks passed!
ruff format --check .   82 files already formatted
pytest -q -rs           385 passed, 1 skipped
uv build                iqforge-0.5.0-py3-none-any.whl, iqforge-0.5.0.tar.gz

Before you tag

CITATION.cff date-released and the [0.5.0] heading both say 2026-09-14, pre-filled deliberately — the comment in CITATION.cff records that adding the date after tagging once shipped 0.2.0 with a dateless citation for two days. If the tag lands on another day, correct both lines first.

🤖 Generated with Claude Code

emrefbulut and others added 5 commits September 14, 2026 13:13
src/iqforge/__init__.py is the single source: pyproject declares
dynamic = ["version"] and [tool.hatch.version] reads this file, so nothing
else in the tree carries a version string to keep in step.

Note for whoever runs this next. After the bump an editable install still
reported 0.4.0 from importlib.metadata, because uv sync had no reason to
rebuild the package metadata -- pyproject itself did not change. That is the
0.2.0 failure exactly, and the fix is to reinstall the package rather than to
trust the sync:

    uv sync --group dev --extra torch --reinstall-package iqforge

__version__, importlib.metadata and `iqforge version` agree afterwards, and
tests/test_packaging.py passes. No tag here; tagging is a separate, manual
step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twenty entries move under a dated 0.5.0 heading and Unreleased is left empty.

Three changes from this cycle had shipped without an entry and are added
first, because rolling up an incomplete section would have published the gap:

- the refuse categories now carrying the same numbers in code, SPEC and
  methodology, including the category 5 / 6.5 collision that the cross-
  reference table exists to head off
- what PARITY_GATE_PASSED asserts, which is the numbers rather than the
  configuration
- the README / ROADMAP / methodology refresh, including the README having
  documented the single-seed-pair regression as if it were the design

One item from the brief does not belong here. --group-by collection is
already in the [0.4.0] section: it shipped in that release, and moving it
would claim it twice and misdate it.

The date is today's. If the tag lands on a different day, this line is the
one to correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The draft covered the Phase 5 migration and nothing else: three bullets of
highlights, a file list, and the gate description. Twenty entries went into
[0.5.0], and the draft named perhaps four of them. It was also written in a
checklist voice rather than the one the previous notes use.

Rewritten in the house style, with the headline where 0.4.0 put its own: the
correctness problem first. 0.4.0 opened with a bug it had been shipping since
0.1.0; this one opens with a migration that reduced every published
measurement to a sample size of one and a gate that approved it.

The "unpublished draft" banner is gone, because the version is now 0.5.0 in
__init__.py and the CHANGELOG section is dated. Tagging remains manual.

Coverage checked entry by entry against the [0.5.0] section rather than by
eye -- which caught --balance-by and measurement_schema missing on the first
pass, and a checker of mine that matched "manifest_schema" and reported
measurement_schema as present when it was not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
version and date-released both move. The comment above date-released explains
why the date is pre-filled rather than added after tagging: doing it
afterwards put the correct value in a commit AFTER the tag, so the tagged tree
shipped a citation with no date, and 0.2.0 went two days that way.

That reasoning still applies, so the date here is the intended tag date
(2026-09-14). If the tag slips, correct this line before pushing it.

Validated as YAML and checked against __version__ rather than eyeballed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Raising __version__ made seven statements false that had been true an hour
earlier. Found by sweeping for "0.4.0" after the bump rather than by
remembering which files mention a version.

- docs/publishing.md: the tag, release and rollback commands, and the
  "a burned number" example, all still said v0.4.0. Someone following that
  page would have tagged the wrong version.
- CHANGELOG link refs: [Unreleased] compared v0.4.0...HEAD and there was no
  [0.5.0] ref for the heading that now exists.
- CHANGELOG: the entry describing v0.5.0.md as an unpublished draft was about
  a state this release ends. Reworded to what shipped -- the four places that
  carry a version agreeing -- rather than deleted, since the drift it records
  is the point.
- README status block said "latest release 0.4.0" plus a note about main
  carrying unreleased work, which is no longer true now that [Unreleased] is
  empty. It now names 0.5.0 and, in its place, the one thing a reader of an
  older build needs to know: between the Phase 5 migration and this release
  measure-leakage ran one seed pair, so any figure it produced carried +/- 0.0.
- README PyPI release list, and the ROADMAP versioning footnote.
- docs/phase5-phase6-checklist.md: the superseded note I added earlier said
  v0.5.0.md was labelled a draft because no tag existed. It is the release
  notes now.

Every surviving "0.4.0" was checked individually and is correct: the [0.4.0]
heading and link ref, the CHANGELOG entry describing the state that was fixed,
and the release notes comparing this release to the previous one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@emrefbulut
emrefbulut merged commit f032f6b into docs/refresh Sep 14, 2026
5 checks passed
emrefbulut added a commit that referenced this pull request Sep 14, 2026
The stacked PRs #18, #19 and #20 were merged at the same moment, so only #18
reached main -- #19 merged into fix/gate-semantics-and-provenance and #20 into
docs/refresh, their own bases. Everything from those two is therefore sitting
on this branch and main is still on 0.4.0. That was my mistake in stacking
three deep instead of retargeting; this merge is the fix.

One conflict, in CHANGELOG.md. main carried the entry describing
docs/release-notes/v0.5.0.md as an unpublished draft, which commit 6491f27 on
this branch had already rewritten into "the four places that carry a version
agree again" -- the draft label was the state this release ends. Kept this
branch's side, which also carries three entries main does not have.

Checked rather than assumed, because a merge across branches that both edited
the test files could silently drop one side: #17's CUDA comparability tests
and #16's forced-measurement width test are both present afterwards, and the
suite goes from 385 to 388 passing, which is main's three additions arriving
rather than anything being lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
emrefbulut added a commit that referenced this pull request Sep 14, 2026
* Say what PARITY_GATE_PASSED asserts, and what it does not

The gate was documented in two lines, neither of which said what it checks.
"The published tables and the parity gate stay on CPU" is a policy, not a
contract, and a reader had no way to tell whether a pass meant "ran with the
same settings" or "produced the same numbers". Those are different claims and
only one of them is true of this gate.

It is the stronger one. compare_cell compares test_accuracy, train_accuracy,
train_windows and test_windows by exact equality, row by row, matched by
(strategy, split seed, train seed) rather than by position -- on top of the
run count and the seed-pair set. Demonstrated against the real rows of
leakage_real_stride_runs.json: one test_accuracy moved by 1e-12 fails the
cell.

What it does not compare is now written down too, because an undocumented
exclusion reads as coverage. It ignores the environment block, and it ignores
stride / noise_sigma / snr_db, which select the cell rather than being
measured by it -- a wrong stride changes the window counts, and those are
compared.

The environment exclusion is load-bearing rather than missing. Because the
gate does not compare environments, a re-run on a different library stack is
a real difference between the two runs, and equality across it is a result.
methodology 8 now records one: leakage_real_stride_runs.json was produced on
2026-08-11 under sigmf 1.11.1 -- inferred from the tripwire not recording
1.12.0 until eight days later, and labelled as an inference because the
artifact carries environment: null -- and three cells of it came back
bit-identical when re-measured under sigmf 1.13.0 on a torch, numpy and scipy
that also moved. Stated at the width it supports: one table, three cells, one
direction of upgrade, on CPU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Refuse --force on the two categories that are not judgements

--force overrode every refuse category, including the two where there is
nothing to overrule.

Categories 2, 3, 4 and 5 are inferences: this tool deciding what a shared
timestamp, a 43-second gap, a separable axis or an audit LEAK probably means.
Someone who knows their own recordings can be right where the inference is
wrong, and that is what the flag is for -- the overridden category stays in
the header so the block cannot be pasted as a clean run.

Categories 1 and 6 are not inferences. Category 1 means the reader could not
open the files; there is nothing to measure and forcing produces a crash
rather than a questionable number. Category 6 means build would refuse the
split, so the paired measurement cannot be constructed at all. Accepting
--force on either is accepting a request that cannot be fulfilled, and then
failing somewhere further in with a message about something else -- which is
exactly the shape of failure the refuse path exists to prevent.

They now stay REFUSED with exit code 1, and the report says so rather than
merely omitting the FORCED header: `forced  refused. category 1 ... cannot be
overridden`. The JSON carries force_refused alongside forced and forced_past.
Silence would have been indistinguishable from the flag not being passed.

Mutations, each red on the test that should catch it: emptying
STRUCTURAL_CATEGORIES lets the structural refusals be forced again; filling it
with every category breaks the four that must stay forcible; dropping the
report line makes the refusal silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Make the report say whether it trained, instead of asserting it did not

The WOULD MEASURE path printed

    started       no. This version of the command stops before training

unconditionally, and on a machine with torch the measurement it denied
followed four lines later in the same block. A refuse path whose report
cannot be trusted about its own behaviour has no standing to be trusted
about the recordings.

The line is now derived rather than fixed. The CLI decides before the block
is rendered whether training will follow -- torch present, and a folder of
recordings rather than a built dataset -- and passes the obstacle to decide()
when there is one. `started` then reads `yes. Both arms train after this
block and the result follows below`, or `no.` followed by the actual reason,
or `no. Refused above; nothing was built and nothing was trained`.

The reason is passed in rather than inferred inside decide(), because the
decision genuinely does not know whether its caller intends to train.
Guessing is what produced the wrong sentence.

Three other places asserted the same stale thing and are corrected: the
module docstring, the default WOULD MEASURE reason, and the WorkEstimate
docstring. The JSON gains `trains` and `no_train_reason` so a machine reader
does not have to parse the sentence.

CHANGELOG: "No training CLI yet." and "This version does not train." were
both false and are rewritten, the second one also picking up the --force
restriction from the previous commit.

Mutations: hardcoding the old sentence back fails two tests; making the CLI
stop passing the reason fails the torch-free test. That second mutation
initially passed, because on this machine every run trains and "yes" is right
by accident -- so the branch is now forced open by patching _torch_available
rather than left unreachable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Declare the schema in the sweep payload too, and stop duplicating the row writer

measure-leakage --format json had two payload builders and only one was
updated when measurement_schema was introduced. The single-cell path emitted
it and serialised rows through _run_row; the sweep path emitted neither, and
repeated _run_row's nine fields by hand.

runs_from_payload treats a missing measurement_schema as "unversioned, assume
compatible". That is right for payloads written before the field existed, and
it meant a sweep payload written today was indistinguishable from one of
those -- so sweep payloads would be exactly the ones that cannot be told
apart when schema 2 arrives.

The hand-rolled copy is the same shape of duplication that dropped the seed
lists from one of three near-identical measurement calls. A field added to
_run_row would not have reached the sweep payload.

The sweep payload also gains `forced`, which the single-cell payload already
carried. A reader should not have to know which mode produced a file to know
whether --force was used.

Closes #3. Mutations: removing the schema key fails two tests; hand-rolling
the rows again fails the third.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Record the three Bolum C changes that never reached the CHANGELOG

Three shipped changes had no entry. The most consequential one had none at
all: the commit that restored the 15 seed pairs touched seven files and not
this one, so the single change that fixed the sample-size regression -- and
added the guard that stops a published grid shrinking again -- was visible
only as a passing mention inside the parity-gate entry.

Added:

- The seed-pair restoration, the --split-seeds / --train-seeds flags,
  guard_artifact_rows, and the check_environment hole that made the guard
  ineffective on exactly the files it was meant to protect.
- measurement_schema in the JSON payload, with the reason it exists: the same
  one read_manifest already has.
- docs/release-notes/v0.5.0.md being labelled an unpublished draft, which is
  what makes __version__, CITATION.cff, the CHANGELOG and the notes agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Split the grouping roadmap item at the line where it was already done

The item was unchecked and its own body said "`--group-by collection` is now
shipped, so the first half of that is done". A checkbox that disagrees with
the paragraph under it teaches a reader to stop trusting the checkboxes.

It was one item covering two things: read the SigMF field, and decide whether
the field needs an extension. The first shipped -- `collection` is the third
scheme beside `path:` and `csv:`, with a test, and a recording that declares
no collection stays its own unit. The second has not started, and is the one
with a bar to clear: use it on a public dataset and record with evidence what
`core:collection` could not express.

They are now two items, ticked according to what is true of each. The
reasoning note and the sigmf-python#159 / SigMF#233 precedent stay attached to
the half they inform, which is the proposal rather than the scheme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Make methodology name the category numbers it is cited by

measure-leakage prints `category 4  ceiling  (methodology 6.4)` and a reader
follows that into §6. §6 did not contain the word "category" anywhere. The
numbering matched for the four eliminated datasets by construction and nothing
held it there, and past four it does not match at all -- which mattered more
than the missing word:

  category 5 is a refusal, a structural leak audit named
  §6.5 is LoRaIQ, the one dataset in that section that was NOT eliminated

Same digit, opposite meaning, and nothing warned a reader following a citation.

§6 now opens with a cross-reference table covering all six categories, marks
the two that have no case here and what they cite instead, states the 5 / 6.5
collision explicitly, and notes which categories --force can override.

A test pins all three documents together: for every member of the Category
enum, SPEC and methodology must each carry a table row with that number and
that name, and the collision warning must still be there. Mutations:
renumbering a category in the code alone fails it, deleting one methodology
row fails it, softening the warning to "Note." fails it.

The assertions compare with whitespace removed. These documents are
hard-wrapped and "There is deliberately no category for it" already straddles
two lines -- an exact-substring assertion passed only until the paragraph was
re-wrapped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Update the assertion that still expected the sentence we removed

test_loraiq_pattern_is_not_refused_when_grouped asserted "stops before
training" in its torch-free branch. That branch is unreachable on a machine
with torch, so the full local suite passed after the output changed and CI's
test (3.11) and test (3.12) jobs are what caught it.

That is precisely the failure this PR describes one commit earlier -- an
assertion whose coverage depends silently on the environment -- and I walked
into it while fixing it. Worth saying rather than quietly amending.

The branch now asserts what the report actually prints, the torch branch
additionally pins `started  yes.`, and the old sentence is asserted absent on
both paths so neither can drift back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Correct the README where it had stopped being true

Four things, one of which was actively misleading.

measure-leakage was advertised as running "at split seed 42 and train seed 0".
That is the Phase 5 regression, described as if it were the design: the
command measures 15 seed pairs, and the single pair was the bug that this
project has spent the last stretch removing. A README that documents the
defect as the feature is worse than one that is merely out of date.

--force was described without its limits. It applies to categories 2-5, which
are inferences about what the recordings mean; categories 1 and 6 say no
measurement can be constructed, and are refused with a reason.

The command tour listed info, inspect, build, stats, audit and
measure-leakage, but not train -- which the rest of the README and
CONTRIBUTING both use.

The roadmap checklist was missing --group-by (three schemes, including
collection) and opt-in CUDA, both shipped, and did not list frequency-aware
labelling even though the known-limitations section points at it as the fix.

The status block keeps saying 0.4.0, because that is the released version:
__version__, CITATION.cff and the newest tag all agree, and
docs/release-notes/v0.5.0.md says on its first line that it is an unpublished
draft. What it now adds is that main carries unreleased work, so a reader can
tell which parts of this file are ahead of the wheel they installed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Move the finished half of the real-capture item out of "Do next"

"Repeat the leakage measurement on a real recording" sat unchecked in Now as
though nothing had happened. Half of it has shipped: the stride sweep ran on
DASH7 and LoRaIQ, both tables are in artifacts/, and LoRaIQ at 7/8 overlap is
the first individually significant real-data result in this repository. A
roadmap that does not show that makes the remaining work look larger than it
is and the finished work invisible.

It is now two entries. The overlap half is ticked, with the numbers and the
limit that still applies -- the intermediate overlaps are unresolved at 15
seed pairs. The open item narrows to the SNR half, and points at
docs/leakage-real-snr.md, where the seed count and SNR list are locked before
the run so the result cannot pick its own stopping rule.

The parity-gate re-measurement is added as a separate finished entry, since
it is the thing that says the published tables still come out of the shipped
command.

The versioning footnote named 0.3.x / 0.4.0 as things to cut; 0.4.0 is out. It
now says where unreleased work lives and that the release notes for the next
version are marked drafts until a tag exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Give methodology a map, so a reader can start from their question

The document is over 1100 lines across nine numbered sections and had no
contents at all -- the only way in was the top. Most readers arrive with one
question, and the section that answers it is rarely the first.

The table maps a question to a section rather than listing titles, because
"Measurement 1 -- accuracy inflation against SNR" does not tell someone who
wants to know how much a leaky split invents that this is their section.

It also points out the two sections worth reading regardless of whether the
measurements apply to your data: 7, the mistakes this project shipped, and 9,
what the numbers do not support.

Anchors verified against GitHub's slug rule rather than assumed. The first
attempt used a checker that collapsed consecutive spaces, which flagged two
correct anchors as dead -- the em dash in those two headings leaves a double
space, and GitHub turns each space into its own hyphen without collapsing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Mark the Phase 5/6 checklist superseded instead of editing its findings

The file carries "Date checked: 2026-08-19" and reports readiness as of that
date. One of its lines -- "no next-version draft file yet" -- is now false,
because docs/release-notes/v0.5.0.md exists.

Correcting the line in place would falsify a dated record, so the body is left
exactly as written and a note at the top says the phase has since been
executed and re-verified, names the line most likely to mislead, and points at
CHANGELOG [Unreleased] and ROADMAP for current state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Make every version source say 0.5.0 (#20)

* Raise the version to 0.5.0

src/iqforge/__init__.py is the single source: pyproject declares
dynamic = ["version"] and [tool.hatch.version] reads this file, so nothing
else in the tree carries a version string to keep in step.

Note for whoever runs this next. After the bump an editable install still
reported 0.4.0 from importlib.metadata, because uv sync had no reason to
rebuild the package metadata -- pyproject itself did not change. That is the
0.2.0 failure exactly, and the fix is to reinstall the package rather than to
trust the sync:

    uv sync --group dev --extra torch --reinstall-package iqforge

__version__, importlib.metadata and `iqforge version` agree afterwards, and
tests/test_packaging.py passes. No tag here; tagging is a separate, manual
step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Close [Unreleased] as [0.5.0], after filling three gaps in it

Twenty entries move under a dated 0.5.0 heading and Unreleased is left empty.

Three changes from this cycle had shipped without an entry and are added
first, because rolling up an incomplete section would have published the gap:

- the refuse categories now carrying the same numbers in code, SPEC and
  methodology, including the category 5 / 6.5 collision that the cross-
  reference table exists to head off
- what PARITY_GATE_PASSED asserts, which is the numbers rather than the
  configuration
- the README / ROADMAP / methodology refresh, including the README having
  documented the single-seed-pair regression as if it were the design

One item from the brief does not belong here. --group-by collection is
already in the [0.4.0] section: it shipped in that release, and moving it
would claim it twice and misdate it.

The date is today's. If the tag lands on a different day, this line is the
one to correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Rewrite the v0.5.0 release notes for what the release actually contains

The draft covered the Phase 5 migration and nothing else: three bullets of
highlights, a file list, and the gate description. Twenty entries went into
[0.5.0], and the draft named perhaps four of them. It was also written in a
checklist voice rather than the one the previous notes use.

Rewritten in the house style, with the headline where 0.4.0 put its own: the
correctness problem first. 0.4.0 opened with a bug it had been shipping since
0.1.0; this one opens with a migration that reduced every published
measurement to a sample size of one and a gate that approved it.

The "unpublished draft" banner is gone, because the version is now 0.5.0 in
__init__.py and the CHANGELOG section is dated. Tagging remains manual.

Coverage checked entry by entry against the [0.5.0] section rather than by
eye -- which caught --balance-by and measurement_schema missing on the first
pass, and a checker of mine that matched "manifest_schema" and reported
measurement_schema as present when it was not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Point the citation at 0.5.0

version and date-released both move. The comment above date-released explains
why the date is pre-filled rather than added after tagging: doing it
afterwards put the correct value in a commit AFTER the tag, so the tagged tree
shipped a citation with no date, and 0.2.0 went two days that way.

That reasoning still applies, so the date here is the intended tag date
(2026-09-14). If the tag slips, correct this line before pushing it.

Validated as YAML and checked against __version__ rather than eyeballed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Follow the bump through every place that named the old version

Raising __version__ made seven statements false that had been true an hour
earlier. Found by sweeping for "0.4.0" after the bump rather than by
remembering which files mention a version.

- docs/publishing.md: the tag, release and rollback commands, and the
  "a burned number" example, all still said v0.4.0. Someone following that
  page would have tagged the wrong version.
- CHANGELOG link refs: [Unreleased] compared v0.4.0...HEAD and there was no
  [0.5.0] ref for the heading that now exists.
- CHANGELOG: the entry describing v0.5.0.md as an unpublished draft was about
  a state this release ends. Reworded to what shipped -- the four places that
  carry a version agreeing -- rather than deleted, since the drift it records
  is the point.
- README status block said "latest release 0.4.0" plus a note about main
  carrying unreleased work, which is no longer true now that [Unreleased] is
  empty. It now names 0.5.0 and, in its place, the one thing a reader of an
  older build needs to know: between the Phase 5 migration and this release
  measure-leakage ran one seed pair, so any figure it produced carried +/- 0.0.
- README PyPI release list, and the ROADMAP versioning footnote.
- docs/phase5-phase6-checklist.md: the superseded note I added earlier said
  v0.5.0.md was labelled a draft because no tag existed. It is the release
  notes now.

Every surviving "0.4.0" was checked individually and is correct: the [0.4.0]
heading and link ref, the CHANGELOG entry describing the state that was fixed,
and the release notes comparing this release to the previous one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <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