Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ 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.

## Decision index

| ADR | Status | Decision |
Expand Down
42 changes: 42 additions & 0 deletions tests/test_adr_number_allocation_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""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"
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"):
match = NUMBERED_ADR_RE.fullmatch(path.name)
if match is None or match.group(1) == "0000":
continue
by_number[match.group(1)].append(path.name)

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",
"ordinary-forward repair",
"Do not force-rewrite history",
"branch-only proposal Accepted",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
):
assert requirement in index
Loading