Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,41 @@ Details: [BRAND-GUIDELINES.md](BRAND-GUIDELINES.md).
| Pre-release | `-alpha.N`, `-beta.N`, `-rc.N` |
| Changelog | [Keep a Changelog](https://keepachangelog.com/) format |

### 5.1 Cutting a release

The version scheme above says what a number means. This says how one gets
out, and every line of it was paid for.
Comment on lines +147 to +148

1. **One command moves every version record, and a test asserts they
agree.** Wherever a version is written more than once, moving them by
hand moves some of them. *Incident: a release bumped the packaging
metadata and left three other records behind, so every artefact that
release produced was stamped with a version that did not produce it.*
Comment on lines +152 to +154
2. **Write the changelog entry before the tag.** It is the release note;
nothing is written twice.
3. **The tag is the only trigger, and pushing it is the human signature.**
No other event publishes. Nothing else should be able to.
4. **Order the pipeline so everything that can fail runs before the
irreversible step**, and grant the job the permissions its *last* step
needs — not its first. Publication to a package index cannot be undone
or repeated. *Incident: a release job published to the index and then
failed creating the source-forge release for want of a write
permission; the dependent job behind it never ran, and no re-run could
repair the state, because the upload could not be replayed.*
5. **Verify the artefacts, not the colour of the run** — in **both**
directions. A green run can leave a half-finished release, and a red
run can sit above a perfectly good one. Open the package index entry,
the release page, and whatever else the pipeline was supposed to touch.
6. **The procedure names its tooling, and is re-read when the tooling
changes.** A documented step the pipeline no longer performs is worse
than no documentation, because it is followed. *Incident: a release
guide still instructed the operator to create the forge release by
hand, months after the workflow began creating it from the tag.*

A failed release is not a private matter: if a version reached users in
any state, say so in the changelog entry for that version rather than in
a commit message nobody reads.

## 6. Repository discipline (every product)

| Item | Rule |
Expand Down
3 changes: 2 additions & 1 deletion lang/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ library extractions.
| [LIBRARY.md](LIBRARY.md) | Starting or maintaining a `codechu-*` library published to PyPI |
| [APPLICATION.md](APPLICATION.md) | Starting or maintaining a Python end-user app (e.g. `disk-cleaner`) |
| [PROPRIETARY.md](PROPRIETARY.md) | Starting or maintaining a **closed-source** Python library or app (private repo, license keys, signed wheels, etc.) |
| [RELEASING.md](RELEASING.md) | Cutting a release: where the version lives, the generated long description, trusted publishing, the order that survives |

These extend, never weaken, the org-wide rules in
[`STANDARDS.md`](../../STANDARDS.md) and the language-agnostic
Expand All @@ -22,5 +23,5 @@ project-type rules in

## Active Python libraries

See the roster in [`STANDARDS.md` §13](../../STANDARDS.md#13-active-repositories)
See the roster in [`STANDARDS.md` §14](../../STANDARDS.md#14-active-repositories)
and [`README.md`](../../README.md).
65 changes: 65 additions & 0 deletions lang/python/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Releasing a Python package

Extends [`STANDARDS.md` §5.1](../../STANDARDS.md#51-cutting-a-release),
which carries the language-agnostic rules. This is what §5.1 means when
the artefact is a wheel on PyPI.

Read first: [`STANDARDS.md`](../../STANDARDS.md), then your project type.

## 1. Where the version lives

A Python project writes its version in more places than it looks:

| File | Why it matters |
|---|---|
| `pyproject.toml` | what the index sees |
| `<package>/__init__.py` | what the running tool reports, and what any artefact it produces is stamped with |
| `CITATION.cff` | what a citation resolves to |
| `.zenodo.json` | what the DOI archives |

**One command moves all of them**, refuses to run when they disagree
beforehand, and does not tag, commit or push — the tag stays a deliberate
act. A test asserts the records agree, so a mismatch fails CI rather than
shipping. `__init__.py` is the quiet one: it is what a produced artefact
records, and an artefact stamped with a version that did not produce it
cannot be reproduced.

## 2. The long description is generated, never maintained

PyPI renders the description outside the repository, so every relative
link in the README 404s there. If the project ships a rewritten copy,
**generate it** and check it in CI — a copy maintained by remembering to
is a copy that ships one revision behind, and only a reader notices.
Comment on lines +31 to +32

## 3. Publish through a trusted publisher

No API token in the repository, and no token in a secret either where the
index supports OIDC. The workflow runs on the tag, builds, runs the suite
and the offline self-check *again* inside the release, then publishes.

Grant the job `contents: write` if any step after publication creates a
release or touches the repository. Publication is irreversible; a job
that can 403 after the upload leaves a state no re-run repairs.

## 4. The order that survives

```
changelog entry # the release note, written first
bump # one command, every record
suite + self-check + any generated-file check
commit, push, let CI pass
tag vX.Y.Z, push the tag # the only trigger
verify: index, release page, anything downstream
```

## 5. Installation the README recommends

A **library** is installed into a project's dependency tree: `pip install`.
A **tool** goes on the PATH and should not join anyone's dependency tree:
recommend `pipx install` first, and say what the import/command name is
when it differs from the distribution name.

A tool that shells out to another Codechu tool declares it as a
dependency, checks for it before the stage that needs it, and records the
version it found beside the result — see
[`project-type/TOOL.md`](../../project-type/TOOL.md) §5.