CIF Design Decisions — RFC & ADR#
One log, one place to look. We record load-bearing design decisions as short documents so the reasoning is externalised instead of living only in someone’s head. There are two kinds of record, sharing one number space and one index.
Templates: rfc-template.md · adr-template.md
Two kinds of record#
RFC (Request for Comments) — decide before building something hard to reverse. Full template, reviewed before significant code. On acceptance it also stands as the permanent decision record.
ADR (Architecture Decision Record) — record a decision already made (in a merge request, a meeting, or historically) whose reasoning a future maintainer needs. Short template, born
Accepted.An accepted RFC needs no separate ADR — it already is one; it appears in the index with Kind = RFC.
Which one do I write? (routing)#
Not yet decided, and one-way-door / numerics / interface / cross-cutting → RFC.
Already decided, load-bearing, and non-obvious → ADR.
Reversible, local, obvious → neither — just open a merge request.
The RFC bar is the one-way-door test below. The ADR bar is different: would a future maintainer waste time, or accidentally break something, if this reasoning weren’t written down? If yes, write the ADR.
When an RFC is required#
Use the one-way-door test: write an RFC when the change is hard to reverse, or when it forces you to reason about parts of the system you don’t personally own.
Requires an RFC:
A change to a plugin interface (transport model, observation operator, minimizer, I/O) — i.e. the contract, not one implementation behind it.
Anything that changes numerical results of a reference configuration.
Anything affecting adjoint / tangent-linear consistency or gradient correctness.
A new heavy or cross-cutting dependency (e.g. JAX, a new autodiff or DA backend).
A change to the user-facing config / YAML contract or to on-disk data formats.
A cross-cutting architectural change (multi-GPU decomposition, surrogate integration, master-loop restructuring).
Does NOT require an RFC (just open a merge request):
Bug fixes that preserve behaviour.
A new plugin that conforms to an existing interface.
Documentation, tests, CI, refactors internal to one component with no interface change.
Performance tuning that leaves results and interfaces unchanged.
If in doubt, write a 1-paragraph “mini-RFC” (Summary + Motivation only) and ask in the contributor meeting whether it needs the full treatment. Cheap to escalate, expensive to over-formalise.
RFC lifecycle#
Draft — Author copies
rfc-template.mdtoNNNN-short-title.md, fills it, opens a merge request labelledrfc(use theRFCmerge request template). The MR is the review thread.Review — At least one advanced contributor (other than the author) must review. For changes touching numerics/adjoint, the reviewer set must include someone who can judge that. Reviewers push on Context, Alternatives, and Invariants — not just the proposed design. Target: substantive feedback within ~1 week; don’t let RFCs rot.
Decision — The maintainer (or a delegated area owner) records the decision: Accepted / Accepted-with-changes / Rejected / Deferred. Rejections are kept, not deleted — a documented “no, because…” is as valuable as a yes.
Freeze as record — On acceptance, set status to
Accepted, stop editing the design sections, and the document stands as the permanent record. Later reversals get a new RFC that links back and supersedes it.
ADR lifecycle#
Born
Accepted. An ADR documents a decision that already holds, so there is no pre-implementation review gate. (A backfilled ADR may sit atDraftuntil its author verifies the reconstructed reasoning, then flips it toAccepted.)Immutable once accepted. Do not edit the Context/Decision of an accepted ADR. If the decision changes, write a new record (RFC or ADR) that Supersedes it, and mark the old one Superseded by.
Decidedmay be approximate for historical decisions — a year, or “historical”.
Roles (both kinds)#
Author — writes the record, owns the deliberation, implements or delegates implementation.
Reviewer(s) — for RFCs, ≥1 advanced contributor; for numerics-affecting RFCs, ≥1 with the relevant scientific competence. Their job is to reconstruct the author’s mental model well enough to challenge it.
Decider — maintainer or delegated subsystem owner. Records the outcome. (Deliberately handing “decider” for a subsystem to an advanced contributor is itself a theory-transfer exercise.)
Writing a new record#
Allocate the next number (see Numbering).
RFC:
cp rfc-template.md NNNN-short-title.md. ADR:cp adr-template.md NNNN-short-title.md.Fill it in, add a row to the Index, open a merge request labelled
rfcoradr.
Numbering#
A single shared 4-digit sequence across both kinds — one chronological log. Allocate the
next unused integer; it is permanent once merged. If two drafts grab the same number
concurrently, whichever merges second renumbers before merge. Numbers are identifiers, not dates:
a backfilled ADR carries a low number but its Decided field holds the real (possibly historical)
date. Templates are un-numbered (rfc-template.md, adr-template.md).
Deliberately keeping it light#
No record should take more than a couple of hours to draft. If a section doesn’t apply, write “N/A” — don’t pad.
The template is a checklist against blind spots, not a form to satisfy. The value is in the thinking it forces — for RFCs especially Context, Alternatives, and Invariants; for ADRs, the Consequences that state what the decision now forbids.
What gets published where#
Decided records (any status except
Draft) are published on the CIF documentation site as the design-decision record. Rejected and Deferred RFCs stay — the documented “no / not yet” is part of the record.Drafts — RFC drafts (in their MR) and backfilled ADRs awaiting verification (
Status: Draft) — are not published. Because the site builds from the default branch only, a draft on an MR branch never appears until it is decided and merged.Decision records do not appear anywhere in the on-demand inversion platform.
Current caveat: the
pagesjob in.gitlab-ci.ymlalso publishes fromdevelandclaude, not just the default branch (main). Until that’s tightened, don’t commit aDraft-status record directly todevelorclaudeoutside of its own MR branch — it would go live before a decision is recorded. This applies to the five backfilled ADR drafts below just as much as to RFC drafts.
Index#
# |
Kind |
Title |
Status |
Decided |
Link |
|---|---|---|---|---|---|
0001 |
ADR |
Dask threaded scheduler for pipeline execution |
Accepted |
historical |
|
0002 |
ADR |
Boolean mapper flags for sparse/sampled transform interface |
Accepted |
historical |
|
0004 |
ADR |
Framework-agnostic ML surrogate wrapper |
Accepted |
2026-06 |
|
0005 |
ADR |
CeCILL-C license for CIF |
Accepted |
2018 |
|
0006 |
ADR |
Plugin registry architecture ( |
Accepted |
historical |
|
0007 |
ADR |
|
Accepted |
historical |
|
0008 |
ADR |
JSON Schema for YAML config generated from plugin metadata |
Accepted |
historical |
|
0009 |
ADR |
|
Accepted |
historical |
|
0010 |
RFC |
Decouple vector transforms from |
Draft |
— |
Add a row when you open a draft; update Status and Decided when the decision is recorded. In-flight rows may point to the MR; merged rows point to the file.