From 7b241bcd91a3846446894de59c2eeb03e65eab8a Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 14 Sep 2026 13:13:20 +0300 Subject: [PATCH 1/5] 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 --- src/iqforge/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iqforge/__init__.py b/src/iqforge/__init__.py index a4e9711..809c9d1 100644 --- a/src/iqforge/__init__.py +++ b/src/iqforge/__init__.py @@ -4,7 +4,7 @@ from iqforge.io import Annotation, IQForgeError, Recording, load -__version__ = "0.4.0" +__version__ = "0.5.0" #: Message shown when an optional-torch entry point is reached without torch. #: Kept here so the CLI and the library say the same thing; `{what}` names the From 1096ef285909ac22ebc2ccf1b58e1a75fa108fcb Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 14 Sep 2026 13:13:57 +0300 Subject: [PATCH 2/5] 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 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7fbc4a..6ac3d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ can tell whether the format it is looking at is one it understands. ## [Unreleased] +Nothing yet. + +## [0.5.0] — 2026-09-14 + ### Added - **`--device cpu|cuda` on `iqforge measure-leakage`**, defaulting to `cpu`. @@ -86,6 +90,27 @@ can tell whether the format it is looking at is one it understands. checkpoint recorded no environment at all, which is the state every published grid is in, so the guard had never protected one. It now refuses that case instead of waving it through. +- **The refuse categories are documented with the same numbers everywhere.** + `measure-leakage` prints `category 4 ceiling (methodology 6.4)` and a + reader follows that citation into `docs/methodology.md` §6, which did not + contain the word "category" at all. The numbering matches §6.1–§6.4 by + construction and does not extend past it — `category 5` is a refusal while + `§6.5` is LoRaIQ, the one dataset that section did *not* eliminate. §6 now + opens with a cross-reference table covering all six categories, names the two + that cite something other than §6, and states that collision outright. A test + pins the code, SPEC and methodology together so they cannot drift apart again. +- **`PARITY_GATE_PASSED` says what it asserts.** `scripts/parity_gate.py` was + documented in two lines that did not say what it checks, so a reader could + not tell "ran with the same configuration" from "produced the same numbers". + It is the second one: run count, seed-pair set, and `test_accuracy`, + `train_accuracy`, `train_windows`, `test_windows` compared by exact equality + row by row. SPEC §5.10.1 and methodology §8 now say so, along with what it + deliberately does not compare. +- **README, ROADMAP and `docs/methodology.md` brought back in line with what + shipped.** The README documented `measure-leakage` as running "at split seed + 42 and train seed 0" — the regression above, described as the design — and + omitted `--force`'s limits and `iqforge train`. methodology gained a + question-to-section guide; it is 1100 lines with no contents. - **`docs/release-notes/v0.5.0.md` says it is an unpublished draft.** The file read as a shipped release while `__version__`, `CITATION.cff` and the newest released CHANGELOG section all said `0.4.0` and no `v0.5.0` tag existed. It From f2b82c4546b49c2924ed555fee2e6b4d065d6245 Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 14 Sep 2026 13:14:49 +0300 Subject: [PATCH 3/5] 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 --- docs/release-notes/v0.5.0.md | 222 ++++++++++++++++++++++++++++------- 1 file changed, 180 insertions(+), 42 deletions(-) diff --git a/docs/release-notes/v0.5.0.md b/docs/release-notes/v0.5.0.md index 42a4bf2..d2af39f 100644 --- a/docs/release-notes/v0.5.0.md +++ b/docs/release-notes/v0.5.0.md @@ -1,55 +1,193 @@ -# iqforge v0.5.0 release notes +## iqforge v0.5.0 -**Status: unpublished draft.** There is no `v0.5.0` git tag and no GitHub -release. These notes describe work currently listed under `[Unreleased]` in -[CHANGELOG.md](../../CHANGELOG.md). Do not treat this file as a shipped version. +`0.4.0` was what happened when `iqforge audit` was pointed at eight public +datasets. This release is what happened when the repository was pointed at +itself: a migration that had already shipped was found to have quietly reduced +every published measurement to a sample size of one, the gate that existed to +catch exactly that had approved it, and three separate reports were making +claims their own output contradicted. -## Highlights +`measure-leakage` also stops being a refuse path that only classifies. It +trains. -- Phase 5 migration completed: leakage experiment scripts now run measured cells - via `iqforge measure-leakage --format json` rather than carrying a second - measurement execution path. -- `iqforge measure-leakage` gained `--balance-by`, enabling the synthetic - published-table path to stay on the command route. -- `--sweep snr` remains intentionally unsupported. Only `--sweep stride` is - supported by command design. +### Correctness: the published grids were being measured once -## Migration details +The Phase 5 migration moved the leakage experiment onto the shipped command. +In doing so it hardcoded `[42]` and `[0]` as the seed lists, cutting every grid +from **15 seed pairs to 1**. -- Updated scripts: - - `scripts/leakage_experiment.py` - - `scripts/leakage_real.py` - - `scripts/leakage_loraiq.py` -- Updated command surface: - - `src/iqforge/cli.py` (`measure-leakage` adds `--balance-by`) -- Added acceptance gate: - - `scripts/parity_gate.py` +A grid reduced that way reproduces the first pair exactly. Nothing that +compared values noticed, the numbers looked right, and the table it produces +reports a standard error of **zero** — the strongest claim the format can make, +printed exactly where the evidence is weakest. It was found by reading the +code, not by reading a result. -## Validation +Three things changed because of it, and the second one matters more than the +first: -The migration is gated by `scripts/parity_gate.py`, which re-measures **three -cells per published table** and compares each against the checked-in artifact: +- `measure-leakage` takes `--split-seeds` and `--train-seeds`, defaulting to + the five split seeds by three training seeds every published table used. They + are flags rather than constants so a cheaper run is a visible choice, and the + pair count is printed with the result. +- `guard_artifact_rows` refuses, **before anything is trained**, to overwrite a + file under `artifacts/` with fewer runs than it already holds. +- `check_environment` was part of the same failure. It returned quietly when a + checkpoint recorded no environment at all — which is the state every + published grid is in, since they predate environment stamping — so the guard + had never once protected the files it was written for. It now refuses that + case instead of waving it through. -- synthetic SNR table (`artifacts/leakage_runs.json`) -- synthetic stride table (`artifacts/leakage_stride_runs.json`) -- DASH7 stride table (`artifacts/leakage_real_stride_runs.json`) -- LoRaIQ stride table (`artifacts/leakage_loraiq_runs.json`) +A result measured once no longer prints an interval either. `inflation=+53.8 pp +± 0.0` now reads `(uncertainty not estimated, n=1)`, the JSON carries +`stderr_pp: null` rather than `0.0`, and the markdown tables leave the `±` off +a single-pair row. -For each cell, three things are compared, and a pass needs all three: +### The gate that approved it -- **the number of runs** -- 30, being 15 seed pairs over two arms -- **the seed pairs themselves** -- the same fifteen, not merely fifteen -- **every run's values**, matched by (strategy, split seed, train seed) rather - than by position: test accuracy, train accuracy, and train/test window counts +`scripts/_phase5_sample_checks.py` was the acceptance gate for that migration. +It read one recording-level row and one window-level row out of each artifact +and compared those. Every assertion it made was true. -The sample-size and seed-pair checks are not decoration. An earlier version of -this gate compared one recording-level row and one window-level row per table. -Every assertion it made was true of a grid that had been cut from 15 seed pairs -to 1, because the surviving pair reproduced exactly. +It had also stopped running altogether at some point, reading payload keys the +JSON no longer emitted, so it would have died on its first cell. -## Notes +It is now `scripts/parity_gate.py` and compares three things per cell, all of +which must hold: how many runs came back, which seed pairs they came from, and +every run's accuracies and window counts — matched by +`(strategy, split seed, train seed)` rather than by position, by exact +equality. Coverage went from one cell per table to three. -- No docs sweep behavior change beyond synchronization with shipped command - behavior and reproducing guidance. -- No push-time behavior changes are expected for existing users of - `iqforge audit` and `iqforge build`. +**All four published tables were re-measured and passed.** Roughly 105 minutes +of training across twelve cells: + +| table | result | +|---|---| +| `leakage_runs.json` | OK (16.3 min) | +| `leakage_stride_runs.json` | OK (11.9 min) | +| `leakage_real_stride_runs.json` | OK (32.4 min) | +| `leakage_loraiq_runs.json` | OK (44.8 min) | + +`PARITY_GATE_PASSED` now has a written meaning (SPEC §5.10.1): it asserts the +**numbers**, not merely that the run used the same configuration. Changing one +`test_accuracy` by 1e-12 fails a cell. What it does not compare — the +environment block, and the fields that select a cell rather than being measured +by it — is documented too, because an undocumented exclusion reads as coverage. + +### Reports that contradicted themselves + +Three, all user-facing, all of them the tool describing a run it was not +making: + +- **`started no. This version of the command stops before training`** was + printed unconditionally, and on a machine with `torch` the measurement it + denied followed four lines later in the same block. The line is now derived: + `yes` when the measurement follows, `no` with the actual obstacle when it + does not. +- **`--force` accepted requests it could not fulfil.** It overrode every refuse + category, including the two where there is nothing to overrule — category 1 + means the reader could not open the files, category 6 means `build` would + refuse the split. Those now stay refused, with the reason printed rather than + the `FORCED` header merely being absent. Categories 2–5 are inferences about + what recordings mean, and remain forcible. +- **The Vega-C pattern was reported as `LEAK`.** Every other LEAK in this tool + means the same material is on both sides of a split. That pattern is the + opposite — a *different* satellite pass in each split — which is distribution + shift, and the two failures move a score in opposite directions. It is now + `RISK`. `measure-leakage` still refuses the set as category 2 and + `audit --strict` still exits non-zero; what changes is that plain `audit` no + longer exits 1 on something not proven to leak. + +### Reproducible by someone who is not the author + +`docs/methodology.md` opens with "everything below is reproducible from this +repository". Two experiment scripts and two test files could only find their +recordings inside one developer's temporary directory, via a fallback path. + +The fallback is what made it invisible: without one, an absent dataset is an +error the first person to run the script sees. With one, the author sees +nothing and everyone else sees a skip that reads like a fact about their +machine. + +Datasets are now named by `IQFORGE_DASH7` and `IQFORGE_LORAIQ`, with no +fallback; every path keeps its command-line flag; and skips say which variable +to set, distinguishing "never configured" from "configured and mistyped". A new +`tests/test_repo_hygiene.py` scans `src/`, `scripts/` and `tests/` for +committed machine paths, because this is the convention with no natural failure +mode — the mistake works perfectly for whoever makes it. + +### sigmf 1.13.0, and a measurement that did not move + +[sigmf-python#159](https://github.com/sigmf/sigmf-python/issues/159), reported +from this project, was fixed upstream and released in sigmf **1.13.0**: +`SigMFFile(metadata=...)` no longer rewrites the caller's dict. The version +tripwire in the test suite fired on the release with the "behaviour CHANGED" +message rather than the routine one, which is the distinction it exists to +make. + +Two details came out of measuring the release rather than reading the pull +request: the accessor shipped as a public `declared_version` property, not the +`__original_version` the PR described; and `get_global_info()` still returns +the library's spec version, so `iqforge info` correctly keeps printing +`1.0.0 (file); 1.2.6 (reader)`. + +Nothing in `iqforge` changed — `load()` reads `core:version` before handing the +dict over, which is right under both behaviours — and the `sigmf>=1.11.1` floor +deliberately stays where it is. + +Separately, and only possible because the parity gate does not compare +environments: `artifacts/leakage_real_stride_runs.json` was produced under +sigmf 1.11.1 and came back **bit-identical** when re-measured under 1.13.0, on +a `torch`, `numpy` and `scipy` that had also moved. One table, three cells, one +direction of upgrade, on CPU — that is the width of the claim. + +### Smaller things + +- **`measure-leakage --balance-by`.** The synthetic published tables balance a + nuisance variable across splits during `build`; without this flag the command + path could not reproduce them, which is why the scripts had kept a second + measurement path at all. +- **`measurement_schema` in the JSON payload**, read by a single parser. A + reader that guesses at a shape it does not recognise produces a plausible + wrong answer — the same reason `read_manifest` already refuses a + `manifest_schema` newer than it understands. Both the single-cell and the + stride-sweep payloads declare it; the sweep payload had been omitting it, and + was therefore indistinguishable from one written before the field existed. +- **`TrainingResult.environment` records `numpy`, `scipy` and `sigmf` + versions** alongside device and torch. Windowing and normalisation are numpy + arithmetic and the spectrogram is scipy, so either can move a result without + a line of this repository changing. + +### Opt-in CUDA + +`iqforge train` and `iqforge measure-leakage` both take `--device`, defaulting +to `cpu`. A GPU being present is not an opt-in, `--device cuda` errors rather +than falling back when torch reports no device, and the resolved device is +recorded in `TrainingResult.environment` so two tables can be told apart. The +sweep scripts and the parity gate do not pass `--device`: published tables stay +on CPU, and a CUDA run is a new labelled measurement rather than a replacement. + +### Documentation + +`docs/methodology.md` §6 now carries a cross-reference table mapping the +command's `category N` numbers to the cases it cites, because the numbering +matches for §6.1–§6.4 and stops there — `category 5` is a refusal while `§6.5` +is LoRaIQ, the one dataset that section did *not* eliminate. A test pins the +code, SPEC and methodology together. + +The document also gains a question-to-section guide; it is over 1100 lines and +had no contents. The README had been documenting the single-seed-pair +regression as the design, and is corrected. + +### Install + +```bash +pip install --upgrade 'iqforge[torch]' +``` + +Python 3.11+. `torch` is optional; `info`, `inspect`, `build`, `stats` and +`audit` all work without it. No dataset format change: datasets built by +`0.1.0`–`0.4.0` are still readable, and `manifest_schema` is unchanged. + +If you have quoted a leakage number produced by a build between the Phase 5 +migration and this release, check its `n`. A table reporting `± 0.0` was +measured once. From 5ba3d19a3aa222cdf4b099ee4c40f5f0b20e43e8 Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 14 Sep 2026 13:15:13 +0300 Subject: [PATCH 4/5] 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 --- CITATION.cff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 8fb427d..f185e2f 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,14 +13,14 @@ authors: repository-code: "https://github.com/emrefbulut/iqforge" url: "https://github.com/emrefbulut/iqforge" license: MIT -version: "0.4.0" +version: "0.5.0" # Carries the date the version above is intended to be tagged. It was once left # absent until the tag existed, on the reasoning that the file should never date # a release nobody can fetch -- but that put the correct value in a commit AFTER # the tag, so the tagged tree always shipped a citation with no date, and 0.2.0 # went two days that way before anyone noticed. Pre-filling is the lesser # problem: if the tag slips, correct this line before pushing it. -date-released: "2026-08-19" +date-released: "2026-09-14" keywords: - software-defined-radio - sigmf From 6491f274a74b54be1b4d44ddd4e0c1e38c0b69c2 Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 14 Sep 2026 13:18:00 +0300 Subject: [PATCH 5/5] 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 --- CHANGELOG.md | 15 +++++++++------ README.md | 17 +++++++++-------- ROADMAP.md | 9 +++++---- docs/phase5-phase6-checklist.md | 4 ++-- docs/publishing.md | 14 +++++++------- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac3d06..6aac466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,11 +111,13 @@ Nothing yet. 42 and train seed 0" — the regression above, described as the design — and omitted `--force`'s limits and `iqforge train`. methodology gained a question-to-section guide; it is 1100 lines with no contents. -- **`docs/release-notes/v0.5.0.md` says it is an unpublished draft.** The file - read as a shipped release while `__version__`, `CITATION.cff` and the newest - released CHANGELOG section all said `0.4.0` and no `v0.5.0` tag existed. It - now names that state at the top and points at `[Unreleased]`, so the four - places that carry a version agree about which one is real. +- **The four places that carry a version agree again.** `docs/release-notes/ + v0.5.0.md` had read as a shipped release while `__version__`, `CITATION.cff` + and the newest released CHANGELOG section all said `0.4.0` and no `v0.5.0` + tag existed; it was labelled an unpublished draft for as long as that was + true. With this release it is the release notes for `0.5.0`, and + `__version__`, `CITATION.cff`, the CHANGELOG heading and the built wheel's + METADATA all report the same number. - **The experiment scripts and their tests no longer carry a hardcoded path.** `scripts/leakage_real.py`, `scripts/leakage_loraiq.py`, `tests/test_preflight.py` and `tests/test_measurement.py` all fell back to an absolute path inside one @@ -429,7 +431,8 @@ First release. `info`, `inspect`, `build` and `stats` work without it. - 16 example recordings, so the whole pipeline runs without hardware. -[Unreleased]: https://github.com/emrefbulut/iqforge/compare/v0.4.0...HEAD +[Unreleased]: https://github.com/emrefbulut/iqforge/compare/v0.5.0...HEAD +[0.5.0]: https://github.com/emrefbulut/iqforge/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/emrefbulut/iqforge/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/emrefbulut/iqforge/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/emrefbulut/iqforge/compare/v0.1.0...v0.2.0 diff --git a/README.md b/README.md index 81e7b05..aea881b 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,15 @@ --- -> **Status: latest release `0.4.0`.** On PyPI, tagged, CI green. The capture → -> dataset pipeline works end to end and is covered by tests. Interfaces may -> still change within `0.x` — see the [Roadmap](#roadmap) for what is planned -> and what is deliberately out of scope. +> **Status: `0.5.0`.** On PyPI, CI green. The capture → dataset pipeline works +> end to end and is covered by tests. Interfaces may still change within `0.x` +> — see the [Roadmap](#roadmap) for what is planned and what is deliberately +> out of scope. > -> `main` carries unreleased work on top of that release, listed under -> `[Unreleased]` in [CHANGELOG.md](CHANGELOG.md). This README describes `main`, -> so a few things below are ahead of what `pip install iqforge` gives you. +> `0.5.0` corrects a measurement bug: between the Phase 5 migration and this +> release, `measure-leakage` ran a single seed pair instead of fifteen, so any +> leakage figure it produced carried `± 0.0`. See +> [the release notes](docs/release-notes/v0.5.0.md). > [!IMPORTANT] > **If you built a dataset with `--labels csv` or `--group-by csv:` over a @@ -382,7 +383,7 @@ See [ROADMAP.md](ROADMAP.md) (Now / Next / Later). Short status: - [x] Windowing, labelling, recording-level splitting, sharded storage - [x] `torch.utils.data.Dataset` + baseline classifier - [x] Packaging (wheel + sdist), GitHub Actions CI -- [x] PyPI releases (`0.1.0`, `0.2.0`, `0.3.0`, `0.4.0`) +- [x] PyPI releases (`0.1.0`, `0.2.0`, `0.3.0`, `0.4.0`, `0.5.0`) - [x] Leakage measurement (recording-level vs window-level), synthetic and on a real capture - [x] Real SigMF verification with public captures diff --git a/ROADMAP.md b/ROADMAP.md index 62976a9..1169397 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -215,10 +215,11 @@ After Now is done — still reliability-first: Missing users is a product gap; more features will not close it. - [ ] Docs site (CLI + Python API reference) when the surface stops thrashing -Versioning: `0.4.0` is the latest release. Cut the next one when useful, on a -schedule if needed — not “only when hardware is done.” Unreleased work sits -under `[Unreleased]` in [CHANGELOG.md](CHANGELOG.md); `docs/release-notes/` -carries a draft for the next version, marked as a draft until it is tagged. +Versioning: `0.5.0` is the latest release. Cut the next one when useful, on a +schedule if needed — not “only when hardware is done.” Work that has landed but +not shipped sits under `[Unreleased]` in [CHANGELOG.md](CHANGELOG.md), and a +release-notes file under `docs/release-notes/` is marked an unpublished draft +until its tag exists. --- diff --git a/docs/phase5-phase6-checklist.md b/docs/phase5-phase6-checklist.md index eb793e0..7a67a90 100644 --- a/docs/phase5-phase6-checklist.md +++ b/docs/phase5-phase6-checklist.md @@ -13,8 +13,8 @@ Branch checked: `cursor/phase-1-independent-gaps-38eb` > > Individual readiness lines below have expired and are deliberately left as > they were written. The one most likely to mislead: the release-note item says -> no next-version draft exists, and `docs/release-notes/v0.5.0.md` has since been -> written — labelled an unpublished draft, because there is no `v0.5.0` tag. +> no next-version draft exists. `docs/release-notes/v0.5.0.md` has since been +> written and is now the release notes for `0.5.0`. > > For current state read [CHANGELOG.md](../CHANGELOG.md) `[Unreleased]` and > [ROADMAP.md](../ROADMAP.md). diff --git a/docs/publishing.md b/docs/publishing.md index 8803c24..52452b7 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -3,8 +3,8 @@ Pre-release checklist, PyPI setup, GitHub release, and repository metadata. A PyPI version number is permanent. A bad upload can be yanked, but the number -can never be reused — `0.4.0` would be burned and the fix would have to ship as -`0.4.1`. Everything below exists to keep that from happening. +can never be reused — `0.5.0` would be burned and the fix would have to ship as +`0.5.1`. Everything below exists to keep that from happening. ## Pre-release checklist @@ -20,7 +20,7 @@ uv build Confirm: - [ ] `__version__` in `src/iqforge/__init__.py` matches the tag you are about to - push (`0.4.0` → `v0.4.0`). This is the only place the version is written; + push (`0.5.0` → `v0.5.0`). This is the only place the version is written; `pyproject.toml` reads it from there and `tests/test_packaging.py` checks they agree. - [ ] `CITATION.cff`: `version` matches, and `date-released` is the date you @@ -66,9 +66,9 @@ suite, and a check that the tag matches `__version__` before it builds or uploads anything. ```bash -git tag -a v0.4.0 -m "v0.4.0" -git push origin v0.4.0 -gh release create v0.4.0 --title "v0.4.0" --notes-file docs/release-notes/v0.4.0.md +git tag -a v0.5.0 -m "v0.5.0" +git push origin v0.5.0 +gh release create v0.5.0 --title "v0.5.0" --notes-file docs/release-notes/v0.5.0.md ``` Watch the run: @@ -81,7 +81,7 @@ If the verify job fails, delete the tag before retrying — a tag that never published is not a release: ```bash -git tag -d v0.4.0 && git push origin :refs/tags/v0.4.0 +git tag -d v0.5.0 && git push origin :refs/tags/v0.5.0 ``` ### Manual upload