Define the in-file RFC contract in docs/rfc/README.md § The file format: the header block (`# RFC: <title>` plus a dateless Status enum cross-checked against the lifecycle folder), the per-lifecycle body skeleton (a Problem opener everywhere; Proposal/Alternatives considered/ Acceptance criteria/Risks in proposed/; present-tense Decision/ Consequences with proposal-era headings banned in implemented/; the frozen proposal shape in rejected/), and a mandatory Alternatives considered section with a date-fenced grandfather comment for pre-format RFCs whose alternatives are not reconstructible from the record. Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the enum, 29 Context openers become Problem, the 39 legacy-format XXX debt markers are resolved and banned from reappearing, proposal-era sections in implemented RFCs are rewritten to shipped reality (including the web/fs/subagent seam RFCs' migration plans and test checklists, closing the doc-tiers deferred-work item on the web seam), every RFC gains an Alternatives considered section or the grandfather comment, and the bilingual pair is re-mirrored and re-recorded. Move the generated index tables out of README.md into a fully generated docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and verify-rfc-classification checks its freshness and rejects index-shaped rows in the curated README — which makes room for the format contract to live in the README front door instead of a separate FORMAT.md. The decision record, and the first RFC written in the new format, is docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
5.2 KiB
RFC: One gated in-file format for RFCs
Status: implemented
Problem
The tree's layout is uniform — the classification scheme path-encodes lifecycle and class and gates both — but the file insides never were. The corpus the format decision faced had two H1 spellings; some fifteen Status: line spellings, from bare enums through dated parentheticals duplicating what the filename and git already carry, to four files with no status at all; two body genres side by side (ADR-style Context/Decision/Consequences beside proposal-style Problem/Proposal/Risks), so every new RFC guessed its shape from whichever neighbor its author opened; thirty-nine files carrying a debt comment that flagged them as "legacy ADR/RFC body format" awaiting a unified template that was never actually defined; and over twenty implemented RFCs still carrying the proposal-era headings (Acceptance criteria, Plan, Migration plan) that the documentation standard's slop checklist outlaws for implemented/ — outlawed, but enforced by nothing, so the proposed/ → implemented/ move could silently skip the rewrite implemented/AGENTS.md requires.
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 four newest pre-format RFCs 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 over twenty 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.