# 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`](rfc-template.md) · [`adr-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

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](https://gitlab.in2p3.fr/satinv/cif/-/blob/devel/.gitlab/merge_request_templates/RFC.md)).
   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](0001-dask-threaded-scheduler.md) |
| 0002 | ADR  | Boolean mapper flags for sparse/sampled transform interface | Accepted | historical | [0002-sparse-sampled-mapper-flags.md](0002-sparse-sampled-mapper-flags.md) |
| 0004 | ADR  | Framework-agnostic ML surrogate wrapper               | Accepted | 2026-06   | [0004-ml-surrogate-wrapper.md](0004-ml-surrogate-wrapper.md) |
| 0005 | ADR  | CeCILL-C license for CIF                               | Accepted | 2018      | [0005-cecill-c-license.md](0005-cecill-c-license.md) |
| 0006 | ADR  | Plugin registry architecture (`Plugin` base class)    | Accepted | historical | [0006-plugin-registry-architecture.md](0006-plugin-registry-architecture.md) |
| 0007 | ADR  | `platform` plugin for HPC portability                 | Accepted | historical | [0007-platform-plugin-hpc-portability.md](0007-platform-plugin-hpc-portability.md) |
| 0008 | ADR  | JSON Schema for YAML config generated from plugin metadata | Accepted | historical | [0008-json-schema-generation.md](0008-json-schema-generation.md) |
| 0009 | ADR  | `obsvect`/`controlvect` derive content from `datavect` | Accepted | historical | [0009-obs-control-vect-from-datavect.md](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](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.*

```{toctree}
:hidden:

rfc-template
adr-template
0001-dask-threaded-scheduler
0002-sparse-sampled-mapper-flags
0004-ml-surrogate-wrapper
0005-cecill-c-license
0006-plugin-registry-architecture
0007-platform-plugin-hpc-portability
0008-json-schema-generation
0009-obs-control-vect-from-datavect
0010-decouple-vector-transforms-from-datavect
```
