4.4 KiB
RFC: One gated in-file format for RFCs
Status: implemented
Problem
RFC paths encoded lifecycle and class, but file contents still mixed headings, status formats, ADR and proposal templates, and proposal-era sections in implemented records. Authors copied whichever neighbor they found, and lifecycle moves could skip the required rewrite because no gate enforced an in-file contract.
Decision
README.md § The file format is the in-file contract — the header block (# RFC: <title> plus a dateless, folder-agreeing Status: enum whose only content is the rejection reason), the per-lifecycle body skeleton (Problem opener everywhere; Proposal/Acceptance criteria/Risks in proposed/; present-tense Decision/Consequences with proposal-era headings banned in implemented/; frozen proposal shape in rejected/), a mandatory Alternatives considered section, and the canonical section vocabulary between which bespoke technical sections stay free-form. pnpm run verify-rfc-format (scripts/verify-rfc-format.ts) enforces every mechanical clause as part of doc-sync, so a lifecycle move that skips its rewrite now fails CI instead of review memory.
The whole corpus was normalized in the same change that defined the format — the pre-release stance: no transition period, no dual-format tolerance. The one grandfather is content, not format: alternatives are recorded, never invented, so a pre-format RFC whose alternatives are not reconstructible from the record carries the exact rfc-format: alternatives-not-recorded comment, which the gate accepts only for files dated before this RFC.
Alternatives considered
- A full rigid template (one fixed section sequence per lifecycle, every RFC restructured to fit) — rejected: the big design RFCs carry eight to fifteen bespoke technical sections (package topology, wire contracts, schemas) that are load-bearing content, not drift; a rigid sequence would force destructive rewrites now and template-fighting forever.
- Header-only normalization (H1 and Status, bodies untouched) — rejected: the debt markers flagged the body genre split, and leaving
Context/DecisionbesideProblem/Proposalindefinitely resolves nothing. - No Status line (the folder already is the status; the three newest pre-format RFCs (and the zh counterpart of one) omitted the line) — rejected in favor of keeping a self-describing file: the drift risk that motivated dropping it is neutralized by gating the line against the folder instead.
- Dated status (
Status: implemented (accepted YYYY-MM-DD)) — rejected: the acceptance date is narrated history the writing rules keep out of docs; the filename carries first-proposed, git carries the rest, and the gate could check a date's format but never its truth. - A bare
# <title>H1 — rejected: theRFC:prefix is the corpus-majority form and self-describes the genre when a file is read outside its tree; the index generator strips it, so index rows are identical either way. ## What we give upas the implemented closer (the README's own phrase for what an RFC records) — rejected: it names only costs, and an honest consequences section records what the trade-off bought as well.- Convention without a gate (write the contract down, enforce by review) — rejected: the slop checklist already outlawed spec-speak in
implemented/by convention, and nineteen files show what convention alone achieves here. - A standalone
FORMAT.mdcontract file — the first landed home; folded into README.md once the generated index moved out to INDEX.md: with the tables gone the README regained the room, and one front door carrying layout, classification, and format beats splitting the contract across two files.
Consequences
Every RFC now costs slightly more structure, and the mandatory Alternatives considered section is deliberate friction: a decision recorded without what it beat invites the re-litigation RFCs exist to prevent. Pre-format RFCs whose alternatives were not reconstructible carry the grandfather comment permanently — an honest gap on the record rather than fabricated rationale. doc-sync gains one gate, and moving an RFC between lifecycle folders is now real work at move time (the body rewrite the move always owed) instead of deferred cleanup nothing tracked. The thirty-nine debt markers are gone, resolved by the template they were waiting for.