Start the arc42 contract and publish the first ADR set

Add the owner-facing publication contract, a regenerable ADR review
ledger, and publication entries for this repo's ADR-0001 plus the eight
ready railiance-master ADRs.
This commit is contained in:
tegwick 2026-08-18 22:16:02 +02:00
parent 7a24e9107f
commit a8fb62a3bc
34 changed files with 9669 additions and 28 deletions

255
tools/adr_review_ledger.py Normal file
View file

@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""Build the ADR review ledger from source-inventory.json and source files."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
import sys
from typing import Any
from render import split_frontmatter
from source_inventory import _repo_paths, _read_json
BARE_ADR = re.compile(r"(?i)\bADR-0*(\d+)\b")
REQUIRED = ("id", "title", "status", "owner")
FIRST_PUBLISH = {
("policy-nexus", "docs/adr/ADR-0001-addressing-and-permanence.md"),
*(
(
"railiance-master",
f"docs/adr/ADR-000{n}-{slug}.md",
)
for n, slug in (
(1, "repository-prefix-architecture"),
(2, "rail-kubernetes-wave-1-boundary"),
(3, "rapp-first-wave-selection"),
(4, "first-wave-reef-rollout"),
(5, "derived-rail-composition"),
(6, "reef-production-admission"),
(7, "rapp-declaration-contract"),
(8, "private-by-default-exposure"),
)
),
}
SPECIALS = {
("policy-nexus", "docs/adr/ADR-0001-addressing-and-permanence.md"): (
"First publish set. This repo's addressing contract."
),
("the-custodian", "canon/architecture/adr-008-multi-tenancy-model.md"): (
"Relocated to net-kingdom Tenancy Posture. Duplicate-claim candidate (kind 6)."
),
}
for n, slug in (
(1, "repository-prefix-architecture"),
(2, "rail-kubernetes-wave-1-boundary"),
(3, "rapp-first-wave-selection"),
(4, "first-wave-reef-rollout"),
(5, "derived-rail-composition"),
(6, "reef-production-admission"),
(7, "rapp-declaration-contract"),
(8, "private-by-default-exposure"),
):
SPECIALS[("railiance-master", f"docs/adr/ADR-000{n}-{slug}.md")] = (
"First publish set. Publication-ready front-matter."
)
def _bare_adr(source_path: str, meta_id: str) -> str:
for text in (meta_id, Path(source_path).name):
match = BARE_ADR.search(text)
if match:
return f"ADR-{int(match.group(1)):04d}"
return ""
def _missing(meta: dict[str, str]) -> list[str]:
missing = [field for field in REQUIRED if not meta.get(field)]
if not (meta.get("revision") or meta.get("version")):
missing.append("revision|version")
if not ((meta.get("last_reviewed") or meta.get("updated")) and meta.get("review_interval")):
missing.append("review")
return missing
def _frontmatter(path: Path) -> dict[str, str]:
if not path.is_file():
return {}
meta, _body = split_frontmatter(path.read_text(encoding="utf-8", errors="replace"))
return {key: value for key, value in meta.items() if value}
def build_ledger(
inventory_path: Path, config_path: Path, *, policy_root: Path, source_root: Path
) -> dict[str, Any]:
inventory = _read_json(inventory_path)
config = _read_json(config_path)
paths = _repo_paths(config, policy_root=policy_root, source_root=source_root)
rows: list[dict[str, Any]] = []
for source in inventory.get("sources", []):
key = (source["source_repo"], source["source_path"])
repo = paths.get(source["source_repo"])
file_path = (repo / source["source_path"]) if repo is not None else None
present = bool(file_path and file_path.is_file())
meta = _frontmatter(file_path) if present and file_path is not None else {}
rows.append(
{
"source_repo": source["source_repo"],
"source_path": source["source_path"],
"inventory_disposition": source.get("disposition", ""),
"inventory_reason": source.get("reason", ""),
"file_present": present,
"frontmatter": {
field: meta.get(field, "")
for field in (
"id",
"title",
"status",
"owner",
"revision",
"version",
"last_reviewed",
"updated",
"review_interval",
)
},
"missing_fields": _missing(meta) if present else ["file-missing"],
"bare_adr": _bare_adr(source["source_path"], meta.get("id", "")),
"id_collisions": [],
"bare_adr_collisions": [],
"notes": SPECIALS.get(key, ""),
"proposed_disposition": "publish" if key in FIRST_PUBLISH else "unreviewed",
"conflict_kinds": [],
"successor": (
"netkingdom-tenancy-posture"
if key == ("the-custodian", "canon/architecture/adr-008-multi-tenancy-model.md")
else ""
),
"review_notes": "",
}
)
by_id: dict[str, list[str]] = {}
by_bare: dict[str, list[str]] = {}
for row in rows:
label = f"{row['source_repo']}/{row['source_path']}"
meta_id = row["frontmatter"]["id"]
if meta_id:
by_id.setdefault(meta_id, []).append(label)
if row["bare_adr"]:
by_bare.setdefault(row["bare_adr"], []).append(label)
for row in rows:
label = f"{row['source_repo']}/{row['source_path']}"
meta_id = row["frontmatter"]["id"]
if meta_id:
row["id_collisions"] = [other for other in by_id.get(meta_id, []) if other != label]
if row["bare_adr"]:
row["bare_adr_collisions"] = [
other for other in by_bare.get(row["bare_adr"], []) if other != label
]
return {
"schema_version": 1,
"protocol": "docs/adr-review/protocol.md",
"source_inventory": "source-inventory.json",
"rows": rows,
}
def _summary(ledger: dict[str, Any]) -> str:
rows = ledger["rows"]
dispositions = {}
inventory = {}
for row in rows:
dispositions[row["proposed_disposition"]] = (
dispositions.get(row["proposed_disposition"], 0) + 1
)
inventory[row["inventory_disposition"]] = inventory.get(row["inventory_disposition"], 0) + 1
missing_files = [row for row in rows if not row["file_present"]]
id_hits = [row for row in rows if row["id_collisions"]]
bare_hits = [row for row in rows if row["bare_adr_collisions"]]
lines = [
"# ADR review ledger summary",
"",
f"Rows: {len(rows)}",
"",
"## Inventory dispositions",
"",
]
for name, count in sorted(inventory.items()):
lines.append(f"- `{name}`: {count}")
lines.extend(["", "## Proposed dispositions", ""])
for name, count in sorted(dispositions.items()):
lines.append(f"- `{name}`: {count}")
lines.extend(["", "## Front-matter `id` collisions", ""])
if not id_hits:
lines.append("None.")
else:
seen = set()
for row in id_hits:
key = row["frontmatter"]["id"]
if key in seen:
continue
seen.add(key)
others = ", ".join([f"{row['source_repo']}/{row['source_path']}", *row["id_collisions"]])
lines.append(f"- `{key}`: {others}")
lines.extend(["", "## Bare ADR-NNNN collisions", ""])
if not bare_hits:
lines.append("None.")
else:
seen = set()
for row in bare_hits:
key = row["bare_adr"]
if key in seen:
continue
seen.add(key)
others = ", ".join(
[f"{row['source_repo']}/{row['source_path']}", *row["bare_adr_collisions"]]
)
lines.append(f"- `{key}`: {others}")
lines.extend(["", "## Missing files", ""])
if not missing_files:
lines.append("None.")
else:
for row in missing_files:
lines.append(f"- `{row['source_repo']}/{row['source_path']}`")
lines.append("")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--config", type=Path, default=Path("source-inventory.config.json"))
parser.add_argument("--inventory", type=Path, default=Path("source-inventory.json"))
parser.add_argument("--output", type=Path, default=Path("docs/adr-review/ledger.json"))
parser.add_argument("--summary", type=Path, default=Path("docs/adr-review/SUMMARY.md"))
parser.add_argument("--policy-root", type=Path)
parser.add_argument("--source-root", type=Path)
args = parser.parse_args(argv)
config_path = args.config.resolve()
policy_root = (args.policy_root or config_path.parent).resolve()
source_root = (args.source_root or policy_root.parent).resolve()
try:
ledger = build_ledger(
args.inventory.resolve(),
config_path,
policy_root=policy_root,
source_root=source_root,
)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(
json.dumps(ledger, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
args.summary.write_text(_summary(ledger), encoding="utf-8")
print(f"{args.output}: {len(ledger['rows'])} row(s)")
return 0
except (OSError, ValueError) as exc:
print(f"adr review ledger failed: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())