2"""Report public selector surfaces that no capability family covers."""
4from __future__
import annotations
10from pathlib
import Path
13REPO_ROOT = Path(__file__).resolve().parents[2]
14REGISTRY = REPO_ROOT /
"tests" /
"tooling" /
"capability_families.json"
15CENSUS = REPO_ROOT /
"tests" /
"tooling" /
"family_census.json"
21SCANNED_MODULE_ROOTS = (
"picurv_cli/core.py",
"picurv_cli/storage",
"picurv_cli/cli.py")
26 @brief Resolve the module roots into concrete files and their public dotted names.
27 @return List of `(repo-relative path, dotted module name)` pairs.
30 for root
in SCANNED_MODULE_ROOTS:
31 path = REPO_ROOT / root
33 dotted = root.replace(
"/",
".")
34 for child
in sorted(path.glob(
"*.py")):
35 if child.name !=
"__init__.py":
36 resolved.append((str(child.relative_to(REPO_ROOT)), dotted))
38 resolved.append((root, root.replace(
"/",
".")[: -len(
".py")]))
44 @brief Public surfaces already covered by a registered capability family.
45 @return Set of `module::symbol` identifiers.
47 registry = json.loads(REGISTRY.read_text(encoding=
"utf-8"))
49 for family
in registry[
"families"]:
50 surface = family.get(
"public_surface", {})
51 if surface.get(
"module")
and surface.get(
"symbol"):
52 covered.add(f
"{surface['module']}::{surface['symbol']}")
61CHOICE_SET_SUFFIXES = (
62 "MAP",
"SPECS",
"MODES",
"TYPES",
"TASKS",
"CHOICES",
"POLICIES",
63 "STRUCTURES",
"MODELS",
"OUTPUTS",
"FORMATS",
"SPELLINGS",
"EXTENSIONS",
64 "SYMBOLS",
"NAMES",
"PROFILES",
"METHODS",
"OPERATORS",
"KINDS",
"SPELLINGS",
66_CHOICE_SET_PATTERN = re.compile(
r"[A-Z0-9_]+(" +
"|".join(CHOICE_SET_SUFFIXES) +
r")$")
71 @brief Public choice points in the CLI, found independently of the registry.
73 @details A `normalize_*` function, or a module-level constant whose name ends in one
74 of CHOICE_SET_SUFFIXES, is how this codebase exposes a user-selectable set.
75 Finding them independently of the registry is the point: a registry that
76 only checks itself cannot report a family nobody registered.
77 @return Mapping of `module::symbol` to a short description.
81 path = REPO_ROOT / module
82 if not path.is_file():
84 tree = ast.parse(path.read_text(encoding=
"utf-8"))
85 for node
in tree.body:
86 if isinstance(node, ast.FunctionDef)
and re.fullmatch(
r"normalize_\w+", node.name):
87 found[f
"{dotted}::{node.name}"] =
"normalizer"
88 elif isinstance(node, ast.Assign):
89 for target
in node.targets:
90 if isinstance(target, ast.Name)
and _CHOICE_SET_PATTERN.fullmatch(target.id):
91 found[f
"{dotted}::{target.id}"] =
"choice set"
99VALID_CLASSIFICATIONS = (
101 "parameter_of_entry",
104 "cli_inventory_sufficient",
109NEEDS_OWNER = (
"parameter_of_entry",
"spelling_alias")
114 @brief Normalize a census entry into a typed classification and reason.
116 @details Older entries were free-text, which let a pending public family be
117 summarized as "not a public family". A typed record cannot do that.
118 @param[in] entry Census entry, either a mapping or a legacy string.
119 @return Tuple of (classification, reason).
121 if isinstance(entry, dict):
122 return entry.get(
"classification",
""), entry.get(
"reason",
"")
124 if "not a public capability family" in text:
125 return "not_public", text
126 return "public_pending", text
131 @brief Report uncovered public selector surfaces, typed.
133 @details Fails when a surface is undiscovered, when a classification is invalid,
134 when a `not_public` entry gives no reason, or when any public family
135 remains pending. Publication cannot proceed while a public family is
137 @return Process status code.
139 census = json.loads(CENSUS.read_text(encoding=
"utf-8"))
if CENSUS.is_file()
else {}
140 acknowledged = census.get(
"acknowledged_uncovered", {})
144 uncovered = {k: v
for k, v
in candidates.items()
if k
not in covered}
145 unacknowledged = sorted(k
for k
in uncovered
if k
not in acknowledged)
146 stale = sorted(k
for k
in acknowledged
if k
not in candidates)
149 not_public: list = []
151 for key, entry
in sorted(acknowledged.items()):
153 if classification
not in VALID_CLASSIFICATIONS:
154 malformed.append(f
"{key}: classification '{classification}' is not one of {list(VALID_CLASSIFICATIONS)}")
156 if classification !=
"public_pending" and not reason.strip():
158 f
"{key}: classified {classification} but gives no reason. Every "
159 f
"classification other than public_pending is a claim, and a claim owes "
163 owner = entry.get(
"owner")
if isinstance(entry, dict)
else None
164 if classification
in NEEDS_OWNER
and not owner:
166 f
"{key}: classified {classification} but names no owner. Say which "
167 f
"capability entry or canonical value it belongs to"
170 (pending
if classification ==
"public_pending" else not_public).append(key)
174 problems += [f
"census acknowledges '{k}', which no longer exists" for k
in stale]
176 problems += [f
"'{k}' ({candidates[k]}) has no capability family and no census entry" for k
in unacknowledged]
177 problems += malformed
180 print(
"Capability family census violations:", file=sys.stderr)
181 for problem
in problems:
182 print(f
" {problem}", file=sys.stderr)
184 "\nRegister a family in capability_families.json, or classify the surface in\n"
185 "family_census.json as one of "
186 +
", ".join(VALID_CLASSIFICATIONS) +
",\nwith a reason. An unregistered "
187 "family is invisible to every other gate.",
192 from collections
import Counter
194 breakdown =
", ".join(f
"{count} {name}" for name, count
in sorted(spread.items()))
196 f
"Family census: {len(covered)} covered by a family; {breakdown} "
197 f
"({len(candidates)} surfaces examined)."
200 print(
"\nPublic capability families still awaiting Tier-2 backfill:", file=sys.stderr)
202 print(f
" {key}", file=sys.stderr)
204 "\nThese are public choice points with no documented capability entries.\n"
205 "Publication cannot proceed while any public family is pending.",
212if __name__ ==
"__main__":
213 raise SystemExit(
main())
tuple classification_of(entry)
Normalize a census entry into a typed classification and reason.
scanned_modules()
Resolve the module roots into concrete files and their public dotted names.
set declared_surfaces()
Public surfaces already covered by a registered capability family.
dict candidate_surfaces()
Public choice points in the CLI, found independently of the registry.
int main()
Report uncovered public selector surfaces, typed.