Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ Template for new material decisions: `docs/adr/0000-template.md`. The template i

A conversation, issue, PR body, design note, or paper summary is not an Accepted decision by itself. Accepted ADRs must match current code/policy or explicitly describe an accepted invariant whose implementation is tracked.

## Number allocation and active reservations

ADR numbers are monotonic decision identities, not disposable branch-local labels. Before publishing a new numbered ADR, the writer must inventory both protected `docs/adr/` and every still-valid active PR reservation. Looking only at the highest number on protected main is insufficient because Draft and Proposed branches reserve identities before integration.

When concurrent, unprotected proposals collide on one number, preserve the earliest-created still-valid proposal's reservation and assign later colliders the next free numbers in PR creation order. The free-number search includes protected ADRs and all live reservations. A stacked proposal keeps its reservation until it is protected-integrated or explicitly retired with no surviving decision delta.

Renumbering is an ordinary-forward repair. Preserve the decision content, tests, citations, review history, and maturity state; update the ADR filename, in-file identity, index, traceability/doctoring/changelog references, tests, and PR authority that encode the number. Do not force-rewrite history, close valid work solely because of a number collision, recycle an identity silently, or mark a branch-only proposal Accepted merely because its number was repaired.

The protected tree itself must contain at most one material ADR per four-digit number. The repository contract test enforces that local invariant; active-reservation coordination remains a live PR/issue governance responsibility because concurrent proposals do not coexist in one checkout before integration.

### Standards basis and local-policy scope

ISO/IEC/IEEE 42010:2022 requires architecture descriptions to record architecture decisions considered essential to the architecture and recommends that organizations/projects establish a decision-recording and sharing strategy with rationale. Nygard's original ADR proposal uses sequentially numbered records with explicit status and preserves superseded decisions instead of rewriting their history. These sources support keeping stable decision identities, maturity, rationale, and non-destructive lineage. They do **not** prescribe GitHub PR reservation semantics or a collision winner.

The rule above that preserves the earliest-created still-valid reservation and assigns later colliders the next free numbers in PR creation order is therefore a repository-local concurrency policy. It is chosen because active proposal branches cannot share one filesystem namespace before integration, while deterministic reservation order prevents two valid decisions from silently reusing one identity. Do not describe this allocation rule as mandated by ISO/IEC/IEEE 42010 or by Nygard.

References:

- International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). *ISO/IEC/IEEE 42010:2022, Software, systems and enterprise—Architecture description* (2nd ed.). https://www.iso.org/standard/74393.html
- Nygard, M. (2011, November 15). *Documenting architecture decisions*. Cognitect. https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions

## Decision index

| ADR | Status | Decision |
Expand Down
48 changes: 48 additions & 0 deletions tests/test_adr_number_allocation_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Contracts for monotonic Architecture Decision Record identities."""

from collections import defaultdict
from pathlib import Path
import re


ROOT = Path(__file__).resolve().parents[1]
ADR_DIR = ROOT / "docs" / "adr"
ADR_INDEX = ADR_DIR / "README.md"
ADR_TEMPLATE = "0000-template.md"
NUMBERED_ADR_RE = re.compile(r"^(\d{4})-.+\.md$")


def test_protected_tree_has_one_material_adr_per_number() -> None:
"""A four-digit ADR identity cannot name two material decisions in one tree."""
by_number: dict[str, list[str]] = defaultdict(list)
for path in ADR_DIR.glob("*.md"):
if path.name == ADR_TEMPLATE:
continue
match = NUMBERED_ADR_RE.fullmatch(path.name)
if match is None:
continue
by_number[match.group(1)].append(path.name)

assert "0000" not in by_number, "ADR 0000 is reserved for the non-live template"
duplicates = {
number: sorted(paths)
for number, paths in by_number.items()
if len(paths) > 1
}
assert duplicates == {}


def test_adr_index_requires_live_reservation_inventory_before_allocation() -> None:
"""Keep the concurrent-proposal reservation rule discoverable and fail-closed."""
index = ADR_INDEX.read_text(encoding="utf-8")
for requirement in (
"active PR reservation",
"Looking only at the highest number on protected main is insufficient",
"earliest-created still-valid proposal",
"later colliders the next free numbers in PR creation order",
"The free-number search includes protected ADRs and all live reservations",
"ordinary-forward repair",
"Do not force-rewrite history",
"branch-only proposal Accepted",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
):
assert requirement in index
Loading