Skip to content

Doc change management — the protocol

Audience: anyone editing files in architecture/, guides/, proposals/, or reports/. Goal: every change is traceable, the docs always reflect what the code actually does, and a newcomer can follow the trail to understand both what the system does today and why it ended up that way.

This file is the rationale + how-to. The operative rules live in CLAUDE.md §"Doc change protocol" and the per-section conventions in PrInstructions.md. If you're impatient, skip to the checklist at the bottom.


The three doc types

We use three kinds of document, each with a job.

Type Where Job
Living Spec architecture/specs/<domain>.md Describes one product or system domain as it is today. There's one per domain (AMM pricing, auth flow, referral, ...). Rewritten in place when behavior changes — never branched into versions.
ADR (Architecture Decision Record) architecture/decisions/NNNN-<slug>.md Captures one decision that shaped the system — what we chose, why, what we rejected. Append-only; once status: accepted, only status and superseded-by may change.
Per-commit walkthrough pr-reports/<branch>-<sha>.html Auto-generated HTML, one per Claude-authored commit, written for a 1st-year intern. Captures the what of every commit. Spec + ADR captures the why and the current truth. See PrInstructions.md.

The three are complementary:

What the system does today      → Living Spec
Why we decided to do it that way → ADR
What this specific commit did    → pr-reports walkthrough
What's been decided but not built → PENDING.md (index over ADRs with implementation-status: pending)

A reader who lands cold on the repo should be able to:

  • Open architecture/specs/amm-pricing.md and learn how the AMM works today.
  • Follow the decisions: list at the top of that spec to the ADRs and read why.
  • Open pr-reports/index.html and see every recent change with intern-friendly explanations.
  • Open PENDING.md and see every feature that's been designed but not yet shipped (the build backlog).

Implementation status — the two-lifecycle model

ADRs carry TWO independent lifecycle fields:

Field Tracks Values
status: The decision lifecycle proposedaccepted → (superseded | rejected)
implementation-status: The code lifecycle pendingin-progressimplemented

Why both? Because a decision can be made (status: accepted) long before the code is written (implementation-status: pending). Forcing them onto one axis hides the gap. Two axes make the gap visible and answerable.

The common case: design-first, code-later

For any meaningful redesign:

  1. First PR (docs-only): ADR authored with status: accepted + implementation-status: pending. The ADR is rich enough to serve as the build guide for the eventual code PR. The ADR appears in PENDING.md. Specs that the ADR will affect are NOT rewritten yet — they still describe the live system. They may carry a small "pending redesign — see ADR-NNNN" banner.
  2. Paired code PR (later, possibly weeks later): code change + spec rewrite + ADR flipped to implementation-status: implemented + any superseded older ADRs updated. PENDING.md row moved to the "Recently shipped" section.

This pattern preserves the principle "spec = current truth" without losing the design intent — the intent lives in the ADR and surfaces via PENDING.md.

When to use each implementation-status value

  • pending — design accepted, no code yet. Default for any new ADR proposing a future change. Listed in PENDING.md.
  • in-progress — paired code PR is open, under review, or merging. Brief window.
  • implemented — code shipped; ADR matches live behavior. Stable end-state.

PENDING.md sync requirement

If an ADR has implementation-status: pending (or in-progress), it MUST have a row in PENDING.md. When the status flips to implemented, the row moves to PENDING.md's "Recently shipped" section in the same PR. This is hand-maintained — small enough to keep correct, easy to script later if it grows.


When to write what

Pure refactor, typo, dead-code removal — no docs change needed

Just commit. The pr-reports walkthrough captures the what. No spec or ADR.

Internal change with no public/behavioral effect — spec untouched

If you rewrite a function but it still does the same thing the spec describes, you don't need to touch the spec. Update the code-refs: line ranges in the spec if they shifted by more than ~20 lines.

Behavior change in a code path the spec describes — spec update + ADR

This is the most common reason to write an ADR. The flow is:

  1. Code change in the relevant ftl-* repo, on a feat/<slug> or bug/<slug> branch.
  2. Spec update — rewrite the affected section of architecture/specs/<domain>.md. Bump last-updated: to today. Adjust code-refs: if files moved.
  3. New ADR — copy architecture/decisions/_TEMPLATE.md to the next number. Fill in affects-specs: and affects-code: and the four required sections (Context, Decision, Consequences, Alternatives).
  4. Supersession, if applicable — if your decision invalidates an earlier ADR, set supersedes: on the new one and open the older one and set both status: superseded and superseded-by:.
  5. Index updates — add a row to architecture/decisions/INDEX.md (newest first). Update architecture/specs/INDEX.md if a spec changed status.

If the code and docs live in different repos, the two PRs should reference each other in their descriptions and merge together.

Whole new domain — new spec + foundational ADR(s)

  1. Copy architecture/specs/_TEMPLATE.md to <domain>.md and fill it in.
  2. Write at least one ADR (0001-<slug> for that domain — but globally numbered across all domains, not per-domain) capturing the foundational decision.
  3. Add rows to both INDEX files.

How the upcoming AMM logic change will land

This is the worked example the system is designed for. When the user dictates an AMM change:

  1. New branch in ftl-backend: feat/amm-<specific-change>.
  2. Code edit (4 mirroring files — see architecture/specs/amm-pricing.md Code References).
  3. Paired branch in ftl-docs with the same name. On it:
  4. Edit architecture/specs/amm-pricing.md — rewrite the affected section, bump last-updated:, fix any drifted code-refs.
  5. Add architecture/decisions/0004-<slug>.md.
  6. Update architecture/decisions/INDEX.md.
  7. If the change invalidates ADR-0001's formula choice, also update 0001's frontmatter to status: superseded and superseded-by: 0004, and set supersedes: 0001 on 0004.
  8. Open both PRs. Reference each from the other.
  9. Merge together.

That's the full loop. Three files in ftl-docs, one focused commit in ftl-backend, fully traceable from either direction.


Why this format and not

Alternative we considered Why we passed
Wiki + freeform docs No structure means no enforceability. Within a few months, half the docs are stale and you can't tell which half.
Per-PR changelog entries Captures the what, not the why. Already covered by pr-reports/ walkthroughs.
Versioned spec docs (amm-v1.md, amm-v2.md) The version most readers want is "what does it do RIGHT NOW". Versioning forces them to figure out which file is current. Living spec + dated ADRs gives both views without the lookup.
Issue tracker as decision log Issues get archived, renamed, moved between projects. We want decisions to live in the code repo with the code they govern.

Quick checklist (run through this before opening a docs PR)

  • [ ] Branch named feat/<slug> or bug/<slug> per CLAUDE.md §"Branch hygiene".
  • [ ] If the PR changes behavior described in any spec, the affected spec was updated.
  • [ ] If the PR documents a decision, an ADR was added with all four sections filled in.
  • [ ] New ADR's implementation-status: is set (pending if no code yet; implemented if this PR ships code that matches the ADR).
  • [ ] If implementation-status: pending, the ADR has a row in PENDING.md.
  • [ ] If this PR flips an ADR from pendingimplemented, the PENDING.md row moves to "Recently shipped" in the same PR.
  • [ ] If superseding an older ADR: both files updated (supersedes: on new, superseded-by:
    • status: superseded on old). Only do this when the code that enacts the supersession is shipping in the same PR — otherwise the older ADR still describes live behavior.
  • [ ] architecture/decisions/INDEX.md row added/updated.
  • [ ] architecture/specs/INDEX.md row updated if spec status changed.
  • [ ] Spec's last-updated: reflects today's date.
  • [ ] Spec's code-refs: paths still exist.
  • [ ] PR description includes Author: @<github-handle> per the branch-hygiene rule.
  • [ ] If the matching code PR lives in another repo, both PRs reference each other.

If any box is unchecked, the PR isn't ready to merge.