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:
parent
7a24e9107f
commit
a8fb62a3bc
34 changed files with 9669 additions and 28 deletions
255
tools/adr_review_ledger.py
Normal file
255
tools/adr_review_ledger.py
Normal 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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue