Add repo-scoping comparison assets

This commit is contained in:
2026-05-23 06:07:18 +02:00
parent 03c9aaf3d1
commit 8f4008ec8f
32 changed files with 1530 additions and 74 deletions

View File

@@ -10,13 +10,17 @@ import yaml
GENERATED_NOTICE = "<!-- GENERATED by info_tech_canon; do not edit by hand. -->"
RETRIEVAL_ARTIFACT_KINDS = {
"benefit-analysis",
"capture-criteria",
"comparison-frame",
"comparison-report",
"concept-catalog",
"conformance-pack",
"consumer-workplan-brief",
"evaluation-pack",
"evaluation-question-set",
"example",
"extension-candidate-set",
"interface-card-expectation",
"kernel",
"mapping",
@@ -761,6 +765,10 @@ def _render_consumer_brief(consumer_id: str) -> str:
"models/network/InfoTechCanonNetworkModel.md",
],
"repo-scoping": [
"evaluations/repo-scoping/comparison-report.md",
"evaluations/repo-scoping/comparison-frame.yaml",
"evaluations/repo-scoping/canon-benefit-analysis.yaml",
"evaluations/repo-scoping/extension-candidates.yaml",
"models/governance/InfoTechCanonPurposeDemandExtension.md",
"patterns/intent-scope-purposes.md",
"agent/templates/canon-interface-card.template.yaml",
@@ -861,8 +869,14 @@ def _safe_id(value: str) -> str:
def _summary_for_artifact(artifact: Any) -> str:
if artifact.kind == "profile-artifact":
return f"Example artifact for the {artifact.provenance.get('profile', 'unknown')} profile: {artifact.title}."
if artifact.kind == "benefit-analysis":
return f"Consumer benefit analysis against canon surfaces: {artifact.title}."
if artifact.kind == "capture-criteria":
return f"Criteria for canonical entity and edge capture: {artifact.title}."
if artifact.kind == "comparison-frame":
return f"Structured comparison questions and domains: {artifact.title}."
if artifact.kind == "comparison-report":
return f"Canon-side comparison report: {artifact.title}."
if artifact.kind == "concept-catalog":
return f"Structured candidate concept catalog: {artifact.title}."
if artifact.kind == "conformance-pack":
@@ -875,6 +889,8 @@ def _summary_for_artifact(artifact: Any) -> str:
return f"Structured canon evaluation question set: {artifact.title}."
if artifact.kind == "example":
return f"Canon-side example artifact: {artifact.title}."
if artifact.kind == "extension-candidate-set":
return f"Reviewable canon extension candidate set: {artifact.title}."
if artifact.kind == "interface-card-expectation":
return f"Expected Canon Interface Card fields and mappings: {artifact.title}."
if artifact.kind == "mapping":

View File

@@ -53,7 +53,10 @@ REQUIRED_SCHEMAS = (
)
RETRIEVAL_BRIEF_KINDS = {
"benefit-analysis",
"capture-criteria",
"comparison-frame",
"comparison-report",
"concept-catalog",
"conformance-pack",
"consumer-workplan-brief",
@@ -70,6 +73,7 @@ RETRIEVAL_BRIEF_KINDS = {
"profile-alignment",
"profile",
"standard",
"extension-candidate-set",
"visualization-example-set",
}
@@ -196,6 +200,49 @@ RAILIANCE_FABRIC_REQUIRED_MODELS = {
"model/security",
}
REPO_SCOPING_COMPARISON_ARTIFACT_IDS = {
"comparison/repo-scoping/canon-benefit-analysis",
"comparison/repo-scoping/consumer-workplan-brief",
"comparison/repo-scoping/extension-candidates",
"comparison/repo-scoping/frame",
"comparison/repo-scoping/report",
}
REPO_SCOPING_REQUIRED_DOMAINS = {
"repository-intent",
"current-scope",
"future-scope",
"consumers-and-purposes",
"decisions-and-evidence",
"risks-and-regressions",
"evolution-requests",
}
REPO_SCOPING_REQUIRED_MAPPINGS = {
"Scope",
"Ability",
"Capability",
"Feature",
"Evidence",
"ObservedFact",
"ReviewDecision",
"ExpectationGap",
"DependencyImpactAnalysis",
}
REPO_SCOPING_REQUIRED_EXTENSION_CANDIDATES = {
"extension/repository-intent-statement",
"extension/repository-scope-profile",
"extension/characteristic-claim",
"extension/evidence-link",
"extension/source-observation",
"extension/source-role",
"extension/utility-relationship",
"extension/scope-freshness",
"extension/propagation-impact",
"extension/scope-md-interface",
}
def structural_checks(context: Any) -> dict[str, list[dict[str, Any]]]:
errors: list[dict[str, Any]] = []
@@ -218,6 +265,11 @@ def structural_checks(context: Any) -> dict[str, list[dict[str, Any]]]:
context.infospace.artifacts,
errors,
)
_check_repo_scoping_comparison_assets(
context.infospace_root,
context.infospace.artifacts,
errors,
)
_check_optional_assets(context.infospace_root, warnings)
return {"errors": errors, "warnings": warnings}
@@ -948,6 +1000,173 @@ def _check_railiance_fabric_conformance_assets(
)
def _check_repo_scoping_comparison_assets(
infospace_root: Path,
artifacts: list[Any],
errors: list[dict[str, Any]],
) -> None:
artifact_ids = {artifact.id for artifact in artifacts}
for artifact_id in sorted(REPO_SCOPING_COMPARISON_ARTIFACT_IDS - artifact_ids):
errors.append(
{
"code": "missing_repo_scoping_comparison_artifact",
"artifact_id": artifact_id,
}
)
frame = _read_yaml(
infospace_root / "evaluations" / "repo-scoping" / "comparison-frame.yaml",
errors,
)
if isinstance(frame, dict):
domains = frame.get("comparison_domains") or []
if not isinstance(domains, list):
errors.append(
{
"code": "invalid_repo_scoping_comparison_domains",
"path": "infospace/evaluations/repo-scoping/comparison-frame.yaml",
}
)
else:
domain_ids = {
str(domain.get("id"))
for domain in domains
if isinstance(domain, dict) and domain.get("id")
}
for domain_id in sorted(REPO_SCOPING_REQUIRED_DOMAINS - domain_ids):
errors.append(
{
"code": "missing_repo_scoping_comparison_domain",
"domain": domain_id,
}
)
for domain in domains:
if not isinstance(domain, dict):
continue
if not domain.get("questions"):
errors.append(
{
"code": "empty_repo_scoping_comparison_domain",
"domain": domain.get("id"),
}
)
if not domain.get("canon_anchors"):
errors.append(
{
"code": "missing_repo_scoping_domain_canon_anchors",
"domain": domain.get("id"),
}
)
analysis = _read_yaml(
infospace_root
/ "evaluations"
/ "repo-scoping"
/ "canon-benefit-analysis.yaml",
errors,
)
if isinstance(analysis, dict):
mappings = analysis.get("candidate_mappings") or []
if not isinstance(mappings, list):
errors.append(
{
"code": "invalid_repo_scoping_candidate_mappings",
"path": "infospace/evaluations/repo-scoping/canon-benefit-analysis.yaml",
}
)
else:
mapped_concepts = {
str(mapping.get("repo_scoping_concept"))
for mapping in mappings
if isinstance(mapping, dict) and mapping.get("repo_scoping_concept")
}
for concept in sorted(REPO_SCOPING_REQUIRED_MAPPINGS - mapped_concepts):
errors.append(
{
"code": "missing_repo_scoping_candidate_mapping",
"concept": concept,
}
)
direct_reuse = analysis.get("direct_reuse") or []
if not isinstance(direct_reuse, list) or not direct_reuse:
errors.append(
{
"code": "missing_repo_scoping_direct_reuse",
"path": "infospace/evaluations/repo-scoping/canon-benefit-analysis.yaml",
}
)
profile_candidates = analysis.get("profile_candidates") or []
if not isinstance(profile_candidates, list) or not profile_candidates:
errors.append(
{
"code": "missing_repo_scoping_profile_candidates",
"path": "infospace/evaluations/repo-scoping/canon-benefit-analysis.yaml",
}
)
extensions = _read_yaml(
infospace_root
/ "evaluations"
/ "repo-scoping"
/ "extension-candidates.yaml",
errors,
)
if isinstance(extensions, dict):
if not extensions.get("candidate_set_status"):
errors.append(
{
"code": "missing_repo_scoping_candidate_set_status",
"path": "infospace/evaluations/repo-scoping/extension-candidates.yaml",
}
)
candidates = extensions.get("candidates") or []
if not isinstance(candidates, list):
errors.append(
{
"code": "invalid_repo_scoping_extension_candidates",
"path": "infospace/evaluations/repo-scoping/extension-candidates.yaml",
}
)
else:
candidate_ids = {
str(candidate.get("id"))
for candidate in candidates
if isinstance(candidate, dict) and candidate.get("id")
}
for candidate_id in sorted(
REPO_SCOPING_REQUIRED_EXTENSION_CANDIDATES - candidate_ids
):
errors.append(
{
"code": "missing_repo_scoping_extension_candidate",
"candidate": candidate_id,
}
)
for candidate in candidates:
if isinstance(candidate, dict) and not candidate.get("review_question"):
errors.append(
{
"code": "missing_repo_scoping_extension_review_question",
"candidate": candidate.get("id"),
}
)
for filename, code in (
("comparison-report.md", "missing_repo_scoping_comparison_report"),
("consumer-workplan-brief.md", "missing_repo_scoping_consumer_workplan_brief"),
):
path = infospace_root / "evaluations" / "repo-scoping" / filename
if not path.is_file():
errors.append(
{
"code": code,
"path": str(Path("infospace") / "evaluations" / "repo-scoping" / filename),
}
)
def _artifact_paths_by_path(
infospace_root: Path,
errors: list[dict[str, Any]],