Skip to content

QEP-1 v1: version-defined living standards (amend in place, not supersede)#5

Merged
mmcky merged 6 commits into
mainfrom
qep-1-versioned-living-standards
Jun 26, 2026
Merged

QEP-1 v1: version-defined living standards (amend in place, not supersede)#5
mmcky merged 6 commits into
mainfrom
qep-1-versioned-living-standards

Conversation

@mmcky

@mmcky mmcky commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Implements the design discussed and agreed with @jstac in #4.

What this does

Today QEP-1 freezes a QEP the moment it is Accepted — the only way to change it is to supersede the whole document with a new one. That suits a one-shot decision, but not a living standard (the label policy, a future style guide, this process itself) that evolves in small, frequent steps.

This PR lets an accepted QEP be amended in place under the same lazy-consensus review. The first substantive change after acceptance gives the QEP a version — v1, then v2, and so on — and routine evolution bumps that number instead of spawning a superseding document. A reader always sees one current standard rather than a chain of replacements.

What deliberately stays the same:

  • status still answers only was this agreed? No new status word is added — living-ness rides on version instead. This sidesteps PEP's Active and the MEP clash where Active means the opposite (frozen for voting).
  • Superseded is kept, but only for a genuine wholesale rethink — never routine maintenance.

Two supporting changes come along for the ride:

  • type becomes a minimal content taxonomy: standard / process / informational.
  • The change record is git itself — surfaced on the rendered site by the theme's git-history feature — rather than a hand-maintained changelog that would drift.

Adopting this is QEP-1's own first substantive amendment, so QEP-1 becomes v1: the document is a worked example of the mechanism it introduces.

How a revision is anchored

Each QEP's version is paired with a version-hash key in the frontmatter that pins the revision to an exact commit:

version: 1
version-hash: a1b2c3d  # stamped by CI; do not edit

version-hash is a real key, not a YAML comment, so a standard parser keeps it (a comment would be silently dropped). CI stamps it at merge — a commit cannot contain its own hash — so authors never write it by hand. Tooling that pins a standard reads version and verifies against version-hash.

Substantive vs editorial

This is an author/reviewer call — CI does not classify it — and it decides whether the number moves:

  • Substantive — any change to normative content (a rule, a value, a table row, a machine-readable appendix) → bump version; commit QEP-N vM: ….
  • Editorial — no change to normative content (typo, wording, formatting, link) → version unchanged; commit QEP-N: ….

Changes

Area Change
qeps/qep-0001-purpose-and-process.md The amendment itself: the version field (implicit v0) and its version-hash anchor, the substantive/editorial rule, amend-in-place vs supersede, squash-merge-only, QEP-N vM: commit subjects, the standard/process/informational type taxonomy, an Automation section, and git-as-history. Sets type: process, version: 1.
qeps/template.md Lowercase type enum; a comment noting version (and its CI-stamped version-hash) appears on first amendment, while a new QEP stays unversioned (v0).
README.md Type and Version columns added to the index; QEP-1 row → process / v1.
AGENTS.md Author-side operational guidance: substantive vs editorial, the version bump, commit subjects, squash-merge, and what CI does vs what the author does.
.github/scripts/ qeps.mjs (frontmatter + README-table parsing), stamp.mjs (post-merge: write version-hash, sync README), check.mjs (per-PR checks). All version parsing is scoped to the frontmatter block, so a version: example in a QEP body is never mistaken for the real field.
.github/workflows/ stamp-version.yml (post-merge: stamp the merged hash, sync README) and qep-checks.yml (per-PR: version increments by at most one, README↔frontmatter parity).

⚠️ One manual step required

The model assumes squash-merge only (one amendment = one commit). That is a repository setting CI cannot apply. After this merges, please set in Settings → General → Pull Requests: enable Allow squash merging, and disable Allow merge commits and Allow rebase merging. I left the live repo settings untouched, as agreed.

Validation

  • The per-PR check (check.mjs) passes locally, and both workflow YAMLs parse.
  • The stamp/parse logic — including the new version-hash key — is covered by round-trip checks: first-stamp insertion, re-stamp replacement, idempotence, and confirming a version: / version-hash: example inside a QEP body is never rewritten.
  • The original v1 design was put through an adversarial multi-agent review against Process: version-defined living standards for continuously-maintained QEPs #4 (faithfulness, cross-file consistency, CI/script correctness, rendering); confirmed findings are folded in.

Decision

Per QEP-1, this is decided by lazy consensus at a deadline. Suggested comment window: two weeks (by 2026-07-08) — adjust as you like.

Amend QEP-1 so a QEP gains a `version` the first time it is substantively
changed after acceptance, rather than being superseded wholesale for every
small change. Living standards (the label policy, a style guide, this process
itself) are now amended in place under the same lazy-consensus review.

- QEP-1: add the `version` field (implicit v0, git-hash anchor stamped at
  merge), the substantive-vs-editorial granularity rule, amend-in-place vs
  supersede, squash-merge-only, the `QEP-N vM:` / `QEP-N:` commit-subject
  convention, the `standard`/`process`/`informational` content taxonomy for
  `type`, and git (surfaced on the site) as the change record. QEP-1's own
  first substantive amendment, so it becomes v1.
- template.md: lowercase `type` enum; note that `version` appears on first
  amendment.
- README: add `Type` and `Version` columns; QEP-1 -> process / v1.
- AGENTS.md: author-side guidance (substantive vs editorial, version bump,
  commit subjects, squash-merge) and what CI does.
- CI: post-merge `stamp-version.yml` stamps the merged hash into
  `version: N  # <hash>` and syncs the README columns; `qep-checks.yml`
  enforces version-increment and README<->frontmatter parity. Logic in
  `.github/scripts/`.

Implements the design agreed in #4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 24, 2026 05:20

This comment was marked as outdated.

@jstac

jstac commented Jun 24, 2026

Copy link
Copy Markdown

I fully agree with this. Many thanks @mmcky , very good call.

My only complaint is that the "What this does" section reads as Claude mumbo-jumbo that a really clear sequence of rules / steps. Maybe Claude could have another go with the aim of improving clarity --- make it like an algorithm / proof, not a word salad.

@mmcky

mmcky commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @jstac -- sorry this was supposed to be in DRAFT still (not ready for review). But I will tidy up the language, check the new automation code and then will merge based on the principle we want to capture.

mmcky and others added 4 commits June 25, 2026 10:31
Move the git anchor from the `version: N  # <hash>` comment to a sibling
`version-hash` frontmatter key. A YAML comment is non-semantic and silently
dropped by any parser; a real key is preserved as data and parseQep now throws
on a malformed value. `version` stays a plain scalar, so the version pill and
the increment check read it untouched.

Scope all version parsing/stamping to the frontmatter block: a QEP body may now
contain a literal version:/version-hash: YAML example (QEP-1 does), which the
old whole-file regex in stamp.mjs/check.mjs would have matched by mistake.

Update QEP-1 text, the template, AGENTS.md, and the README to match. Stays at
v1 (no bump) since v1 is not yet merged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QEP-1's own principle is that the change record is git, not in-document. Make
the prose practise that: it should read as the current process, with the v0->v1
story left to git history. No normative content changes.

- Fold the status/version orthogonality and the Superseded clarification into
  the Lifecycle paragraph, dropping the redundant standalone paragraph (the
  Amending section already owns amend-in-place vs supersede).
- De-tense the Summary (drop "now at v1 ... introduces").
- Compress the version-hash rationale, dropping the framing relative to the
  rejected YAML-comment design.
- Remove the "(added at v1)" annotations from Alternatives considered.
- Fix a stale "git-hash anchor" -> "version-hash" in Rollout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gent

Address the PR review's must-fixes and cheap hardening.

- stamp.mjs (#1): detect a merge commit and diff against the first parent
  (`git diff --name-only HEAD^1 HEAD`) instead of `git diff-tree -r HEAD`, which
  prints nothing for a merge and would make stamping silently no-op if a QEP PR
  is merged as a merge commit rather than squashed. Verified on synthetic merge
  and squash commits.
- check.mjs (#2, #5): a new QEP must start unversioned (v0); a versioned QEP may
  not drop its version field (once versioned, stays versioned). Distinguishes a
  brand-new file from a v0 file via a base-existence check.
- check.mjs (#3): validate that type/status are known enum values.
- check.mjs (#8): tolerate a hand-typed ASCII "-" or empty README Version cell
  for a v0 QEP (stamp.mjs normalises it to the en dash post-merge), so the cell
  no longer blocks the PR with a visually identical-looking parity error.
- stamp-version.yml (#6b): push explicitly with `git push origin HEAD:main`.
- QEP-1 + AGENTS.md (#7): the site uses book-theme today, so make the type/version
  pills and git-history-dropdown claims contingent on the QuantEcon theme; lead
  with the always-true README columns. Describe the strengthened PR checks.

All eight check.mjs scenarios pass in an isolated synthetic repo; check.mjs
passes on the real tree. Stays at v1 (no normative rule changed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These are local review notes, not part of the QEP record; gitignore the
pr-*-review.md pattern so they can't be re-added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky mmcky requested a review from Copilot June 25, 2026 01:28
@mmcky

mmcky commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review in 791af8c (and untracked the stray pr-5-review.md working notes):

All eight check.mjs scenarios pass in an isolated synthetic repo, and check.mjs passes on the branch.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Comment thread .github/scripts/check.mjs
check.mjs guarded the presence of the Type and Version columns but not Status,
so a removed/renamed Status header would make the status-parity check silently
no-op (expect() early-returns when the column index is -1). Add the matching
presence check (Copilot review comment). Verified with a scenario test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky mmcky merged commit 0bff77a into main Jun 26, 2026
1 check passed
@mmcky mmcky deleted the qep-1-versioned-living-standards branch June 26, 2026 02:25
mmcky added a commit that referenced this pull request Jun 26, 2026
## Problem

The **Deploy QEP site** workflow has failed on every push to `main`
since the QEP-1 v1 merge (#5) — including the merge commit's own deploy,
so the amended QEP-1 (the new `Type`/`Version` columns and pills)
**never reached the live site**. The last successful deploy was #1 on
2026-06-16.

The failure is unrelated to #5. `myst build --html` serves all pages
fine, then fails resolving the favicon. With no favicon configured, MyST
falls back to fetching the default from
`https://mystmd.org/favicon.ico`. On the runner's Node 24.17.0 that
remote fetch dies with `ERR_STREAM_PREMATURE_CLOSE` (a recent
`http.Agent` keep-alive change), retries 3× and aborts the build:

```
Failed to fetch http://localhost:3000/favicon.ico after 3 attempts.
##[error]Process completed with exit code 1.
```

## Fix

Vendor QuantEcon's favicon into the repo and reference it locally, so
the static build never reaches the network for it:

```yaml
site:
  options:
    favicon: favicon.png
```

## Verification

Built locally on Node 26 with the change:

| Check | Result |
|---|---|
| `myst build --html` exit code | `0` |
| Build errors | none |
| Emitted `_build/html/favicon.ico` | byte-identical to the vendored
source |
| `mystmd.org/favicon` references in output | none |
| HTML `<head>` | `<link rel="icon" href="/favicon.ico"/>` |

Once merged, the push to `main` re-triggers the deploy, which should now
go green and publish the amended QEP-1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

3 participants