Skip to content

Process: version-defined living standards for continuously-maintained QEPs #4

Description

@mmcky

Updated after the discussion below. The original framing proposed a new Living/Active status; the converged design is simpler — it adds no new status. A QEP starts unversioned (implicitly v0) and gains a version the first time it is substantively changed after merge. type is repurposed to describe the kind of content a QEP carries.

The problem

QEP-1's lifecycle (Draft → Accepted / Rejected / Withdrawn / Superseded) treats every QEP as a one-shot decision: Accepted, frozen, and any change means Superseded by a brand-new QEP. That's right for a decision ("we adopt X"). It's wrong for a living standard that changes in small, frequent steps — tweak a colour, add a label, clarify a rule. Superseding the whole document for a one-label change is disproportionate, and it scatters "the current standard" across QEP-0002 → 0007 → 0012 that a reader has to chase.

The label policy (QEP-0002), a future style guide, and QEP-1 itself are standards of this kind: the current state is the point, and they evolve continuously.

The idea: QEPs gain a version when they change; type describes content

We do not add a Living status, and living-ness is not restricted by type — any QEP can evolve. The mechanism is a version that appears the first time a QEP is substantively amended after merge:

Field Answers Behaviour
status Was this agreed? Universal and unchanged: Draft → Accepted / Rejected / Withdrawn / Superseded.
type What kind of content is it? Descriptive content taxonomy — see below. Does not gate behaviour.
version Which revision is current? Absent = implicitly v0 (as originally accepted, never substantively changed). The first substantive amendment introduces v1; further substantive changes climb v2, v3….

In practice many QEPs stay at v0 forever (a one-off decision, never revised); a living standard climbs through versions. This keeps PEP's spirit (PEP 1, 8, 13 are amended in place) without PEP's Active status — which also sidesteps the MEP collision, where Active means frozen for voting.

The type set

With immutability no longer carried by type, it describes the kind of content a QEP holds. We adopt PEP's minimal, proven taxonomy:

Type Contains Examples
standard a normative spec or rule you conform to label schema, style guide, editorial rules, licensing, metadata conventions
process how the team or the QEP system operates the QEP process (QEP-1), review/release procedure, governance
informational non-binding guidance, rationale, reference design notes, recommendations, recorded rationale

A "policy" is a normative rule — i.e. a standard you conform to — so it is not a separate type. A one-off "decision" is a standard if it sets an ongoing rule, or informational if it is a recorded rationale.

The version field and its git anchor

The per-QEP version is the canonical pin. A git tag tags the whole repo, so it can't identify a single QEP's revision; the version field can. From v1 onward it carries a short commit hash as a YAML comment, anchoring it to git history:

version: 1  # a1b2c3d

The hash is stamped automatically at merge — a commit cannot contain its own hash, so a post-merge step writes it. Tooling that pins a standard (e.g. qe gh labels sync) reads version and verifies against the hash.

Two granularities: version (substantive) and hash (every change)

  • Substantive change (a rule, a value, a label row, the machine-readable appendix) → introduce/bump version; the hash moves too.
  • Editorial change (typo, wording, formatting, link) → version unchanged; only the hash moves (at v0, the change is simply a git commit).

One-line rule: editorial = no change to normative content; substantive = any change to normative content. For QEP-0002 this is unambiguous — any change to the label table or the appendix block is substantive.

Version numbers therefore stay meaningful (not inflated by typos), while editorial fixes are still precisely pinned by the hash and visible in git.

How amendments are made

  • Amend in place via PR under QEP-1's normal lazy-consensus. A substantive evolution of the same standard bumps the version; a different decision that replaces it is a new QEP that supersedes (so Superseded keeps its meaning — wholesale rethink only).
  • Squash-merge only. One amendment = one commit, so the document's history stays clean. A repo setting, not a convention to remember.
  • Commit subjects: substantive → QEP-N vM: <summary> (e.g. QEP-2 v1: add release-blocker label); editorial → QEP-N: <summary>. A vM: token in the log is itself the substantive-milestone marker.

Automation: stamping the hash and README (enforced by CI)

The hash stamp and README sync are mechanical and must always happen, so they live in CI, not in agent guidance:

  • A post-merge GitHub Action fires on merge to main, reads the merged short SHA, writes it into the version: N # <sha> comment, and updates the README Version column — committing with [skip ci] (or an auto-merge PR if main is protected). This runs regardless of who merged.
  • AGENTS.md / CLAUDE.md document the author-side judgement (substantive vs editorial, bump version, commit subject). This is documentation, not the enforcement — a maintainer merging via the GitHub UI never runs it, so it can't be relied on for the mechanical steps.

History: git, surfaced on the site — not a hand-maintained changelog

The change record is git itself, surfaced two ways rather than duplicated into a hand-maintained changelog (which would drift and clutter the document):

  • On the rendered site — the QuantEcon theme's git-history feature (QuantEcon/quantecon-theme.mystmd#83) renders a "Last changed: ⟨date⟩" control that expands a dropdown of recent commits per page, with GitHub-linked hashes and a full-history link. The squash-commit subjects are the changelog entries.
  • On GitHub — the file's full commit history / blame for anything beyond the recent window.

Publication: making type and version visible

  • A coloured type pill (always) and a version pill (once v1+) on the rendered QEP header — e.g. standard · v2. A v0 QEP shows only the type pill.
  • The "Last changed" + commit dropdown from the theme feature above.
  • The README index gains Type and Version columnsVersion shows at v0, then v{N} — mirroring the pills at index level.

Worked example — adding one label to QEP-0002

Illustration only; QEP-0002 is currently v0. Suppose the team later wants a release-blocker label. Under today's frozen model that one-label change would require superseding QEP-0002 wholesale. Under this proposal it is one small, in-place change:

  1. Open a PR against QEP-0002 adding the row to the Type table and the machine-readable appendix, and introducing version: 1.
  2. Review under QEP-1 lazy-consensus; squash-merge with subject QEP-2 v1: add core label release-blocker. CI stamps the hash and updates the README.
  3. On publish, the pill reads v1, the README Version column reads v1, and the theme's history dropdown shows the change with a GitHub link.
  4. qe gh labels sync pins v1 and creates release-blocker additively on its next run.

One reviewed, fully-traceable diff — not a new QEP.

CI / enforcement

  • Post-merge: stamp the version: N # hash comment and sync the README Version column.
  • On PR: version, if present, only ever increments by one; README Type/Version ⇄ front-matter parity, alongside the existing CLAUDE.md/CI label-parity check.
  • The substantive-vs-editorial call sits with author and reviewer; CI does not classify it.

Open questions from the original framing — now resolved

Original question Resolution
What counts as substantive vs editorial? Editorial = no change to normative content → hash only. Substantive → version.
v1/v2 snapshots + version dropdown — build now or defer? Rendering exists: theme git-history feature (#83) + the version pill. Pins are the simple v{N} + hash.
Living vs Active naming Moot — there is no new status word.
Should QEP-1 itself be versioned? It is versionable like any QEP; it gains v1 on its first substantive amendment (adopting this mechanism may be that amendment).

Proposed path

Amend QEP-1 to: add the version field (implicit v0, + git-hash anchor) and the version/hash granularity rule; repurpose type as the standard/process/informational content taxonomy; adopt amend-in-place + squash-merge + the commit-subject convention; and point history at git (theme feature + GitHub) rather than a hand-maintained changelog. Supporting changes: update qeps/template.md; add Type/Version columns to the README index; add the post-merge hash-stamp + README-sync Action; document the author-side steps in AGENTS.md; and set the repo to squash-merge only.

Links

Metadata

Metadata

Assignees

Labels

discussDiscussion / decision threadqepQuantEcon Enhancement Proposal

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions