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-cuttingRFC.

  • 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#

  1. Draft — Author copies rfc-template.md to NNNN-short-title.md, fills it, opens a merge request labelled rfc (use the RFC merge request template). The MR is the review thread.

  2. 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.

  3. 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.

  4. 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 at Draft until its author verifies the reconstructed reasoning, then flips it to Accepted.)

  • 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.

  • Decided may 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#

  1. Allocate the next number (see Numbering).

  2. RFC: cp rfc-template.md NNNN-short-title.md. ADR: cp adr-template.md NNNN-short-title.md.

  3. Fill it in, add a row to the Index, open a merge request labelled rfc or adr.

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 pages job in .gitlab-ci.yml also publishes from devel and claude, not just the default branch (main). Until that’s tightened, don’t commit a Draft-status record directly to devel or claude outside 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

0001-dask-threaded-scheduler.md

0002

ADR

Boolean mapper flags for sparse/sampled transform interface

Accepted

historical

0002-sparse-sampled-mapper-flags.md

0004

ADR

Framework-agnostic ML surrogate wrapper

Accepted

2026-06

0004-ml-surrogate-wrapper.md

0005

ADR

CeCILL-C license for CIF

Accepted

2018

0005-cecill-c-license.md

0006

ADR

Plugin registry architecture (Plugin base class)

Accepted

historical

0006-plugin-registry-architecture.md

0007

ADR

platform plugin for HPC portability

Accepted

historical

0007-platform-plugin-hpc-portability.md

0008

ADR

JSON Schema for YAML config generated from plugin metadata

Accepted

historical

0008-json-schema-generation.md

0009

ADR

obsvect/controlvect derive content from datavect

Accepted

historical

0009-obs-control-vect-from-datavect.md

0010

RFC

Decouple vector transforms from datavect (sparse controlvect, super-obs)

Draft

0010-decouple-vector-transforms-from-datavect.md

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.