Record ADR conflicts and publish the estate arc42
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 39s

T03 writes a rulings overlay and a readable conflict list. T04 starts
relevance: five superseded, five live conflicts, estate and activity-core
ADRs marked publish-after-prefix. The Coulomb estate map is now a
published architecture document.
This commit is contained in:
tegwick 2026-08-18 22:27:05 +02:00
parent 9c6a1d3ce0
commit d1f9ac6cd7
17 changed files with 1263 additions and 126 deletions

View file

@ -82,11 +82,30 @@ def _frontmatter(path: Path) -> dict[str, str]:
return {key: value for key, value in meta.items() if value}
def _load_rulings(path: Path | None) -> dict[tuple[str, str], dict[str, Any]]:
if path is None or not path.exists():
return {}
payload = _read_json(path)
rulings: dict[tuple[str, str], dict[str, Any]] = {}
for ruling in payload.get("rulings", []):
key = (ruling["source_repo"], ruling["source_path"])
if key in rulings:
raise ValueError(f"duplicate ruling: {key[0]}/{key[1]}")
rulings[key] = ruling
return rulings
def build_ledger(
inventory_path: Path, config_path: Path, *, policy_root: Path, source_root: Path
inventory_path: Path,
config_path: Path,
*,
policy_root: Path,
source_root: Path,
rulings_path: Path | None = None,
) -> dict[str, Any]:
inventory = _read_json(inventory_path)
config = _read_json(config_path)
rulings = _load_rulings(rulings_path)
paths = _repo_paths(config, policy_root=policy_root, source_root=source_root)
rows: list[dict[str, Any]] = []
for source in inventory.get("sources", []):
@ -123,11 +142,7 @@ def build_ledger(
"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 ""
),
"successor": "",
"review_notes": "",
}
)
@ -150,11 +165,31 @@ def build_ledger(
row["bare_adr_collisions"] = [
other for other in by_bare.get(row["bare_adr"], []) if other != label
]
key = (row["source_repo"], row["source_path"])
ruling = rulings.get(key)
if ruling:
for field in (
"proposed_disposition",
"conflict_kinds",
"successor",
"review_notes",
):
if field in ruling:
row[field] = ruling[field]
if row["id_collisions"] and 1 not in row["conflict_kinds"]:
row["conflict_kinds"] = [*row["conflict_kinds"], 1]
if not row["review_notes"]:
row["review_notes"] = (
f"Front-matter id {row['frontmatter']['id']!r} is shared; "
"pick a repo-prefixed publication id before publish. "
"Who rules: owning repo."
)
return {
"schema_version": 1,
"protocol": "docs/adr-review/protocol.md",
"source_inventory": "source-inventory.json",
"rulings": str(rulings_path) if rulings_path else "",
"rows": rows,
}
@ -210,6 +245,17 @@ def _summary(ledger: dict[str, Any]) -> str:
[f"{row['source_repo']}/{row['source_path']}", *row["bare_adr_collisions"]]
)
lines.append(f"- `{key}`: {others}")
conflicts = [row for row in rows if row["proposed_disposition"] == "conflict"]
lines.extend(["", "## Conflict rows", ""])
if not conflicts:
lines.append("None.")
else:
for row in conflicts:
kinds = ",".join(str(kind) for kind in row["conflict_kinds"]) or "-"
lines.append(
f"- `{row['source_repo']}/{row['source_path']}` "
f"(kinds {kinds}): {row['review_notes']}"
)
lines.extend(["", "## Missing files", ""])
if not missing_files:
lines.append("None.")
@ -226,6 +272,7 @@ def main(argv: list[str] | None = None) -> int:
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("--rulings", type=Path, default=Path("docs/adr-review/rulings.json"))
parser.add_argument("--policy-root", type=Path)
parser.add_argument("--source-root", type=Path)
args = parser.parse_args(argv)
@ -238,6 +285,7 @@ def main(argv: list[str] | None = None) -> int:
config_path,
policy_root=policy_root,
source_root=source_root,
rulings_path=args.rulings.resolve() if args.rulings else None,
)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(