2"""Enforce the documentation obligations each subsystem lifecycle status carries."""
4from __future__
import annotations
9from pathlib
import Path
12REPO_ROOT = Path(__file__).resolve().parents[2]
13RECORDS = REPO_ROOT /
"tests" /
"tooling" /
"subsystem_records.json"
14PAGE_TYPES = REPO_ROOT /
"tests" /
"tooling" /
"page_types.json"
15FAMILIES = REPO_ROOT /
"tests" /
"tooling" /
"capability_families.json"
16PAGE_DIRS = (
"docs/pages",
"docs")
21LADDER = (
"planned",
"internal",
"experimental",
"supported")
24 "planned": (
"purpose",
"intended_scope",
"design_owner",
"not_implemented_status"),
25 "internal": (
"scope_boundary",
"architecture_boundary",
"dependencies",
26 "developer_entry_points"),
27 "experimental": (
"configuration",
"selection_guidance",
"observability",
"limitations",
28 "safe_use_boundaries"),
29 "supported": (
"evidence",
"lifecycle_and_restart",
"operations",
"troubleshooting",
30 "examples",
"complete_reference"),
35TERMINAL_OBLIGATIONS = {
36 "known-defective": (
"defect_disclosure",
"defect_scope_record",
"safe_use_boundaries",
38 "deprecated": (
"migration_path",
"replacement",
"compatibility_period",
"removal_policy"),
39 "removed": (
"history_record",
"rejection_behavior"),
43NON_INHERITED = (
"not_implemented_status",)
44NEEDS_PEAK = (
"known-defective",
"deprecated",
"removed")
46INHERITS_LADDER = (
"known-defective",
"deprecated")
48VALID_STATUSES = tuple(LADDER) + tuple(TERMINAL_OBLIGATIONS)
69 None: set(VALID_STATUSES) - {
"removed"},
70 "planned": {
"planned",
"internal",
"experimental",
"supported",
"removed"},
71 "internal": {
"internal",
"experimental",
"supported",
"known-defective",
"deprecated"},
72 "experimental": {
"experimental",
"supported",
"known-defective",
"deprecated"},
73 "supported": {
"supported",
"experimental",
"known-defective",
"deprecated"},
74 "known-defective": {
"known-defective",
"planned",
"experimental",
"supported",
"deprecated",
"removed"},
75 "deprecated": {
"deprecated",
"removed"},
76 "removed": {
"removed"},
82DEMOTIONS = {(
"supported",
"experimental"), (
"known-defective",
"planned")}
84VALID_VISIBILITY = (
"internal",
"public")
88 "numerical_method",
"user_selector",
"units_and_nondimensionalization",
89 "artifact_topology",
"persistent_restart_state",
"determinism_and_reproducibility",
90 "mpi_distributed_execution",
"external_service",
"security_credentials",
91 "generated_artifact",
"file_format",
"destructive_scope",
"backward_compatibility",
92 "scientific_verification",
95RECORD_KEYS = {
"id",
"title",
"status",
"visibility",
"previous_status",
"peak_status",
96 "proposed_status",
"promotion_rationale",
"demotion_reason",
97 "capability_families",
98 "obligations",
"concerns",
"note"}
101FILLER_REASONS = {
"n/a",
"na",
"none",
"not applicable",
"no",
"-",
"tbd"}
106 @brief Every documentation page id with the anchors it defines.
107 @return Mapping of page id to the set of `@section`, `@subsection`, and `@anchor` names.
110 for directory
in PAGE_DIRS:
111 for markdown
in sorted((REPO_ROOT / directory).glob(
"*.md")):
112 text = markdown.read_text(encoding=
"utf-8")
113 match = re.search(
r"^@page\s+(\S+)", text, re.M)
114 if not match
or match.group(1).startswith(
"<"):
116 anchors = set(re.findall(
r"^@(?:section|subsection|subsubsection|anchor)\s+(\S+)",
118 index[match.group(1)] = anchors
124 @brief The obligation ids a record owes.
126 @details A record may declare a `proposed_status` above its claimed one. That is
127 how a subsystem documented to a higher bar waits for the owner to decide
128 whether it has actually earned it: obligations are checked against the
129 proposal, so the writing is held to the higher standard, while the status
130 the documentation publishes stays conservative until a human agrees.
131 @param[in] record One subsystem record.
132 @return Ordered tuple of obligation ids.
134 status = record.get(
"proposed_status")
or record.get(
"status")
137 for rung
in LADDER[: LADDER.index(status) + 1]:
138 owed.extend(LADDER_OBLIGATIONS[rung])
140 if status
in INHERITS_LADDER:
141 peak = record.get(
"peak_status")
143 for rung
in LADDER[: LADDER.index(peak) + 1]:
144 owed.extend(LADDER_OBLIGATIONS[rung])
145 owed.extend(TERMINAL_OBLIGATIONS.get(status, ()))
146 if status !=
"planned":
147 owed = [key
for key
in owed
if key
not in NON_INHERITED]
149 return tuple(dict.fromkeys(owed))
153 public: bool) -> list:
155 @brief Verify one obligation or concern is genuinely answered.
157 @details An answer is a documentation reference that resolves, or a stated reason
158 for non-applicability, or a literal value where the obligation is a fact
159 rather than prose. An empty or filler answer is a violation.
160 @param[in] context Record id, for messages.
161 @param[in] key Obligation or concern id.
162 @param[in] spec The declared answer.
163 @param[in] pages Page index from page_index().
164 @param[in] published Ids of the pages the site publishes.
165 @param[in] public Whether the subsystem is publicly visible.
166 @return List of violation strings.
168 if not isinstance(spec, dict):
169 return [f
"{context}: '{key}' must be an object, got {type(spec).__name__}"]
171 if "not_applicable" in spec:
172 reason = str(spec[
"not_applicable"]).strip()
173 if reason.lower().rstrip(
".")
in FILLER_REASONS
or len(reason) < MIN_REASON_CHARS:
175 f
"{context}: '{key}' is declared not applicable without a stated reason "
176 f
"({reason!r}). A reasoned N/A is accepted; a bare one is not"
180 if not str(spec[
"value"]).strip():
181 problems.append(f
"{context}: '{key}' declares an empty value")
183 page = spec.get(
"page")
186 f
"{context}: '{key}' is unsatisfied - give it a page reference, a value, or a "
187 f
"reasoned not_applicable"
190 if page
not in pages:
191 problems.append(f
"{context}: '{key}' cites page '{page}', which does not exist")
193 anchor = spec.get(
"anchor")
194 if anchor
and anchor
not in pages[page]:
196 f
"{context}: '{key}' cites anchor '{anchor}' on page '{page}', which does not "
199 if public
and page
not in published:
201 f
"{context}: '{key}' cites '{page}', which is not a published page, but the "
202 f
"subsystem is publicly visible"
207def validate(records: list, pages: dict, families: dict, published: set) -> list:
209 @brief Validate every subsystem record against the lifecycle contract.
210 @param[in] records Subsystem records.
211 @param[in] pages Page index from page_index().
212 @param[in] families Capability family metadata, keyed by family id.
213 @param[in] published Ids of the pages the site publishes.
214 @return List of violation strings; empty means the contract holds.
218 for record
in records:
219 identifier = record.get(
"id")
221 problems.append(
"a record declares no id")
223 if identifier
in seen:
224 problems.append(f
"{identifier}: declared more than once")
227 unknown = set(record) - RECORD_KEYS
229 problems.append(f
"{identifier}: unknown field(s) {sorted(unknown)}")
231 status = record.get(
"status")
232 if status
not in VALID_STATUSES:
234 f
"{identifier}: status {status!r} is not one of {list(VALID_STATUSES)}"
238 visibility = record.get(
"visibility")
239 if visibility
not in VALID_VISIBILITY:
241 f
"{identifier}: visibility {visibility!r} is not one of "
242 f
"{list(VALID_VISIBILITY)}"
245 if status ==
"internal" and visibility !=
"internal":
247 f
"{identifier}: status 'internal' contradicts visibility 'public'; a "
248 f
"subsystem the user can reach is at least experimental"
251 previous = record.get(
"previous_status",
None)
252 if previous
is not None and previous
not in VALID_STATUSES:
253 problems.append(f
"{identifier}: previous_status {previous!r} is not a valid status")
254 elif status
not in TRANSITIONS[previous]:
256 f
"{identifier}: {previous or 'a new record'} -> '{status}' is not a valid "
257 f
"lifecycle transition (allowed: {sorted(TRANSITIONS[previous])})"
260 reason = record.get(
"demotion_reason")
261 if (previous, status)
in DEMOTIONS:
262 text = (reason
or "").strip()
263 if text.lower().rstrip(
".")
in FILLER_REASONS
or len(text) < MIN_REASON_CHARS:
265 f
"{identifier}: {previous} -> '{status}' is a demotion and needs a "
266 f
"demotion_reason saying what the earlier claim did not establish"
268 elif reason
is not None:
270 f
"{identifier}: demotion_reason applies only to a demotion "
271 f
"({sorted(DEMOTIONS)}), not {previous or 'a new record'} -> '{status}'"
274 proposed = record.get(
"proposed_status")
275 if proposed
is not None:
276 if proposed
not in LADDER:
278 f
"{identifier}: proposed_status {proposed!r} must name a rung of "
281 elif status
not in LADDER
or LADDER.index(proposed) <= LADDER.index(status):
283 f
"{identifier}: proposed_status '{proposed}' is not above the claimed "
284 f
"status '{status}'; a proposal that is not a promotion is noise"
286 elif not record.get(
"promotion_rationale"):
288 f
"{identifier}: proposes '{proposed}' but gives no promotion_rationale. "
289 f
"Say what the owner is being asked to confirm"
292 if status
in NEEDS_PEAK:
293 peak = record.get(
"peak_status")
294 if peak
not in LADDER:
296 f
"{identifier}: status '{status}' requires peak_status naming the highest "
297 f
"rung it reached, one of {list(LADDER)}"
299 elif record.get(
"peak_status")
is not None:
301 f
"{identifier}: peak_status applies only to {list(NEEDS_PEAK)}, not '{status}'"
304 obligations = record.get(
"obligations")
or {}
307 if key
not in obligations:
309 f
"{identifier}: status '{status}' owes '{key}', which is not declared"
314 visibility ==
"public")
316 for key
in sorted(set(obligations) - set(owed)):
318 f
"{identifier}: declares obligation '{key}', which status '{status}' does not "
319 f
"owe; remove it or claim the status that owes it"
322 concerns = record.get(
"concerns")
or {}
323 for key
in sorted(concerns):
324 if key
not in VALID_CONCERNS:
326 f
"{identifier}: concern '{key}' is not in the concern vocabulary of "
327 f
"64_Documentation_Extension_Framework"
332 visibility ==
"public")
336 for family_id
in record.get(
"capability_families", []):
337 family = families.get(family_id)
340 f
"{identifier}: cites capability family '{family_id}', which is not "
344 if status !=
"planned":
347 name
for name, meta
in family.get(
"value_metadata", {}).items()
348 if (meta
or {}).get(
"status") ==
"supported"
352 f
"{identifier}: is 'planned' but family '{family_id}' already offers "
353 f
"supported value(s) {live}; a planned subsystem must not appear as "
354 f
"supported behavior"
361 @brief Check that every capability value has an owning subsystem it does not outrank.
363 @details A value's status and its subsystem's status used to be independent axes,
364 which let a value read `supported` under a subsystem still `experimental`:
365 the Newton-Krylov solver value, the periodic handlers, and every storage
366 policy all did. The subsystem status is now a ceiling. The rules need the
367 whole registry, so they are kept out of the per-record `validate()`:
369 - every registered family is listed by at least one subsystem record;
370 - a family listed by several subsystems names each canonical value's owner
371 in `value_metadata[*].subsystem`, and that owner must list the family;
372 - a value may not claim a ladder rung above its owner's, and under an owner
373 that is off the ladder it must share the owner's status or be removed;
374 - `off_switch: true` exempts the single value per family that disables
375 the owning subsystem, since selecting it claims nothing about it.
377 Accepted spellings and deprecated aliases inherit their target's owner and
378 are not checked separately.
379 @param[in] records Every subsystem record.
380 @param[in] families Capability family metadata, keyed by family id.
381 @return List of violation strings; empty means every value sits under its owner.
384 status_of = {record.get(
"id"): record.get(
"status")
for record
in records}
386 for record
in records:
387 for family_id
in record.get(
"capability_families", []):
388 listed_by.setdefault(family_id, []).append(record.get(
"id"))
390 for family_id, family
in sorted(families.items()):
391 owners = listed_by.get(family_id, [])
394 f
"{family_id}: no subsystem lists this family, so its values have no "
395 f
"lifecycle above them; add it to the owning record's capability_families"
399 for name, meta
in sorted(family.get(
"value_metadata", {}).items()):
401 if meta.get(
"spelling_of")
or meta.get(
"alias_of"):
403 owner = meta.get(
"subsystem")
407 f
"{family_id}: '{name}' belongs to a family listed by {sorted(owners)}; "
408 f
"name its owner in value_metadata.subsystem"
412 elif owner
not in owners:
414 f
"{family_id}: '{name}' names subsystem '{owner}', which does not list "
418 if "off_switch" in meta:
419 if meta[
"off_switch"]
is not True:
421 f
"{family_id}: '{name}' declares off_switch {meta['off_switch']!r}; "
422 f
"it is either true or absent"
424 off_switches.append(name)
426 owner_status = status_of.get(owner)
427 value_status = meta.get(
"status")
428 if value_status
in LADDER
and owner_status
in LADDER:
429 if LADDER.index(value_status) > LADDER.index(owner_status):
431 f
"{family_id}: '{name}' claims '{value_status}' but its subsystem "
432 f
"'{owner}' is '{owner_status}'; a value cannot outrank the "
433 f
"subsystem it belongs to"
435 elif owner_status
not in LADDER
and value_status
not in (owner_status,
"removed"):
437 f
"{family_id}: '{name}' is '{value_status}' under '{owner}', which is "
438 f
"'{owner_status}'; under an owner off the ladder a value shares its "
439 f
"status or is removed"
441 if len(off_switches) > 1:
443 f
"{family_id}: {off_switches} are all marked off_switch; a family has at "
444 f
"most one value that disables its subsystem"
451 @brief Report subsystem lifecycle violations.
452 @return Process status code.
454 document = json.loads(RECORDS.read_text(encoding=
"utf-8"))
455 records = document[
"subsystems"]
456 published = set(json.loads(PAGE_TYPES.read_text(encoding=
"utf-8"))[
"assignments"])
459 for family
in json.loads(FAMILIES.read_text(encoding=
"utf-8"))[
"families"]
464 print(
"Subsystem lifecycle violations:", file=sys.stderr)
465 for problem
in problems:
466 print(f
" {problem}", file=sys.stderr)
468 "\nObligations grow with the status a subsystem claims. See\n"
469 "64_Documentation_Extension_Framework section 4. Lower the claimed status, or\n"
470 "write the documentation that status owes - do not add empty prose to pass.",
475 from collections
import Counter
476 spread = Counter(record[
"status"]
for record
in records)
477 summary =
", ".join(f
"{count} {status}" for status, count
in sorted(spread.items()))
478 print(f
"Subsystem lifecycle audit passed: {len(records)} subsystem(s) ({summary}).")
479 proposals = [r
for r
in records
if r.get(
"proposed_status")]
481 print(f
"\n{len(proposals)} promotion(s) awaiting the owner's decision. The "
482 f
"documentation meets the higher bar; whether the subsystem has earned "
483 f
"the status is a human judgement this gate cannot make:")
484 for record
in proposals:
485 print(f
" {record['id']}: {record['status']} -> {record['proposed_status']}")
486 print(f
" {record['promotion_rationale']}")
490if __name__ ==
"__main__":
491 raise SystemExit(
main())
dict page_index()
Every documentation page id with the anchors it defines.
list check_satisfaction(str context, str key, spec, dict pages, set published, bool public)
Verify one obligation or concern is genuinely answered.
list validate_value_ownership(list records, dict families)
Check that every capability value has an owning subsystem it does not outrank.
tuple required_obligations(dict record)
The obligation ids a record owes.
int main()
Report subsystem lifecycle violations.
list validate(list records, dict pages, dict families, set published)
Validate every subsystem record against the lifecycle contract.