Add retrieval agent briefs and interface cards

This commit is contained in:
2026-05-23 04:38:57 +02:00
parent a82bdc0bb3
commit b5e1e48ddb
33 changed files with 3323 additions and 11 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from collections import defaultdict
import json
from pathlib import Path
from typing import Any
@@ -8,6 +9,50 @@ import yaml
GENERATED_NOTICE = "<!-- GENERATED by info_tech_canon; do not edit by hand. -->"
RETRIEVAL_ARTIFACT_KINDS = {"kernel", "model", "standard", "profile"}
CONSUMER_BRIEF_IDS = ("user-engine", "railiance-fabric", "repo-scoping")
COMMON_DISTINCTIONS = [
{
"id": "actor-subject-principal",
"title": "Actor vs Subject vs Principal",
"summary": "Use actor for the acting entity in a context, subject for the entity a policy evaluates, and principal for the authenticated identity bound to access decisions.",
"source_artifacts": [
"model/organization",
"model/access-control",
"standard/caring",
],
},
{
"id": "organization-role-access-role-caring-role",
"title": "Organization Role vs AccessRole vs CARING role",
"summary": "Organization roles describe responsibility or position; access roles describe permissions; CARING roles classify access-governance needs and analysis.",
"source_artifacts": [
"model/organization",
"model/access-control",
"standard/caring",
],
},
{
"id": "policy-control-evidence",
"title": "Policy vs Control vs Evidence",
"summary": "Policy states intent or rule, control implements or enforces that rule, and evidence records why the claim should be trusted.",
"source_artifacts": [
"model/governance",
"model/security",
"model/observability",
],
},
{
"id": "intent-scope-purpose",
"title": "Intent vs Scope vs Purpose",
"summary": "Intent captures why an actor wants something, scope bounds what is included, and purpose captures consumer demand or use case pressure on the repo.",
"source_artifacts": [
"kernel/itc-core",
"model/governance",
"profile/small-saas",
],
},
]
def generate_indexes(context: Any) -> dict[str, Any]:
@@ -86,12 +131,48 @@ def generate_tree(context: Any) -> dict[str, Any]:
def generate_agent_briefs(context: Any) -> dict[str, Any]:
retrieval = retrieval_index(context)
files = [
_write_text(
context.infospace_root / "agent" / "global-agent-brief.md",
_render_global_agent_brief(context),
)
_render_global_agent_brief(context, retrieval),
),
_write_text(
context.infospace_root / "agent" / "retrieval-index.md",
_render_retrieval_index_markdown(retrieval),
),
_write_yaml(
context.infospace_root / "agent" / "retrieval-index.yaml",
retrieval,
),
_write_json(
context.infospace_root / "agent" / "retrieval-index.json",
retrieval,
),
_write_yaml(
context.infospace_root / "agent" / "templates" / "canon-interface-card.template.yaml",
interface_card_template(),
),
_write_text(
context.infospace_root / "agent" / "templates" / "consumer-brief.template.md",
_render_consumer_brief_template(),
),
]
for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id):
if artifact.kind in RETRIEVAL_ARTIFACT_KINDS:
files.append(
_write_text(
context.infospace_root / "agent" / "briefs" / f"{_safe_id(artifact.id)}.md",
_render_artifact_agent_brief(context, artifact, retrieval),
)
)
for consumer_id in CONSUMER_BRIEF_IDS:
files.append(
_write_text(
context.infospace_root / "agent" / "consumer-briefs" / f"{consumer_id}.md",
_render_consumer_brief(consumer_id),
)
)
return _result("agent-briefs", files)
@@ -211,6 +292,61 @@ def artifact_tree(context: Any) -> dict[str, Any]:
return {"root": "infospace", "file_count": len(files), "files": files}
def retrieval_index(context: Any) -> dict[str, Any]:
ownership = concept_ownership(context)
concepts_by_owner: dict[str, list[str]] = defaultdict(list)
for concept in ownership["concepts"]:
concepts_by_owner[str(concept["owner"])].append(str(concept["concept"]))
items = []
for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id):
relationships = [
{
"type": str(relationship.get("type") or "related"),
"target": str(relationship.get("target") or ""),
}
for relationship in artifact.relationships
]
imports = [
item["target"]
for item in relationships
if item["type"] in {"imports", "requires", "uses", "conforms_to"}
]
warnings = []
source_path = str(artifact.provenance.get("source_path") or artifact.path)
if not (context.repo_root / source_path).is_file() and not (
context.infospace_root / source_path
).is_file():
warnings.append(
{
"code": "source_path_not_file",
"source_path": source_path,
}
)
items.append(
{
"id": artifact.id,
"kind": artifact.kind,
"title": artifact.title,
"canonical_path": artifact.path,
"source_path": source_path,
"summary": _summary_for_artifact(artifact),
"owned_concepts": sorted(set(concepts_by_owner.get(artifact.id, []))),
"imports": sorted(set(imports)),
"relationships": relationships,
"warnings": warnings,
}
)
return {
"schema": "info-tech-canon.retrieval-index.v1",
"infospace": context.infospace.config.slug,
"item_count": len(items),
"items": items,
"common_distinctions": COMMON_DISTINCTIONS,
}
def _render_by_standard(context: Any) -> str:
lines = _heading("By Standard")
standards = [
@@ -348,7 +484,7 @@ def _render_repository_tree(tree: dict[str, Any]) -> str:
return "\n".join(lines).rstrip() + "\n"
def _render_global_agent_brief(context: Any) -> str:
def _render_global_agent_brief(context: Any, retrieval: dict[str, Any]) -> str:
lines = _heading("Global Agent Brief")
lines.extend(
[
@@ -356,8 +492,10 @@ def _render_global_agent_brief(context: Any) -> str:
"",
f"- Infospace slug: `{context.infospace.config.slug}`",
f"- Artifact count: {len(context.infospace.artifacts)}",
f"- Retrieval index items: {retrieval['item_count']}",
"- Primary confidence command: `make validate`",
"- Refresh generated indexes and views with: `make index`",
"- Refresh agent briefs and interface templates with: `make agent-briefs`",
"",
"## Useful Commands",
"",
@@ -365,6 +503,24 @@ def _render_global_agent_brief(context: Any) -> str:
"- `PYTHONPATH=src python3 -m info_tech_canon validate`",
"- `PYTHONPATH=src python3 -m info_tech_canon graph`",
"- `PYTHONPATH=src python3 -m info_tech_canon index`",
"- `PYTHONPATH=src python3 -m info_tech_canon profile validate small-saas`",
"",
"## Retrieval Entry Points",
"",
"- `agent/retrieval-index.md`",
"- `agent/retrieval-index.yaml`",
"- `agent/retrieval-index.json`",
"- `agent/briefs/` for per-artifact briefs",
"- `agent/templates/canon-interface-card.template.yaml`",
"",
"## Common Distinctions",
"",
]
)
for distinction in retrieval["common_distinctions"]:
lines.append(f"- **{distinction['title']}**: {distinction['summary']}")
lines.extend(
[
"",
"## Consumption Notes",
"",
@@ -376,6 +532,205 @@ def _render_global_agent_brief(context: Any) -> str:
return "\n".join(lines).rstrip() + "\n"
def _render_retrieval_index_markdown(retrieval: dict[str, Any]) -> str:
lines = _heading("Retrieval Index")
lines.extend(
[
f"Schema: `{retrieval['schema']}`",
f"Infospace: `{retrieval['infospace']}`",
f"Items: **{retrieval['item_count']}**",
"",
"## Common Distinctions",
"",
]
)
for distinction in retrieval["common_distinctions"]:
sources = ", ".join(f"`{item}`" for item in distinction["source_artifacts"])
lines.append(f"- **{distinction['title']}**: {distinction['summary']} Sources: {sources}")
lines.extend(["", "## Items", ""])
for item in retrieval["items"]:
imports = ", ".join(f"`{target}`" for target in item["imports"]) or "none"
concepts = ", ".join(f"`{concept}`" for concept in item["owned_concepts"]) or "none"
lines.extend(
[
f"### {item['title']}",
"",
f"- ID: `{item['id']}`",
f"- Kind: `{item['kind']}`",
f"- Canonical path: `{item['canonical_path']}`",
f"- Source path: `{item['source_path']}`",
f"- Summary: {item['summary']}",
f"- Imports and anchors: {imports}",
f"- Owned concepts: {concepts}",
"",
]
)
return "\n".join(lines).rstrip() + "\n"
def _render_artifact_agent_brief(
context: Any,
artifact: Any,
retrieval: dict[str, Any],
) -> str:
item = next(entry for entry in retrieval["items"] if entry["id"] == artifact.id)
frontmatter = {
"id": f"agent-brief/{_safe_id(artifact.id)}",
"artifact_id": artifact.id,
"source_path": artifact.path,
"source_kind": artifact.kind,
"generated": True,
}
lines = [
"---",
yaml.safe_dump(frontmatter, sort_keys=False).strip(),
"---",
"",
GENERATED_NOTICE,
"",
f"# Agent Brief: {artifact.title}",
"",
f"- Artifact ID: `{artifact.id}`",
f"- Kind: `{artifact.kind}`",
f"- Canonical path: `{artifact.path}`",
f"- Full source: `{artifact.path}`",
f"- Summary: {item['summary']}",
"",
"## Retrieval Hints",
"",
]
if item["imports"]:
lines.append("Imports and anchors:")
lines.extend(f"- `{target}`" for target in item["imports"])
else:
lines.append("No imports or anchors recorded.")
lines.extend(["", "## Owned Concepts", ""])
if item["owned_concepts"]:
lines.extend(f"- `{concept}`" for concept in item["owned_concepts"])
else:
lines.append("No owned concepts recorded yet.")
lines.extend(["", "## Related Distinctions", ""])
related = [
distinction
for distinction in retrieval["common_distinctions"]
if artifact.id in distinction["source_artifacts"]
]
if related:
for distinction in related:
lines.append(f"- **{distinction['title']}**: {distinction['summary']}")
else:
lines.append("No common distinction is anchored directly on this artifact.")
return "\n".join(lines).rstrip() + "\n"
def interface_card_template() -> dict[str, Any]:
return {
"schema": "info-tech-canon.interface-card.v1",
"id": "consumer-repo/interface-card",
"title": "Consumer Repo Canon Interface Card",
"consumer": {
"repo": "",
"domain": "",
"owner": "",
"intent": "",
"scope": "",
"purposes": [],
},
"canon_surfaces": {
"implemented_profiles": [],
"consumed_artifacts": [],
"owned_concepts": [],
"produced_concepts": [],
"consumed_concepts": [],
"mappings": [],
},
"validation_expectations": {
"commands": [],
"evidence_required": [],
"known_gaps": [],
},
"consumer_needs": {
"current": [],
"requested_extensions": [],
"feedback": [],
},
}
def _render_consumer_brief_template() -> str:
lines = [
"---",
"id: consumer-brief/template",
"consumer: TBD",
"generated: true",
"---",
"",
GENERATED_NOTICE,
"",
"# Consumer Brief Template",
"",
"## Consumer Intent",
"",
"- Intent:",
"- Scope:",
"- Purposes:",
"",
"## Canon Surfaces",
"",
"- Implemented profiles:",
"- Consumed standards:",
"- Produced concepts:",
"- Consumed concepts:",
"",
"## Validation Expectations",
"",
"- Commands:",
"- Evidence:",
"- Known gaps:",
]
return "\n".join(lines).rstrip() + "\n"
def _render_consumer_brief(consumer_id: str) -> str:
titles = {
"user-engine": "User Engine Canon Consumer Brief",
"railiance-fabric": "Railiance Fabric Canon Consumer Brief",
"repo-scoping": "Repo Scoping Canon Consumer Brief",
}
purposes = {
"user-engine": "Evaluate user-management concepts, roles, access traces, profile claims, and governance evidence against the canon before integration.",
"railiance-fabric": "Use the canon to make captured entities and edges cleaner for conformance and visualization.",
"repo-scoping": "Compare repo-scoping concepts with canon INTENT, SCOPE, PURPOSES, and interface-card expectations.",
}
lines = [
"---",
f"id: consumer-brief/{consumer_id}",
f"consumer: {consumer_id}",
"generated: true",
"---",
"",
GENERATED_NOTICE,
"",
f"# {titles[consumer_id]}",
"",
"## Purpose",
"",
purposes[consumer_id],
"",
"## Starting Points",
"",
"- `agent/retrieval-index.md`",
"- `agent/templates/canon-interface-card.template.yaml`",
"- `profiles/small-saas/profile.yaml`",
"- `views/by-concept.md`",
"",
"## Workplan Boundary",
"",
"Adoption and repo-specific implementation workplans belong in the consumer repository.",
]
return "\n".join(lines).rstrip() + "\n"
def _heading(title: str) -> list[str]:
return [GENERATED_NOTICE, "", f"# {title}", ""]
@@ -395,6 +750,12 @@ def _write_yaml(path: Path, data: dict[str, Any]) -> dict[str, Any]:
return _write_text(path, content)
def _write_json(path: Path, data: dict[str, Any]) -> dict[str, Any]:
path.parent.mkdir(parents=True, exist_ok=True)
content = json.dumps(data, indent=2, sort_keys=True) + "\n"
return _write_text(path, content)
def _result(kind: str, files: list[dict[str, Any]]) -> dict[str, Any]:
return {
"ok": True,
@@ -420,6 +781,24 @@ def _normalize_concept(value: str) -> str:
return "-".join(value.lower().replace("_", "-").split())
def _safe_id(value: str) -> str:
return value.replace("/", "-").replace("_", "-")
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 == "profile":
return f"Profile that constrains canon artifacts for a practical implementation slice: {artifact.title}."
if artifact.kind == "kernel":
return f"Kernel artifact that defines canon structure or integration: {artifact.title}."
if artifact.kind == "model":
return f"Domain model used by canon profiles and standards: {artifact.title}."
if artifact.kind == "standard":
return f"Cross-cutting canon standard: {artifact.title}."
return f"Canon artifact: {artifact.title}."
def _is_generated(path: Path) -> bool:
try:
return path.read_text(encoding="utf-8").startswith(GENERATED_NOTICE)