PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
review_packet.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Assemble bounded review packets from PICurv's declared routing registries."""
3
4from __future__ import annotations
5
6import argparse
7import difflib
8import hashlib
9import json
10import re
11import subprocess
12import sys
13from functools import lru_cache
14from pathlib import Path
15
16
17REPO_ROOT = Path(__file__).resolve().parents[2]
18PAGES_DIR = REPO_ROOT / "docs" / "pages"
19TOOLING_DIR = Path(__file__).resolve().parent
20if str(TOOLING_DIR) not in sys.path:
21 sys.path.insert(0, str(TOOLING_DIR))
22
23from generate_xref_index import ( # noqa: E402
24 SCHEMA_VERSION as XREF_SCHEMA_VERSION,
25 digest_files as xref_digest_files,
26 file_digest as xref_file_digest,
27 source_files as xref_source_files,
28)
29
30
31CONTRACTS = TOOLING_DIR / "contract_registry.json"
32FRESHNESS = TOOLING_DIR / "freshness_manifest.json"
33MEASUREMENTS = TOOLING_DIR / "measurement_records.json"
34FAMILIES = TOOLING_DIR / "capability_families.json"
35SUBSYSTEMS = TOOLING_DIR / "subsystem_records.json"
36SCOPE = TOOLING_DIR / "capability_scope_records.json"
37XREF = REPO_ROOT / "docs_build" / "xref.json"
38XREF_CAVEAT = (
39 "Doxygen provides source-reference edges, not a semantic call graph. Registry tables, "
40 "callbacks, function pointers, macros, PETSc dispatch, and runtime-selected paths may "
41 "require following intermediate symbols. No direct edge does not prove that a symbol is unused."
42)
43COMPLETE = "ROUTE: complete over declared registry data (not over code behavior)"
44INCOMPLETE = "ROUTE: incomplete — declared identifier has unresolved routing"
45UNAVAILABLE = "ROUTE: unavailable — required environment or metadata is absent"
46
47
48def records(path: Path, key: str) -> list[dict]:
49 """!
50 @brief Read one registry list.
51 @param[in] path Registry JSON path.
52 @param[in] key Top-level list key.
53 @return Registry records.
54 """
55
56 return json.loads(path.read_text(encoding="utf-8"))[key]
57
58
59def resolve_page(name: str) -> Path:
60 """!
61 @brief Resolve a page argument, accepting a number, stem, or filename.
62 @param[in] name Page identifier supplied on the command line.
63 @return Path to the page.
64 @throws SystemExit when no page matches.
65 """
66
67 candidates = sorted(PAGES_DIR.glob("*.md"))
68 for page in candidates:
69 if name in (page.name, page.stem) or page.stem.startswith(f"{name}_"):
70 return page
71 print(f"ROUTE: invalid — no page matches '{name}'.", file=sys.stderr)
72 print("Known pages:", file=sys.stderr)
73 for page in candidates:
74 print(f" {page.stem}", file=sys.stderr)
75 raise SystemExit(2)
76
77
78def source_path(source: str) -> str:
79 """!
80 @brief Extract a repository path from a path-or-symbol source declaration.
81 @param[in] source Declared source string.
82 @return Repository-relative path portion.
83 """
84
85 return source.split("::", 1)[0]
86
87
88def module_path(module: str) -> str:
89 """!
90 @brief Convert a Python module identifier to its repository path.
91
92 @details A dotted name may address a package rather than a single file - the
93 storage surface is one - in which case the package directory is the
94 source, because that is what an importer resolves the name to.
95 @param[in] module Dotted Python module.
96 @return Repository-relative Python path.
97 """
98
99 relative = module.replace(".", "/")
100 if (REPO_ROOT / relative).is_dir():
101 return relative
102 return relative + ".py"
103
104
105@lru_cache(maxsize=1)
106def page_aliases() -> dict[str, str]:
107 """!
108 @brief Map both filenames and Doxygen page identifiers to published page stems.
109 @return Alias-to-page-stem mapping.
110 """
111
112 aliases: dict[str, str] = {}
113 for page in PAGES_DIR.glob("*.md"):
114 aliases[page.stem] = page.stem
115 match = re.search(r"(?:^|\n)\s*@page\s+(\S+)", page.read_text(encoding="utf-8"))
116 if match:
117 aliases[match.group(1)] = page.stem
118 return aliases
119
120
121def canonical_page(reference: str) -> str:
122 """!
123 @brief Resolve a registry page reference to its filename stem when declared by Doxygen id.
124 @param[in] reference Page stem or Doxygen page identifier.
125 @return Canonical filename stem, or the unresolved input.
126 """
127
128 return page_aliases().get(reference, reference)
129
130
131def git_log(paths: list[str]) -> str:
132 """!
133 @brief Recent history for a set of paths, for spotting what changed underneath a route.
134 @param[in] paths Repository-relative paths.
135 @return Formatted git log output.
136 """
137
138 if not paths:
139 return " (none declared)"
140 result = subprocess.run(
141 ["git", "-C", str(REPO_ROOT), "log", "--oneline", "-8", "--", *paths],
142 capture_output=True,
143 text=True,
144 check=False,
145 )
146 return "\n".join(f" {line}" for line in result.stdout.strip().splitlines()) or " (no history)"
147
148
149def contracts_for(page_stem: str) -> list[dict]:
150 """!
151 @brief Invariant contracts that own or depend on a page.
152 @param[in] page_stem Page identifier without extension.
153 @return Matching contract records.
154 """
155
156 return [
157 contract
158 for contract in records(CONTRACTS, "contracts")
159 if page_stem
160 in contract.get("canonical_documentation", []) + contract.get("dependent_pages", [])
161 ]
162
163
164def families_for(page_stem: str) -> list[dict]:
165 """!
166 @brief Capability families whose entries live on a page.
167 @param[in] page_stem Page identifier without extension.
168 @return Matching family records.
169 """
170
171 return [family for family in records(FAMILIES, "families") if family["family_page"] == page_stem]
172
173
174def record_pages(record: dict) -> set[str]:
175 """!
176 @brief Collect obligation and concern pages from a subsystem record.
177 @param[in] record Subsystem lifecycle record.
178 @return Page stems named by the record.
179 """
180
181 pages: set[str] = set()
182 for section in ("obligations", "concerns"):
183 for value in record.get(section, {}).values():
184 if isinstance(value, dict) and value.get("page"):
185 pages.add(canonical_page(value["page"]))
186 return pages
187
188
189def subsystems_for_pages(page_stems: set[str]) -> list[dict]:
190 """!
191 @brief Find subsystem records routed through any supplied page.
192 @param[in] page_stems Page stems to match.
193 @return Matching subsystem records.
194 """
195
196 return [record for record in records(SUBSYSTEMS, "subsystems") if record_pages(record) & page_stems]
197
198
199def scope_records_for(page_stem: str) -> list[dict]:
200 """!
201 @brief Scope records that name a page as a disclosure or activation surface.
202 @param[in] page_stem Page identifier without extension.
203 @return Matching scope records.
204 """
205
206 if not SCOPE.is_file():
207 return []
208 matches = []
209 for record in records(SCOPE, "records"):
210 surfaces = record.get("disclosed_at", []) + record.get("activation_condition", {}).get("surfaces", [])
211 if any(page_stem in surface for surface in surfaces):
212 matches.append(record)
213 return matches
214
215
216def freshness_for(page_stem: str) -> list[tuple[str, dict]]:
217 """!
218 @brief Freshness surfaces that route review to a page.
219 @param[in] page_stem Page identifier.
220 @return List of role and surface pairs.
221 """
222
223 found: list[tuple[str, dict]] = []
224 for surface in records(FRESHNESS, "surfaces"):
225 if page_stem in surface.get("owning_pages", []):
226 found.append(("owns", surface))
227 elif page_stem in surface.get("dependent_pages", []):
228 found.append(("depends on", surface))
229 return found
230
231
232def freshness_paths(surface: dict) -> list[str]:
233 """!
234 @brief Return the artifact or watched paths that define a freshness surface.
235 @param[in] surface Freshness manifest record.
236 @return Repository-relative paths.
237 """
238
239 return [surface["artifact"]] if surface["tier"] == "hard" else list(surface.get("watched_paths", []))
240
241
242def freshness_state(surface: dict) -> str:
243 """!
244 @brief Whether a surface is current, suspect, or never attested.
245 @param[in] surface One freshness manifest entry.
246 @return Human-readable state.
247 """
248
249 paths = freshness_paths(surface)
250 missing = [path for path in paths if not (REPO_ROOT / path).exists()]
251 if missing:
252 return f"INTEGRITY FAILURE - missing {', '.join(missing)}"
253 attested = surface.get("attested_digest")
254 if attested in (None, "unattested"):
255 return "never attested"
256 return "current" if attested == _freshness_digest(paths) else "SUSPECT"
257
258
259def _freshness_digest(paths: list[str]) -> str:
260 """!
261 @brief Digest matching audit_freshness.digest_of.
262 @param[in] paths Repository-relative paths.
263 @return Hex digest with algorithm prefix.
264 """
265
266 accumulator = hashlib.sha256()
267 for relative in sorted(paths):
268 accumulator.update(relative.encode("utf-8"))
269 accumulator.update(b"\0")
270 accumulator.update((REPO_ROOT / relative).read_bytes())
271 accumulator.update(b"\0")
272 return f"sha256:{accumulator.hexdigest()}"
273
274
275def uncommitted(paths: list[str]) -> str:
276 """!
277 @brief Current uncommitted status for declared sources.
278 @param[in] paths Repository-relative paths.
279 @return Formatted status output.
280 """
281
282 if not paths:
283 return " (none declared)"
284 result = subprocess.run(
285 ["git", "-C", str(REPO_ROOT), "status", "--porcelain", "--", *paths],
286 capture_output=True,
287 text=True,
288 check=False,
289 )
290 body = result.stdout.strip()
291 return "\n".join(f" {line}" for line in body.splitlines()) if body else " (clean)"
292
293
294def family_source_specs(family: dict) -> list[tuple[str, str, str]]:
295 """!
296 @brief Resolve a capability family's declared source paths and symbols.
297 @param[in] family Capability family record.
298 @return Tuples of path, symbol, and source kind.
299 """
300
301 specs: list[tuple[str, str, str]] = []
302 surface = family.get("public_surface", {})
303 if surface.get("module") and surface.get("symbol"):
304 specs.append((module_path(surface["module"]), surface["symbol"], surface.get("kind", "public")))
305 for parity in family.get("parity_sources", []):
306 symbol = parity.get("function") or parity.get("symbol") or parity.get("prefix")
307 if parity.get("path") and symbol:
308 specs.append((parity["path"], symbol, parity.get("kind", "parity")))
309 return specs
310
311
312def symbols_for(family: dict) -> list[str]:
313 """!
314 @brief Named symbols a capability family depends on, rather than whole files.
315 @param[in] family Family record.
316 @return Symbol descriptions.
317 """
318
319 return [f"{path}::{symbol} ({kind})" for path, symbol, kind in family_source_specs(family)]
320
321
322def evidence_sources(family: dict) -> list[str]:
323 """!
324 @brief Declared evidence sources across a family's values.
325 @param[in] family Family record.
326 @return Sorted source identifiers.
327 """
328
329 sources: set[str] = set()
330 for metadata in family.get("value_metadata", {}).values():
331 for facet_sources in (metadata.get("evidence") or {}).values():
332 sources.update(facet_sources)
333 return sorted(sources)
334
335
336def recorded_measurements(sources: list[str]) -> list:
337 """!
338 @brief Measurement records cited by declared evidence sources.
339 @param[in] sources Declared evidence source identifiers.
340 @return Matching measurement records, in citation order.
341 """
342
343 wanted = [s.split(":", 1)[1] for s in sources if s.startswith("measurement:")]
344 if not wanted or not MEASUREMENTS.is_file():
345 return []
346 by_id = {r.get("id"): r for r in
347 json.loads(MEASUREMENTS.read_text(encoding="utf-8")).get("records", [])}
348 return [by_id[i] for i in wanted if i in by_id]
349
350
351def make_targets(sources: list[str]) -> list[str]:
352 """!
353 @brief Make targets named by declared evidence sources.
354 @param[in] sources Source identifiers.
355 @return Verification commands.
356 """
357
358 return [f"make {source.split(':', 1)[1]}" for source in sources if source.startswith("make:")]
359
360
361def route_status(unresolved: list[str], unavailable: bool = False) -> int:
362 """!
363 @brief Print the fixed route state and any unresolved declarations.
364 @param[in] unresolved Problems found while joining declared registry data.
365 @param[in] unavailable Whether required environmental metadata is unavailable.
366 @return Route exit status.
367 """
368
369 if unavailable:
370 print(UNAVAILABLE)
371 for problem in unresolved:
372 print(f" {problem}")
373 return 3
374 if unresolved:
375 print(INCOMPLETE)
376 for problem in unresolved:
377 print(f" {problem}")
378 return 3
379 print(COMPLETE)
380 return 0
381
382
383def unknown_identifier(kind: str, identifier: str, known: list[str]) -> int:
384 """!
385 @brief Report an invalid registry identifier with close matches and known ids.
386 @param[in] kind Identifier category.
387 @param[in] identifier Unknown value.
388 @param[in] known Valid values.
389 @return Invalid-argument status.
390 """
391
392 print(f"ROUTE: invalid — unknown {kind} '{identifier}'.", file=sys.stderr)
393 close = difflib.get_close_matches(identifier, known, n=5)
394 if close:
395 print(f"Close matches: {', '.join(close)}", file=sys.stderr)
396 print("Known ids:", file=sys.stderr)
397 for value in known:
398 print(f" {value}", file=sys.stderr)
399 return 2
400
401
402def print_freshness(surfaces: list[tuple[str, dict]], heading: str) -> None:
403 """!
404 @brief Print freshness routing with current attestation state.
405 @param[in] surfaces Role and surface pairs.
406 @param[in] heading Section heading.
407 @return None.
408 """
409
410 print(heading)
411 for role, surface in surfaces:
412 blocking = surface.get("enforcement") or ("blocking" if surface["tier"] == "hard" else "report")
413 state = freshness_state(surface)
414 print(f" [{surface['tier']}/{blocking}] {surface['id']} ({role}) - {state}")
415 for path in freshness_paths(surface):
416 print(f" {path}")
417 if state != "current":
418 print(f" after review: make attest-freshness ARGS=\"{surface['id']}\"")
419 if not surfaces:
420 print(" (none - no declared freshness surface routes here)")
421 print("")
422
423
424def load_xref() -> tuple[str, dict | None]:
425 """!
426 @brief Load the optional cross-reference index only when its dirty-byte stamp is current.
427 @return State (`current`, `missing`, or `stale`) and current index when available.
428 """
429
430 if not XREF.is_file():
431 return "missing", None
432 try:
433 index = json.loads(XREF.read_text(encoding="utf-8"))
434 current_sources = xref_source_files(REPO_ROOT)
435 current_digest = xref_digest_files(REPO_ROOT, current_sources)
436 doxyfile_digest = xref_file_digest(REPO_ROOT / "docs" / "Doxyfile")
437 except (OSError, ValueError, KeyError, TypeError):
438 return "stale", None
439 if (
440 index.get("schema_version") != XREF_SCHEMA_VERSION
441 or index.get("source_digest") != current_digest
442 or index.get("source_files") != [path.as_posix() for path in current_sources]
443 or index.get("doxyfile_digest") != doxyfile_digest
444 or not isinstance(index.get("symbols"), dict)
445 ):
446 return "stale", None
447 return "current", index
448
449
450def xref_matches(index: dict, path: str, symbol: str) -> list[str]:
451 """!
452 @brief Match a declared path and symbol to Doxygen member identifiers.
453 @param[in] index Current cross-reference index.
454 @param[in] path Expected definition path, or an empty string.
455 @param[in] symbol Expected unqualified or qualified symbol.
456 @return Matching member ids.
457 """
458
459 matches: list[str] = []
460 for refid, record in index["symbols"].items():
461 name_match = record.get("name") == symbol or record.get("qualified_name") == symbol
462 name_match = name_match or str(record.get("qualified_name", "")).endswith(f"::{symbol}")
463 name_match = name_match or str(record.get("qualified_name", "")).endswith(f".{symbol}")
464 path_match = not path or record.get("definition", {}).get("path") == path
465 if name_match and path_match:
466 matches.append(refid)
467 return sorted(matches)
468
469
470def format_xref_node(index: dict, refid: str) -> str:
471 """!
472 @brief Format one indexed symbol with its definition location.
473 @param[in] index Current cross-reference index.
474 @param[in] refid Doxygen member id.
475 @return Compact symbol and source location.
476 """
477
478 record = index["symbols"].get(refid, {})
479 location = record.get("definition", {})
480 line = f":{location.get('line')}" if location.get("line") else ""
481 return f"{record.get('qualified_name', record.get('name', refid))} ({location.get('path', '?')}{line})"
482
483
484def print_measurements(records: list) -> None:
485 """!
486 @brief Print the measurements already recorded against this route.
487 @param[in] records Measurement records to render.
488 @return None.
489 """
490
491 if not records:
492 return
493 print("MEASUREMENTS ALREADY RECORDED (read before running anything expensive)")
494 for record in records:
495 result = record.get("result") or {}
496 verdict = result.get("verdict", "?") if isinstance(result, dict) else "?"
497 print(f" [{verdict}] {record.get('id')} - {record.get('question')}")
498 print(f" taken {record.get('date')} at {record.get('commit')} on {record.get('environment')}")
499 print(f" does not establish: {record.get('limitations')}")
500 print("")
501
502
503def print_xref(specs: list[tuple[str, str, str]], paths: list[str] | None = None) -> None:
504 """!
505 @brief Print bounded direct and two-hop Doxygen reference evidence for a route.
506 @param[in] specs Declared source path, symbol, and kind tuples.
507 @param[in] paths Optional paths whose connected definitions should seed the view.
508 @return None.
509 """
510
511 print("OPTIONAL SOURCE CROSS-REFERENCES")
512 state, index = load_xref()
513 if state == "missing":
514 print("XREF: not built (optional)")
515 print(" Registry routing below is unaffected.")
516 print(" Run `make docs-xref` to add bounded source-reference evidence.")
517 print(f" {XREF_CAVEAT}\n")
518 return
519 if state == "stale" or index is None:
520 print("XREF: stale — run make docs-xref")
521 print(" Stale edges are not displayed; registry routing remains available.")
522 print(f" {XREF_CAVEAT}\n")
523 return
524
525 print("XREF: current")
526 print(f" {XREF_CAVEAT}")
527 selected: list[tuple[str, str]] = []
528 for path, symbol, _ in specs:
529 for refid in xref_matches(index, path, symbol):
530 selected.append((f"{path}::{symbol}", refid))
531 if paths:
532 path_set = set(paths)
533 connected = [
534 (f"definitions in {record.get('definition', {}).get('path')}", refid)
535 for refid, record in index["symbols"].items()
536 if record.get("definition", {}).get("path") in path_set
537 and (record.get("incoming") or record.get("outgoing"))
538 ]
539 selected.extend(sorted(connected, key=lambda item: format_xref_node(index, item[1]))[:8])
540
541 seen: set[str] = set()
542 bounded = []
543 for label, refid in selected:
544 if refid not in seen:
545 seen.add(refid)
546 bounded.append((label, refid))
547 if not bounded:
548 print(" (no indexed definition matched the declared symbols or paths)\n")
549 return
550 for label, refid in bounded[:8]:
551 record = index["symbols"].get(refid, {})
552 print(f" {label}")
553 print(f" definition: {format_xref_node(index, refid)}")
554 incoming = [edge for edge in record.get("incoming", []) if edge in index["symbols"]]
555 outgoing = [edge for edge in record.get("outgoing", []) if edge in index["symbols"]]
556 for edge in incoming[:4]:
557 print(f" direct caller: {format_xref_node(index, edge)}")
558 for edge in outgoing[:4]:
559 print(f" direct reference: {format_xref_node(index, edge)}")
560 second_in = sorted(
561 {
562 second
563 for edge in incoming[:8]
564 for second in index["symbols"].get(edge, {}).get("incoming", [])
565 if second in index["symbols"] and second != refid
566 }
567 )
568 second_out = sorted(
569 {
570 second
571 for edge in outgoing[:8]
572 for second in index["symbols"].get(edge, {}).get("outgoing", [])
573 if second in index["symbols"] and second != refid
574 }
575 )
576 for edge in second_in[:3]:
577 print(f" two-hop caller: {format_xref_node(index, edge)}")
578 for edge in second_out[:3]:
579 print(f" two-hop reference: {format_xref_node(index, edge)}")
580 if len(incoming) > 4 or len(outgoing) > 4 or len(second_in) > 3 or len(second_out) > 3:
581 print(" (additional edges omitted; inspect docs_build/xref.json for this symbol)")
582 print("")
583
584
585def contract_mode(contract_id: str) -> int:
586 """!
587 @brief Print a review packet for one invariant contract.
588 @param[in] contract_id Contract identifier.
589 @return Process status code.
590 """
591
592 known = {contract["id"]: contract for contract in records(CONTRACTS, "contracts")}
593 contract = known.get(contract_id)
594 if not contract:
595 return unknown_identifier("contract", contract_id, sorted(known))
596 sources = [source_path(source) for source in contract.get("authoritative_sources", [])]
597 pages = [
598 canonical_page(page)
599 for page in contract.get("canonical_documentation", []) + contract.get("dependent_pages", [])
600 ]
601 unresolved = [f"missing source {path}" for path in sources if not (REPO_ROOT / path).exists()]
602 unresolved.extend(
603 f"missing page docs/pages/{page}.md" for page in pages if not (PAGES_DIR / f"{page}.md").is_file()
604 )
605
606 print(f"REVIEW PACKET: contract {contract['id']}")
607 status = route_status(unresolved)
608 print(f" {contract['title']} [{contract['status']} / {contract['enforcement']}]\n")
609 if contract.get("note"):
610 print("SCOPE")
611 print(f" {contract['note']}\n")
612 print("AUTHORITATIVE SOURCES")
613 for source in contract.get("authoritative_sources", []) or ["(none)"]:
614 print(f" {source}")
615 print("\nCANONICAL DOCUMENTATION (owns the narrative)")
616 for page in contract.get("canonical_documentation", []) or ["(none)"]:
617 print(f" docs/pages/{page}.md")
618 print("\nDEPENDENT PAGES (review if this contract changes)")
619 for page in contract.get("dependent_pages", []) or ["(none)"]:
620 print(f" docs/pages/{page}.md")
621 print("")
622
623 if contract["id"].startswith("run.artifact"):
624 topology = json.loads((TOOLING_DIR / "artifact_topology.json").read_text(encoding="utf-8"))
625 print("LOGICAL IDENTITIES")
626 for artifact in topology["artifacts"]:
627 print(f" {artifact['id']:26} {artifact['path_rule']}")
628 for entry in topology.get("resolved_safety_history", {}).get("entries", []):
629 print(f"\n RESOLVED SAFETY DEFECT: {entry['id']} ({entry['severity']}) - {entry['state']}")
630 print(f" residual risk: {entry['resolution']['residual_risk'][:120]}")
631 print("")
632
633 scoped = []
634 for surface in records(FRESHNESS, "surfaces"):
635 if set(surface.get("owning_pages", [])) & set(contract.get("canonical_documentation", [])):
636 scoped.append(("owns", surface))
637 if scoped:
638 print_freshness(scoped, "FRESHNESS SURFACES ON THIS CONTRACT'S PAGES")
639 print_xref([], sources)
640 print("VERIFY WITH")
641 checker = contract.get("checker")
642 print(f" python3 {' '.join(checker)}" if checker else " (no automated checker - human review)")
643 print(" make audit-contracts\n")
644 print("UNCOMMITTED CHANGES TO DECLARED SOURCES")
645 print(uncommitted(sources))
646 print("\nRECENT HISTORY")
647 print(git_log(sources))
648 return status
649
650
651def capability_mode(capability_id: str) -> int:
652 """!
653 @brief Join one capability family across symbols, pages, contracts, evidence, and subsystems.
654 @param[in] capability_id Capability family id.
655 @return Process status code.
656 """
657
658 known = {family["id"]: family for family in records(FAMILIES, "families")}
659 family = known.get(capability_id)
660 if not family:
661 return unknown_identifier("capability", capability_id, sorted(known))
662 specs = family_source_specs(family)
663 page = family["family_page"]
664 unresolved = [
665 f"missing declared source {path}" for path, _, _ in specs
666 if not (REPO_ROOT / path).exists()
667 ]
668 if not specs:
669 unresolved.append("no public or parity source symbol is declared")
670 if not (PAGES_DIR / f"{page}.md").is_file():
671 unresolved.append(f"missing family page docs/pages/{page}.md")
672 related_contracts = contracts_for(page)
673 related_subsystems = [
674 subsystem
675 for subsystem in records(SUBSYSTEMS, "subsystems")
676 if capability_id in subsystem.get("capability_families", [])
677 ]
678 evidence = evidence_sources(family)
679
680 print(f"REVIEW PACKET: capability {capability_id}")
681 status = route_status(unresolved)
682 mode = "enforced" if family.get("coverage_enforced") else "advisory"
683 print(f" {family['title']} [{mode}; {len(family.get('value_metadata', {}))} values]")
684 print(f" selector: {family.get('selector', '(not declared)')}\n")
685 print("SOURCE SYMBOLS")
686 for symbol in symbols_for(family) or ["(none declared)"]:
687 print(f" {symbol}")
688 print("\nFAMILY DOCUMENTATION")
689 print(f" docs/pages/{page}.md")
690 print("\nINVARIANT CONTRACTS ON THAT PAGE")
691 for contract in related_contracts or [None]:
692 print(f" [{contract['status']}] {contract['id']}" if contract else " (none)")
693 print("\nSUBSYSTEMS USING THIS FAMILY")
694 for subsystem in related_subsystems or [None]:
695 print(f" [{subsystem['status']}] {subsystem['id']}" if subsystem else " (none declared)")
696 print("")
697 print_freshness(freshness_for(page), "FRESHNESS SURFACES ROUTING TO THE FAMILY PAGE")
698 print("DECLARED EVIDENCE SOURCES")
699 for source in evidence or ["(none declared)"]:
700 print(f" {source}")
701 print("")
703 print_xref(specs)
704 print("VERIFY WITH")
705 for command in sorted(set(make_targets(evidence))):
706 print(f" {command}")
707 print(" make audit-capability")
708 print(f" make review-packet PAGE={page}")
709 return status
710
711
712def subsystem_mode(subsystem_id: str) -> int:
713 """!
714 @brief Join one subsystem across capability families, obligation pages, contracts, and evidence.
715 @param[in] subsystem_id Subsystem lifecycle id.
716 @return Process status code.
717 """
718
719 known = {record["id"]: record for record in records(SUBSYSTEMS, "subsystems")}
720 subsystem = known.get(subsystem_id)
721 if not subsystem:
722 return unknown_identifier("subsystem", subsystem_id, sorted(known))
723 family_map = {family["id"]: family for family in records(FAMILIES, "families")}
724 family_ids = subsystem.get("capability_families", [])
725 pages = record_pages(subsystem)
726 unresolved = [f"unknown capability family {family_id}" for family_id in family_ids if family_id not in family_map]
727 unresolved.extend(f"missing page docs/pages/{page}.md" for page in pages if not (PAGES_DIR / f"{page}.md").is_file())
728 families = [family_map[family_id] for family_id in family_ids if family_id in family_map]
729 specs = [spec for family in families for spec in family_source_specs(family)]
730 surfaces = [
731 ("routes subsystem page", surface)
732 for surface in records(FRESHNESS, "surfaces")
733 if set(surface.get("owning_pages", []) + surface.get("dependent_pages", [])) & pages
734 ]
735 related_contracts = [
736 contract
737 for contract in records(CONTRACTS, "contracts")
738 if set(contract.get("canonical_documentation", []) + contract.get("dependent_pages", [])) & pages
739 ]
740 evidence = sorted({source for family in families for source in evidence_sources(family)})
741
742 print(f"REVIEW PACKET: subsystem {subsystem_id}")
743 status = route_status(unresolved)
744 print(f" {subsystem['title']} [{subsystem['status']} / {subsystem.get('visibility', '?')}]\n")
745 print("CAPABILITY FAMILIES")
746 for family in families or [None]:
747 print(f" {family['id']} -> docs/pages/{family['family_page']}.md" if family else " (none declared)")
748 print("\nOBLIGATION AND CONCERN PAGES")
749 for page in sorted(pages) or ["(none declared)"]:
750 print(f" docs/pages/{page}.md")
751 print("\nINVARIANT CONTRACTS ON THOSE PAGES")
752 for contract in related_contracts or [None]:
753 print(f" [{contract['status']}] {contract['id']}" if contract else " (none)")
754 print("")
755 print_freshness(surfaces, "FRESHNESS SURFACES ROUTING TO THOSE PAGES")
756 print("DECLARED EVIDENCE AND TEST TARGETS")
757 for source in evidence or ["(none declared through capability families)"]:
758 print(f" {source}")
759 print("")
760 print_xref(specs, [path for _, surface in surfaces for path in freshness_paths(surface)])
761 print("VERIFY WITH")
762 for command in sorted(set(make_targets(evidence))):
763 print(f" {command}")
764 print(" make audit-subsystems")
765 return status
766
767
768def surface_mode(surface_id: str) -> int:
769 """!
770 @brief Join one freshness surface across watched paths, pages, contracts, families, and subsystems.
771 @param[in] surface_id Freshness surface id.
772 @return Process status code.
773 """
774
775 known = {surface["id"]: surface for surface in records(FRESHNESS, "surfaces")}
776 surface = known.get(surface_id)
777 if not surface:
778 return unknown_identifier("surface", surface_id, sorted(known))
779 paths = freshness_paths(surface)
780 pages = {
781 canonical_page(page)
782 for page in surface.get("owning_pages", []) + surface.get("dependent_pages", [])
783 }
784 unresolved = [
785 f"missing watched path {path}" for path in paths
786 if not (REPO_ROOT / path).exists()
787 ]
788 unresolved.extend(f"missing page docs/pages/{page}.md" for page in pages if not (PAGES_DIR / f"{page}.md").is_file())
789 families = [family for family in records(FAMILIES, "families") if family["family_page"] in pages]
790 related_contracts = [
791 contract
792 for contract in records(CONTRACTS, "contracts")
793 if set(contract.get("canonical_documentation", []) + contract.get("dependent_pages", [])) & pages
794 ]
795 subsystems = subsystems_for_pages(pages)
796 specs = [spec for family in families for spec in family_source_specs(family)]
797
798 print(f"REVIEW PACKET: surface {surface_id}")
799 status = route_status(unresolved)
800 print(f" {surface['title']} [{surface['tier']}] - {freshness_state(surface)}\n")
801 print("WATCHED PATHS")
802 for path in paths or ["(none declared)"]:
803 print(f" {path}")
804 print("\nOWNING AND DEPENDENT PAGES")
805 for page in sorted(pages) or ["(none declared)"]:
806 role = "owns" if page in surface.get("owning_pages", []) else "depends on"
807 print(f" {role}: docs/pages/{page}.md")
808 print("\nINVARIANT CONTRACTS")
809 for contract in related_contracts or [None]:
810 print(f" [{contract['status']}] {contract['id']}" if contract else " (none)")
811 print("\nCAPABILITY FAMILIES")
812 for family in families or [None]:
813 print(f" {family['id']}" if family else " (none routed through these pages)")
814 print("\nSUBSYSTEMS")
815 for subsystem in subsystems or [None]:
816 print(f" [{subsystem['status']}] {subsystem['id']}" if subsystem else " (none routed through these pages)")
817 print("")
818 print_xref(specs, paths)
819 print("VERIFY WITH")
820 print(f" make review-packet PAGE={next(iter(sorted(pages)), '<page>')}")
821 print(f" make attest-freshness ARGS=\"{surface_id}\" # only after source comparison")
822 return status
823
824
825def changed_paths() -> tuple[dict[str, set[str]], bool]:
826 """!
827 @brief Collect staged, unstaged, and untracked nonignored paths from Git.
828 @return Path-to-state mapping and whether Git metadata was available.
829 """
830
831 probe = subprocess.run(
832 ["git", "-C", str(REPO_ROOT), "rev-parse", "--is-inside-work-tree"],
833 capture_output=True,
834 text=True,
835 check=False,
836 )
837 if probe.returncode != 0:
838 return {}, False
839 commands = {
840 "staged": ["git", "-C", str(REPO_ROOT), "diff", "--cached", "--name-only", "-z"],
841 "unstaged": ["git", "-C", str(REPO_ROOT), "diff", "--name-only", "-z"],
842 "untracked": ["git", "-C", str(REPO_ROOT), "ls-files", "--others", "--exclude-standard", "-z"],
843 }
844 found: dict[str, set[str]] = {}
845 for state, command in commands.items():
846 completed = subprocess.run(command, capture_output=True, check=False)
847 if completed.returncode != 0:
848 return {}, False
849 for raw in completed.stdout.split(b"\0"):
850 if raw:
851 found.setdefault(raw.decode("utf-8", errors="replace"), set()).add(state)
852 return found, True
853
854
855def classify_path(path: str) -> str:
856 """!
857 @brief Classify a changed path for routing coverage.
858 @param[in] path Repository-relative changed path.
859 @return Category label excluding routed versus unrouted production state.
860 """
861
862 candidate = Path(path)
863 if path.startswith("docs/generated/") or path.startswith("docs_build/"):
864 return "generated"
865 if (
866 (candidate.parts and candidate.parts[0] == "src" and candidate.suffix in {".c", ".h"})
867 or (candidate.parts and candidate.parts[0] == "include" and candidate.suffix in {".h", ".hpp"})
868 or (candidate.parts and candidate.parts[0] == "picurv_cli" and candidate.suffix == ".py")
869 ):
870 return "production"
871 routed_roots = {"docs", "config", "tests", ".github", ".agents", ".claude", "examples"}
872 routed_files = {"AGENTS.md", "CLAUDE.md", "CONTRIBUTING.md", "README.md", "Makefile", ".gitignore"}
873 if (candidate.parts and candidate.parts[0] in routed_roots) or path in routed_files:
874 return "documentation/configuration/test/tooling"
875 return "ignored/out of scope"
876
877
878def routes_for_path(path: str) -> dict[str, list[str]]:
879 """!
880 @brief Find declared capabilities, surfaces, contracts, and subsystems touching a path.
881 @param[in] path Repository-relative production path.
882 @return Nonempty routing categories and sorted identifiers.
883 """
884
885 family_hits = [
886 family["id"]
887 for family in records(FAMILIES, "families")
888 if path in {spec[0] for spec in family_source_specs(family)}
889 ]
890 surface_hits = [
891 surface["id"] for surface in records(FRESHNESS, "surfaces") if path in freshness_paths(surface)
892 ]
893 contract_hits = [
894 contract["id"]
895 for contract in records(CONTRACTS, "contracts")
896 if path in {source_path(source) for source in contract.get("authoritative_sources", [])}
897 ]
898 pages: set[str] = set()
899 for family in records(FAMILIES, "families"):
900 if family["id"] in family_hits:
901 pages.add(family["family_page"])
902 for surface in records(FRESHNESS, "surfaces"):
903 if surface["id"] in surface_hits:
904 pages.update(surface.get("owning_pages", []) + surface.get("dependent_pages", []))
905 for contract in records(CONTRACTS, "contracts"):
906 if contract["id"] in contract_hits:
907 pages.update(contract.get("canonical_documentation", []) + contract.get("dependent_pages", []))
908 subsystem_hits = [record["id"] for record in subsystems_for_pages(pages)]
909 routes = {
910 "capability": sorted(family_hits),
911 "surface": sorted(surface_hits),
912 "contract": sorted(contract_hits),
913 "subsystem": sorted(subsystem_hits),
914 }
915 return {kind: values for kind, values in routes.items() if values}
916
917
918def guide_fallback(path: str) -> str:
919 """!
920 @brief Return the nearest existing directory guide for an unrouted production path.
921 @param[in] path Repository-relative path.
922 @return Guide path or root instruction fallback.
923 """
924
925 current = (REPO_ROOT / path).parent
926 while current != REPO_ROOT and REPO_ROOT in current.parents:
927 guide = current / "guide.md"
928 if guide.is_file():
929 return guide.relative_to(REPO_ROOT).as_posix()
930 current = current.parent
931 return "AGENTS.md"
932
933
934def changed_mode(value: str) -> int:
935 """!
936 @brief Report declared routing coverage for current working-tree changes.
937 @param[in] value Supported selector, currently `working-tree`.
938 @return Process status code.
939 """
940
941 if value != "working-tree":
942 return unknown_identifier("changed-set", value, ["working-tree"])
943 paths, available = changed_paths()
944 routed: dict[str, dict[str, list[str]]] = {}
945 unrouted: list[str] = []
946 categories: dict[str, str] = {}
947 for path in sorted(paths):
948 category = classify_path(path)
949 if category == "production":
950 declared = routes_for_path(path)
951 if declared:
952 category = "routed production"
953 routed[path] = declared
954 else:
955 category = "unrouted production"
956 unrouted.append(path)
957 categories[path] = category
958
959 print("REVIEW PACKET: changed working-tree")
960 problems = [f"unrouted production path: {path}" for path in unrouted]
961 status = route_status(problems if available else ["Git working-tree metadata is unavailable"], not available)
962 print(" Coverage is advisory and covers staged, unstaged, and untracked nonignored paths.")
963 print(" Ignored files are intentionally excluded by Git.\n")
964 if not paths and available:
965 print("CHANGED PATHS\n (working tree clean)")
966 return status
967 print("CHANGED PATHS")
968 for path in sorted(paths):
969 states = ", ".join(sorted(paths[path]))
970 print(f" [{categories[path]}] {path} ({states})")
971 for kind, identifiers in routed.get(path, {}).items():
972 print(f" {kind}: {', '.join(identifiers)}")
973 if categories[path] == "unrouted production":
974 guide = guide_fallback(path)
975 root = Path(path).parts[0]
976 print(f" fallback guide: {guide}")
977 print(f" targeted search: rg -n \"<responsibility-or-symbol>\" {root}")
978 return status
979
980
981def page_mode(page_name: str) -> int:
982 """!
983 @brief Print the original page-scoped review packet with explicit route status and xrefs.
984 @param[in] page_name Page number, stem, or filename.
985 @return Process status code.
986 """
987
988 page = resolve_page(page_name)
989 stem = page.stem
990 page_contracts = contracts_for(stem)
991 families = families_for(stem)
992 scope_records = scope_records_for(stem)
993 sources: list[str] = []
994 specs: list[tuple[str, str, str]] = []
995 evidence: list[str] = []
996 for contract in page_contracts:
997 sources.extend(source_path(source) for source in contract.get("authoritative_sources", []))
998 for family in families:
999 family_specs = family_source_specs(family)
1000 specs.extend(family_specs)
1001 sources.extend(path for path, _, _ in family_specs)
1002 evidence.extend(evidence_sources(family))
1003 sources = sorted(set(sources))
1004 evidence = sorted(set(evidence))
1005 unresolved = [f"missing declared source {source}" for source in sources if not (REPO_ROOT / source).exists()]
1006
1007 print(f"REVIEW PACKET: {page.relative_to(REPO_ROOT)}")
1008 status = route_status(unresolved)
1009 print(f" {len(page.read_text(encoding='utf-8').splitlines())} lines\n")
1010 print("SOURCE SYMBOLS (read these, not whole files)")
1011 for symbol in [f"{path}::{symbol} ({kind})" for path, symbol, kind in specs] or ["(none declared)"]:
1012 print(f" {symbol}")
1013 print("\nAUTHORITATIVE FILES")
1014 for source in sources or ["(none declared)"]:
1015 print(f" {source}")
1016 print("")
1017 if stem == "71_Invariant_Contracts":
1018 print("THIS PAGE DOCUMENTS THE WHOLE REGISTRY")
1019 for contract in sorted(records(CONTRACTS, "contracts"), key=lambda item: (item["status"], item["id"])):
1020 print(f" [{contract['status']:8}] {contract['id']}")
1021 print("")
1022 print("INVARIANT CONTRACTS")
1023 for contract in page_contracts:
1024 role = "owns" if stem in contract.get("canonical_documentation", []) else "depends on"
1025 print(f" [{contract['status']}] {contract['id']} ({role} this page)")
1026 print(f" inspect: make review-packet CONTRACT={contract['id']}")
1027 if not page_contracts:
1028 print(" (none)")
1029 print("\nCAPABILITY FAMILIES ON THIS PAGE")
1030 for family in families:
1031 mode = "enforced" if family.get("coverage_enforced") else "advisory"
1032 print(f" {family['id']} ({mode}) - {len(family.get('value_metadata', {}))} declared values")
1033 if not families:
1034 print(" (none)")
1035 print("")
1036 if evidence:
1037 print("DECLARED EVIDENCE SOURCES")
1038 for source in evidence:
1039 print(f" {source}")
1040 print("")
1041 if scope_records:
1042 print("SCOPE RECORDS NAMING THIS PAGE")
1043 for record in scope_records:
1044 print(f" [{record.get('status')}] {record['id']} ({record.get('publication_policy')})")
1045 print("")
1046 print_freshness(freshness_for(stem), "FRESHNESS SURFACES ROUTING REVIEW HERE")
1047 print_xref(specs, sources)
1048 print("UNCOMMITTED CHANGES TO DECLARED SOURCES")
1049 print(uncommitted(sources))
1050 print("\nRECENT HISTORY")
1051 print(git_log(sources))
1052 print("\nVERIFY WITH")
1053 for command in sorted(set(make_targets(evidence))):
1054 print(f" {command}")
1055 print(" make audit-capability")
1056 print(" make audit-contracts")
1057 print(" make preview-docs")
1058 return status
1059
1060
1061def parse_args(argv: list[str]) -> argparse.Namespace:
1062 """!
1063 @brief Parse the mutually exclusive review-packet route selectors.
1064 @param[in] argv Arguments excluding the executable name.
1065 @return Parsed command-line namespace.
1066 """
1067
1068 parser = argparse.ArgumentParser(
1069 description="Assemble everything a reviewer needs for one declared PICurv route."
1070 )
1071 group = parser.add_mutually_exclusive_group(required=True)
1072 group.add_argument("page", nargs="?", help="Page number, stem, or filename (for example 44).")
1073 group.add_argument("--contract", help="Invariant contract id.")
1074 group.add_argument("--capability", help="Capability family id.")
1075 group.add_argument("--subsystem", help="Subsystem lifecycle id.")
1076 group.add_argument("--surface", help="Freshness surface id.")
1077 group.add_argument("--changed", help="Changed-set selector; currently working-tree.")
1078 return parser.parse_args(argv)
1079
1080
1081def main(argv: list[str] | None = None) -> int:
1082 """!
1083 @brief Dispatch the requested bounded route and preserve differentiated exit states.
1084 @param[in] argv Optional arguments excluding the executable name.
1085 @return Zero for complete, two for invalid, and three for incomplete or unavailable.
1086 """
1087
1088 args = parse_args(sys.argv[1:] if argv is None else argv)
1089 if args.contract:
1090 return contract_mode(args.contract)
1091 if args.capability:
1092 return capability_mode(args.capability)
1093 if args.subsystem:
1094 return subsystem_mode(args.subsystem)
1095 if args.surface:
1096 return surface_mode(args.surface)
1097 if args.changed:
1098 return changed_mode(args.changed)
1099 return page_mode(args.page)
1100
1101
1102if __name__ == "__main__":
1103 raise SystemExit(main())
int surface_mode(str surface_id)
Join one freshness surface across watched paths, pages, contracts, families, and subsystems.
list[dict] records(Path path, str key)
Read one registry list.
int changed_mode(str value)
Report declared routing coverage for current working-tree changes.
None print_measurements(list records)
Print the measurements already recorded against this route.
Path resolve_page(str name)
Resolve a page argument, accepting a number, stem, or filename.
str guide_fallback(str path)
Return the nearest existing directory guide for an unrouted production path.
str classify_path(str path)
Classify a changed path for routing coverage.
int subsystem_mode(str subsystem_id)
Join one subsystem across capability families, obligation pages, contracts, and evidence.
str canonical_page(str reference)
Resolve a registry page reference to its filename stem when declared by Doxygen id.
argparse.Namespace parse_args(list[str] argv)
Parse the mutually exclusive review-packet route selectors.
list[str] symbols_for(dict family)
Named symbols a capability family depends on, rather than whole files.
int contract_mode(str contract_id)
Print a review packet for one invariant contract.
str module_path(str module)
Convert a Python module identifier to its repository path.
list[dict] scope_records_for(str page_stem)
Scope records that name a page as a disclosure or activation surface.
list[dict] subsystems_for_pages(set[str] page_stems)
Find subsystem records routed through any supplied page.
str _freshness_digest(list[str] paths)
Digest matching audit_freshness.digest_of.
str format_xref_node(dict index, str refid)
Format one indexed symbol with its definition location.
tuple[dict[str, set[str]], bool] changed_paths()
Collect staged, unstaged, and untracked nonignored paths from Git.
None print_freshness(list[tuple[str, dict]] surfaces, str heading)
Print freshness routing with current attestation state.
int route_status(list[str] unresolved, bool unavailable=False)
Print the fixed route state and any unresolved declarations.
str source_path(str source)
Extract a repository path from a path-or-symbol source declaration.
set[str] record_pages(dict record)
Collect obligation and concern pages from a subsystem record.
list recorded_measurements(list[str] sources)
Measurement records cited by declared evidence sources.
str freshness_state(dict surface)
Whether a surface is current, suspect, or never attested.
int page_mode(str page_name)
Print the original page-scoped review packet with explicit route status and xrefs.
list[str] make_targets(list[str] sources)
Make targets named by declared evidence sources.
list[tuple[str, str, str]] family_source_specs(dict family)
Resolve a capability family's declared source paths and symbols.
tuple[str, dict|None] load_xref()
Load the optional cross-reference index only when its dirty-byte stamp is current.
int main(list[str]|None argv=None)
Dispatch the requested bounded route and preserve differentiated exit states.
list[str] freshness_paths(dict surface)
Return the artifact or watched paths that define a freshness surface.
str git_log(list[str] paths)
Recent history for a set of paths, for spotting what changed underneath a route.
None print_xref(list[tuple[str, str, str]] specs, list[str]|None paths=None)
Print bounded direct and two-hop Doxygen reference evidence for a route.
dict[str, str] page_aliases()
Map both filenames and Doxygen page identifiers to published page stems.
str uncommitted(list[str] paths)
Current uncommitted status for declared sources.
int capability_mode(str capability_id)
Join one capability family across symbols, pages, contracts, evidence, and subsystems.
list[str] xref_matches(dict index, str path, str symbol)
Match a declared path and symbol to Doxygen member identifiers.
list[dict] contracts_for(str page_stem)
Invariant contracts that own or depend on a page.
list[dict] families_for(str page_stem)
Capability families whose entries live on a page.
int unknown_identifier(str kind, str identifier, list[str] known)
Report an invalid registry identifier with close matches and known ids.
list[tuple[str, dict]] freshness_for(str page_stem)
Freshness surfaces that route review to a page.
list[str] evidence_sources(dict family)
Declared evidence sources across a family's values.
dict[str, list[str]] routes_for_path(str path)
Find declared capabilities, surfaces, contracts, and subsystems touching a path.
Head of a generic C-style linked list.
Definition variables.h:475