Report and enforce concept-declaration coverage (T03)
validation-coverage gains a concept_declaration block: declared, defined in prose, undeclared, ratio, silent artifacts, and the extraction limit stated in words. The ratio sits slightly above one because seed-concept lists and YAML payload concepts are declared but not extractable, and saying so in the report is better than a number that looks complete. Two checks carry different weights. concept_declaration_missing is an error: an artifact that defines concepts and declares none, with the kernel map exempt by name because it assigns concepts rather than defining them. Zero today, so a new artifact added without declarations fails. concept_defined_without_owner is a warning over concepts no artifact declares; a name another artifact owns is an import rather than a gap, which keeps the warning from firing 57 times and training reviewers to ignore it. Three warnings today and each is real: the Organization Model defines eleven concepts nobody owns, CARING four including Effective Access and Declared Access, and the Capability Model two. They are carried as T07 rather than declared in passing, because declaring without a boundary review is the mistake this workplan exists to fix. make check passes with 53 tests, clean validation and three warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 3588@bnt-lap001 Assistant-Session: 24b80f66-e5a7-4e61-99fe-2d422e6d17da
This commit is contained in:
parent
772422553f
commit
a28526e540
4 changed files with 140 additions and 3 deletions
|
|
@ -57,10 +57,27 @@ def coverage(context: Any) -> dict:
|
|||
schema = bindings.get(artifact.kind)
|
||||
rows.append({"id": artifact.id, "kind": artifact.kind, "schema": schema,
|
||||
"checks": "schema-and-references" if schema else "structural-or-specialized"})
|
||||
from .maintenance import concept_candidates
|
||||
|
||||
concepts = concept_candidates(context)
|
||||
declared = concepts["declared_total"]
|
||||
candidates = concepts["candidate_total"]
|
||||
return {"ok": True, "artifacts": rows,
|
||||
"specialized": ["capability catalog/record", "practice pattern",
|
||||
"emission cadence example", "consumer packs"],
|
||||
"not_proven": ["ownership of concepts only in prose", "all Markdown links",
|
||||
"concept_declaration": {
|
||||
"declared": declared,
|
||||
"defined_in_prose": candidates,
|
||||
"undeclared": concepts["undeclared_total"],
|
||||
"ratio": round(declared / candidates, 3) if candidates else None,
|
||||
"silent_artifacts": concepts["silent_artifacts"],
|
||||
"measured_by": "maintenance.concept_candidates",
|
||||
"limit": "Extraction reads bold, numbered-heading and concept-table forms. "
|
||||
"Concepts listed in seed-concept blocks or defined in YAML payloads "
|
||||
"are declared but not extracted, so the ratio may exceed one.",
|
||||
},
|
||||
"not_proven": ["ownership of concepts defined outside the extracted forms",
|
||||
"all Markdown links",
|
||||
"consumer adoption", "cross-version interoperability",
|
||||
"CARING effective access", "mapping rationale completeness"]}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,9 @@ def validate_canon(root: Path | str | None = None) -> dict[str, Any]:
|
|||
ownership = generation.concept_ownership(context)
|
||||
errors.extend(dict(item, code="concept_ownership_conflict")
|
||||
for item in ownership["ownership_conflicts"])
|
||||
declaration = concept_declaration_checks(context, ownership)
|
||||
errors.extend(declaration["errors"])
|
||||
warnings.extend(declaration["warnings"])
|
||||
|
||||
return {
|
||||
"ok": not errors,
|
||||
|
|
@ -241,6 +244,36 @@ def validate_canon(root: Path | str | None = None) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
#: The kernel map assigns concepts to owners rather than defining them, so it is
|
||||
#: the one artifact allowed to define concept names and declare none.
|
||||
DECLARATION_EXEMPT = {"kernel/itc-kernel-map"}
|
||||
|
||||
|
||||
def concept_declaration_checks(context, ownership: dict) -> dict:
|
||||
"""Undeclared prose is a warning; defining concepts and declaring none is an error.
|
||||
|
||||
A defined name another artifact owns is an import, not a gap, so only names
|
||||
no artifact declares are reported.
|
||||
"""
|
||||
from .maintenance import concept_candidates
|
||||
|
||||
owned = {generation._normalize_concept(item["concept"]) for item in ownership["concepts"]}
|
||||
errors, warnings = [], []
|
||||
for item in concept_candidates(context)["artifacts"]:
|
||||
unowned = [name for name in item["undeclared"]
|
||||
if generation._normalize_concept(name) not in owned]
|
||||
if not item["declares_frontmatter"] and item["candidate_count"] \
|
||||
and item["artifact"] not in DECLARATION_EXEMPT:
|
||||
errors.append({"code": "concept_declaration_missing",
|
||||
"artifact_id": item["artifact"], "path": item["path"],
|
||||
"defined": item["candidate_count"]})
|
||||
if unowned:
|
||||
warnings.append({"code": "concept_defined_without_owner",
|
||||
"artifact_id": item["artifact"], "path": item["path"],
|
||||
"concepts": unowned})
|
||||
return {"errors": errors, "warnings": warnings}
|
||||
|
||||
|
||||
def write_validation_report(
|
||||
destination: Path | str,
|
||||
root: Path | str | None = None,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue