85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Registry health: stale & unowned surface detection (ATLAS-WP-0003-T05).
|
||
|
|
|
||
|
|
Reports promoted surfaces that need attention:
|
||
|
|
- unowned : missing owner, or owner not resolvable to a known identity
|
||
|
|
- stale : evidence.last_seen older than --max-age-days (default 180), or absent
|
||
|
|
|
||
|
|
Ownership resolution uses reuse-surface's local-repo-roster as a stand-in for
|
||
|
|
domain-tree identities (until domain-tree binding is wired, ATLAS-WP-0002 follow-up).
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python3 tools/registry_health.py [--max-age-days N] [--strict]
|
||
|
|
make registry-health
|
||
|
|
Exit 0 normally; 1 when --strict and any issue is found.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import datetime as _dt
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
try:
|
||
|
|
import yaml
|
||
|
|
except ImportError as exc: # pragma: no cover
|
||
|
|
raise SystemExit(f"setup error: missing PyYAML ({exc})")
|
||
|
|
|
||
|
|
from effective_config import SURFACES_DIR, load_entry
|
||
|
|
|
||
|
|
ROSTER = Path.home() / "reuse-surface" / "registry" / "federation" / "local-repo-roster.yaml"
|
||
|
|
|
||
|
|
|
||
|
|
def known_owners() -> set[str]:
|
||
|
|
owners = {"custodian"} # State Hub domain identity not in the repo roster
|
||
|
|
if ROSTER.exists():
|
||
|
|
data = yaml.safe_load(ROSTER.read_text()) or {}
|
||
|
|
owners |= {r.get("slug") for r in data.get("repos", []) if r.get("slug")}
|
||
|
|
return owners
|
||
|
|
|
||
|
|
|
||
|
|
def main(argv: list[str]) -> int:
|
||
|
|
max_age = 180
|
||
|
|
strict = "--strict" in argv
|
||
|
|
if "--max-age-days" in argv:
|
||
|
|
i = argv.index("--max-age-days")
|
||
|
|
max_age = int(argv[i + 1])
|
||
|
|
cutoff = _dt.date.today() - _dt.timedelta(days=max_age)
|
||
|
|
owners = known_owners()
|
||
|
|
|
||
|
|
unowned: list[str] = []
|
||
|
|
stale: list[str] = []
|
||
|
|
for p in sorted(SURFACES_DIR.glob("*.md")):
|
||
|
|
e = load_entry(p)
|
||
|
|
sid = e.get("id", p.stem)
|
||
|
|
owner = e.get("owner")
|
||
|
|
if not owner:
|
||
|
|
unowned.append(f"{sid}: missing owner")
|
||
|
|
elif owner not in owners:
|
||
|
|
unowned.append(f"{sid}: owner '{owner}' not resolvable to a known identity")
|
||
|
|
seen = (e.get("evidence", {}) or {}).get("last_seen")
|
||
|
|
if not seen:
|
||
|
|
stale.append(f"{sid}: no evidence.last_seen")
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
if _dt.date.fromisoformat(str(seen)) < cutoff:
|
||
|
|
stale.append(f"{sid}: last_seen {seen} older than {max_age}d")
|
||
|
|
except ValueError:
|
||
|
|
stale.append(f"{sid}: unparseable last_seen '{seen}'")
|
||
|
|
|
||
|
|
total = len(list(SURFACES_DIR.glob("*.md")))
|
||
|
|
print(f"registry health: {total} promoted surface(s)")
|
||
|
|
print(f" unowned/unresolved: {len(unowned)}")
|
||
|
|
for u in unowned:
|
||
|
|
print(f" - {u}")
|
||
|
|
print(f" stale (> {max_age}d): {len(stale)}")
|
||
|
|
for s in stale:
|
||
|
|
print(f" - {s}")
|
||
|
|
if not unowned and not stale:
|
||
|
|
print(" all surfaces owned and fresh.")
|
||
|
|
return 1 if (strict and (unowned or stale)) else 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||
|
|
raise SystemExit(main(sys.argv[1:]))
|