Engineering RFC and Design Doc Template
Three statuses cannot tell an unanswered objection from a decision nobody recorded. Eleven states, a decision date, and the count of comments nobody disposed of.
Free download · No account needed
Search for an RFC template and you get nine headings and three statuses: draft, in review, approved. The headings are fine. The statuses are why proposals fail. Under three values, a proposal rejected on purpose, a proposal parked until next year, a proposal accepted eight months ago that nobody is building, and a proposal waiting on one colleague's unanswered objection all render as the same word. Nobody skips the format. What gets skipped is finishing the decision.
A status is three separate facts. Which state the proposal is in, from a vocabulary that tells a decided no apart from a parked maybe. When it stops being in that state, which is a date no template has. And whether an objection against it is unanswered, which is a count. Real processes carry all three. Kubernetes puts the state in a machine-readable file with seven permitted values, beside a field for the last milestone in which work actually happened.
This pack carries eleven states, a decision date on every row, and a comment log joined to the register rather than filed beside it. Kestrel Data's worked register holds 18 proposals. Five are live, and a three-state view calls all five in review; three are past their own decision date, one by 119 days. Six blocking comments sit open across four proposals. The oldest is 335 days old, nobody argued with it, and the engineer who raised it is still here.
What comes in the pack
RFC Register, with the header as written beside the state that is true
One row per proposal. Status On Document holds what the document's own header literally says, transcribed. Status holds the normalised state from the eleven-value vocabulary. They disagree on nine of the eighteen worked rows and the disagreement always flatters: Accepted on something that shipped last autumn, In Review on something nobody has opened since spring, Draft on a document four people have reviewed. Four more rows differ only in wording, and the sheet counts those separately because they are a different problem.
A decision date on every row, and the days past it
In Review with no end is where proposals go to die, and no RFC template has this field. Set it when you open the document, two weeks inside a team and six across three, and name one decider rather than a group, because a committee cannot be past due. Three of the five live rows in the worked register are past theirs, by 3 days, 41 days and 119 days, and the register says so on the row rather than in somebody's memory.
Comment Resolution Log, joined to the register
One row per comment, each with a class and a disposition. Blocking, non-blocking or question, decided by the reviewer before they type. Then one of six dispositions, each requiring a person, a date and where the resolution was written down. The register's blocking count is a join against this sheet, not a field, so it cannot drift from the thing it counts.
Review Sign-off that records depth, not just position
Signed off, not looked, blocked, or ruled not required with the ruling attached, so an empty row is a decision instead of a gap. Beside it, whether the reviewer read the proposal in full or skimmed it. That is not an accusation: Rust's own process notes the sign-off gate before a final comment period is often where reviewers first read a proposal in full depth. Recording the skim is what makes the fraction honest.
A rule that stops a state advancing over an open objection
While a proposal has a blocking comment with no disposition, its state cannot move toward a decision. It can move away from one, because Deferred and Withdrawn are honest descriptions of a stalled proposal. The worked register ships one row breaking the rule on purpose, and keeps it visible rather than tidying it away, because a register that cannot show a violation hides the thing you needed it for.
A monthly sweep, and the decision record it feeds
Four passes: past their date, gone silent, accepted and unbuilt, header disagrees with the register. It reports arithmetic before it reports a list, and it proposes state changes rather than applying them, because the name in Status Set By has to belong to somebody who chose it. Every resolution writes an entry naming what was decided, who decided it, and where, which is what an architecture decision record starts from. Thirteen of the eighteen worked proposals have one.
How it works
- 1
Send whatever you already have
A proposal you are drafting, a problem you want to change, or the folder somebody started two years ago. River reads the review threads too, because that is where the unanswered objections are. It pulls constraints from an architecture document, or reconstructs one when the system was never written down.
- 2
Every proposal gets a state, a date and a count
The header is transcribed, then the state is set from evidence rather than from the header. Live rows get a decision date. Every substantive concern in the threads becomes a classified comment, and the blocking count follows.
- 3
The first report is arithmetic
How many proposals your team calls in review, how many are past a date, how many blocking comments are open and what their proposals' statuses claim. Then the three rows that need a person today, each with the reason.
- 4
Draft, review and resolve inside the register
Five prompts open a proposal, review one from a named role, dispose of a comment, sweep the stalled rows and record the decision. Each recomputes the counts, so the sheets never drift. A resolved proposal is the agreed design a threat model reads, and the change it authorises is what a migration plan sequences.
Frequently asked questions
Why eleven states instead of three?
Because the four things three values cannot tell apart are the four things that go wrong. Every state here is one a shipped process carries. Kubernetes' own proposal metadata permits seven values in a machine-readable file, and separating Deferred from Postponed matters because one means nobody is working on this and the other means we chose to wait.
Is Deferred just a polite rejection?
No, and the distinction is documented. Python's process defines the status by one test: an author or editor assigns it when no progress is being made. It is a fact about the document, not a verdict on the idea. Applying it is a kindness, because the alternative is a register claiming five things are in review when two of them are not.
What stops an accepted RFC from never getting built?
Two columns and a sweep pass. Accepted rows carry an implementation owner and a tracker, and blank ones are reported. Rust's process makes the tracking issue mandatory for the same reason, and settles ownership too, since the author of a proposal is not obliged to implement it. Two of the eighteen worked rows are accepted with nobody named.
How does the blocking count actually work?
It is a join, recomputed rather than typed. It counts the comment log's rows against that proposal whose class is blocking and whose disposition is Open. Six are open in the worked register, and the four proposals holding them read In Review, In Review, Final Comment Period and Deferred. That is the field which separates a proposal stalled by disagreement from one stalled by indifference.
Does this replace an architecture decision record?
No. An architecture decision record holds a decision already taken and answers whether it is still in force. This pack holds the interval before that, when the proposal is still being argued about. Choosing between named candidate technologies first is a separate evaluation, in the technology selection pack.
Why record that a reviewer only skimmed it?
Because a sign-off given without a full read is a normal event, and pretending otherwise makes the whole sheet worthless. One worked row is marked skimmed at the reviewer's own request. Depth also explains the failure mode where somebody signs off, the migration section lands afterwards, and their blocking comment arrives three days later.
What if my team's proposals are scattered across a wiki?
That is the normal starting state and the first pass handles it. River builds the register from whatever it finds, transcribes each header, sets the real state from evidence, and classifies the old review threads into comments. Expect the two status columns to disagree on about half the rows, and expect the direction to be flattering.
Find out which of your RFCs is dying
Send the folder. River puts every proposal on the register with its real state, its decision date, and the objections nobody answered.
Install the RFC pack