From 17be61ce81b534426e8f1bb9bef96c908e934b45 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Thu, 27 Aug 2026 13:27:59 +1000 Subject: [PATCH] QEP-1 v3: stamp version and version-hash from v0; one-week window Every merged QEP carries version and version-hash from the moment it lands: the post-merge stamp writes version: 0 and the hash at first merge, uniformly across Accepted, Rejected and Withdrawn outcomes, and the hash moves on every later change, editorial included. Replaces the implicit v0 (absent version, anchored only from v1), which pushed an absent-means-v0 special case into every consumer and left QEP-2's machine-readable appendix with no recorded revision to cite (#22). Also shortens the default comment window from one-to-two weeks to one week (small team; the author may extend it for a larger change), states plainly that version-hash is a historical anchor rather than a file checksum, and records the dropped implicit-v0 design under Alternatives. Supporting machinery changes (stamp action, checks, backfill, README note, AGENTS.md) follow after acceptance per adoption entry v3. Co-Authored-By: Claude Fable 5 --- README.md | 5 +- qeps/qep-0001-purpose-and-process.md | 99 +++++++++++++++++++--------- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 17c03fb..1d3525a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ need a QEP. | QEP | Title | Type | Status | Version | |-----|-------|------|--------|---------| -| [QEP-1](qeps/qep-0001-purpose-and-process.md) | QEP Purpose and Process | process | Accepted | v2 | +| [QEP-1](qeps/qep-0001-purpose-and-process.md) | QEP Purpose and Process | process | Accepted | v3 | | [QEP-2](qeps/qep-0002-standard-github-labels.md) | Standard GitHub Label Set and Labelling Policy | standard | Accepted | – | QEPs that set an ongoing rule are **maintained in place**: a substantive amendment bumps @@ -29,7 +29,8 @@ and each QEP's `version-hash` is stamped into its frontmatter at merge; `Version socialise it and confirm it needs a QEP. 2. **Draft it.** Copy [`qeps/template.md`](qeps/template.md) to `qeps/qep-XXXX-short-slug.md`, fill it in with **Status: Draft**, and open a PR. -3. **Set a deadline.** Announce the PR and give a comment window (1–2 weeks). +3. **Set a deadline.** Announce the PR and give a comment window (normally one + week; extend it for a larger change). 4. **Decide.** At the deadline the Core Maintainers decide by lazy consensus; the QEP is merged recording the outcome (Accepted / Rejected / Withdrawn). diff --git a/qeps/qep-0001-purpose-and-process.md b/qeps/qep-0001-purpose-and-process.md index 2854d5e..96836ef 100644 --- a/qeps/qep-0001-purpose-and-process.md +++ b/qeps/qep-0001-purpose-and-process.md @@ -4,8 +4,7 @@ title: QEP Purpose and Process author: "@mmcky" status: Accepted type: process -version: 2 -version-hash: 4ee318d # stamped by CI; do not edit +version: 3 created: 2026-06-16 discussion: https://github.com/QuantEcon/meta/issues/325 --- @@ -19,7 +18,7 @@ discussion: https://github.com/QuantEcon/meta/issues/325 | **Author** | @mmcky | | **Status** | Accepted | | **Type** | process | -| **Version** | 2 | +| **Version** | 3 | | **Created** | 2026-06-16 | | **Discussion** | [QuantEcon/meta#325](https://github.com/QuantEcon/meta/issues/325) | @@ -109,14 +108,16 @@ standard. 2. **Draft.** Open a PR adding `qeps/qep-XXXX-slug.md` from the template (with **Status: Draft** and a discussion link) and a matching row in the README index. 3. **Set a deadline.** The author announces the PR and sets a comment window — - normally **one to two weeks** — recording the **decision deadline** in the PR + normally **one week**; the team is small, and the author may extend it for a + larger or more contested change — recording the **decision deadline** in the PR description. 4. **Decide.** At the deadline, the **Core Maintainers** decide by **lazy consensus**: objections are raised as PR comments, and no sustained objection means the QEP is Accepted. If there is no consensus, the lead (@jstac) decides or defers. 5. **Record.** On acceptance, set **Status: Accepted** — in the frontmatter, the header - table, and the README index row — confirm the number, and merge. A newly accepted - QEP carries no `version`: it is implicitly **v0** until first amended. + table, and the README index row — confirm the number, and merge. The PR itself + carries no `version`: CI stamps **`version: 0`** and its `version-hash` anchor at + merge (see *Versioning*). ### Amending an accepted QEP @@ -144,36 +145,45 @@ substantive-milestone marker. ### Versioning: `version` and its git anchor -A QEP gains a `version` the first time it is **substantively** changed after acceptance: +Every merged QEP carries a `version` from the moment it lands: | `version` | Meaning | | ----------- | --------------------------------------------------------------------- | -| *absent* | Implicitly **v0** — as originally accepted, never substantively changed. Many QEPs (a one-off decision) stay here forever. | -| `1`, `2`, … | The current substantive revision. The first substantive amendment introduces `version: 1`; each later substantive change climbs to `2`, `3`, … | +| `0` | As originally merged, never substantively changed. Many QEPs (a one-off decision) stay here forever. | +| `1`, `2`, … | The current substantive revision. The first substantive amendment climbs to `version: 1`; each later substantive change to `2`, `3`, … | -From `v1` onward a sibling `version-hash` field carries the short commit hash that -anchors the revision to git history: +A sibling `version-hash` field carries the short commit hash that anchors the revision +to git history: ```yaml -version: 2 +version: 0 version-hash: a1b2c3d # stamped by CI; do not edit ``` +Both fields are machine-written at birth: a Draft carries neither, and the post-merge +step (see *Automation*) stamps `version: 0` and the hash when the QEP first merges — a +commit cannot contain its own hash, so neither field is ever hand-written to start. +From then on the author bumps `version` on substantive amendments, and CI re-stamps the +hash on every merged change, editorial included. The stamp is uniform across merged +outcomes — Accepted, Rejected, and Withdrawn QEPs all carry it — so every durable +record is machine-referenceable. + `version` is a plain number; the commit hash lives in the separate `version-hash` field — -a real key, so any YAML parser keeps it. The hash is stamped -**automatically at merge** — a commit cannot contain its own hash, so a post-merge step -(see *Automation*) writes it; never hand-write it. Tooling that pins a standard (for -example a labels-sync command) reads `version` and verifies against `version-hash`. A -per-QEP `version` is the right anchor because a git *tag* tags the whole repository, not -one QEP's revision. +a real key, so any YAML parser keeps it. Tooling that pins a standard (for example a +labels-sync command) reads `version` and cross-checks `version-hash` against the +revision it fetched. **`version-hash` is a historical anchor, not a file checksum**: +the stamp commit post-dates the hash it writes, so the field names the revision that +last changed the QEP — it does not hash the file's bytes. A per-QEP `version` is the +right anchor because a git *tag* tags the whole repository, not one QEP's revision. **Substantive vs editorial** decides whether the number moves: - **Substantive** — any change to normative content (a rule, a value, a table row, a machine-readable appendix) → **bump `version`** by one; the hash moves too. - **Editorial** — no change to normative content (a typo, wording, formatting, a link) - → **`version` unchanged**; only the hash moves (at v0, the change is simply a git - commit). + → **`version` unchanged**; only the hash moves — at v0 exactly as at v1+, so a + consumer of a machine-readable appendix sees that something changed without diffing + git. One-line rule: *editorial = no change to normative content; substantive = any change to normative content.* This keeps version numbers meaningful — not inflated by typos — @@ -194,21 +204,24 @@ hand-maintained changelog (which would drift and clutter the document): Type and version are surfaced two ways: -- the **README index** carries `Type` and `Version` columns, with `Version` showing `–` - at v0 and `v{N}` thereafter — repo-controlled, so it renders on any theme; -- under the **QuantEcon theme** (once adopted), a coloured **`type` pill** always and a - **`version` pill** once a QEP reaches `v1` — e.g. `standard` · `v2`; a v0 QEP shows only - the type pill. +- the **README index** carries `Type` and `Version` columns, with `Version` showing + `v{N}` from `v0` up (`–` only while a Draft's PR is open) — repo-controlled, so it + renders on any theme; +- under the **QuantEcon theme** (once adopted), a coloured **`type` pill** and a + **`version` pill** on every merged QEP — e.g. `standard` · `v2`; `v0` is shown rather + than hidden, since it names an anchored revision. ### Automation Two mechanical steps are enforced by CI rather than left to memory: - a **post-merge action** (`.github/workflows/stamp-version.yml`) reads the merged short - hash, writes it into the `version-hash` field, and keeps the README `Type`/`Version` - columns in sync with each QEP's frontmatter; + hash, stamps `version: 0` alongside it into any newly merged QEP that carries no + `version`, writes the hash into every changed QEP's `version-hash` field, and keeps + the README `Type`/`Version` columns in sync with each QEP's frontmatter; - a **pull-request check** (`.github/workflows/qep-checks.yml`) confirms that `version` - moves legally — a new QEP starts unversioned, a versioned QEP stays versioned, and the + moves legally — a new QEP arrives unversioned in its PR (`version: 0` is stamped at + merge), a stamped QEP stays versioned, and the number stays the same (editorial) or increases by exactly one (substantive) — that `type` and `status` are known values, and that the README `Type`/`Status`/`Version` columns match each QEP's frontmatter. @@ -237,8 +250,8 @@ light as the decisions it records. ### Format Each QEP is a Markdown file with YAML frontmatter (`qep`, `title`, `author`, `status`, -`type`, `created`, `discussion` — plus `version` and its CI-stamped `version-hash`, which -sit just after `type` once the QEP is first amended) followed by the sections in +`type`, `created`, `discussion` — plus the CI-stamped `version` and `version-hash`, which +sit just after `type` from the QEP's first merge) followed by the sections in [`qeps/template.md`](../qeps/template.md): **Summary, Motivation, Proposal, Alternatives considered, Adoption**. The `type` field describes the **kind of content** the QEP carries: @@ -278,6 +291,14 @@ type; a one-off *decision* is a `standard` if it sets an ongoing rule, or QEP would duplicate git, drift from it, and clutter the document; we point at git instead, surfaced on the site by the theme's history feature and on GitHub by history/blame. +- **An implicit v0 (absent `version`), anchored only from v1.** The v1–v2 design: + absence itself said "never substantively changed", and one-off QEPs carried no stamp. + Dropped in v3 because the asymmetry pushed a special case into every consumer + ("absent means v0 — choose your own anchor") and left the machine-readable appendix + of an accepted-but-unamended standard with no recorded revision at all: QEP-2 shipped + normative tooling input with nothing to cite ([#22](https://github.com/QuantEcon/qeps/issues/22)). + Uniform stamping from v0 costs a pill and a bot commit; the implicit v0 cost + correctness, in prose that described pinning which did not yet exist. ## Adoption @@ -302,3 +323,21 @@ type; a one-off *decision* is a `standard` if it sets an ongoing rule, or can be falsified by work not happening; sequenced execution (who does what, when) belongs in a tracking issue. Applied first by QEP-2, whose acceptance PR carries this amendment. +4. **(v3) Stamp `version` from v0; default comment window one week.** The same + amendment shortens the normal comment window from one-to-two weeks to **one + week** — the team is small enough that a fortnight is drift, not diligence, and + the author can still extend the window for a larger change. On the stamping + change: every merged QEP carries `version` and + `version-hash` from the moment it lands, so tooling reads one uniform contract + instead of treating an absent `version` as an implicit v0 with no anchor — the + asymmetry surfaced by QEP-2's machine-readable appendix + ([#22](https://github.com/QuantEcon/qeps/issues/22)). Stamping's supporting + changes, landing + as a follow-up once this amendment merges: the post-merge stamp action adds + `version: 0` where missing; already-merged v0 QEPs are backfilled, each stamped + with the most recent commit that touched it — mechanical, and consistent with the + rule that editorial changes move the hash; the pull-request check's new-QEP rule + becomes "unversioned in the PR, `v0` at merge"; the README index and the theme's + version pill show `v0` rather than `–`; `AGENTS.md` and the README's index note + follow. `qeps/template.md` is unchanged — `version` is machine-written, never + hand-written.