import-review takes any partner manifest and returns, per concept, whether the name resolves in the ownership index and to which artifact, and per entry whether the pinned SHA-256 matches the blob at the declared source commit. Both run in one pass so neither can be recorded without the other, which is the failure this workplan exists to prevent. It exits non-zero on a finding, reads JSON or YAML, needs no partner checkout, and carries its own limit: resolution proves a name exists and names one owner, nothing more. Accepted manifests are registered under infospace/interfaces/manifests/ as provenance-preserving copies owned by the partner, with the partner revision and retrieval date recorded. Editing a copy to make a check pass is forbidden in the file itself. Validation re-resolves them and reports drift as federation_import_drift, a warning naming the partner rather than an error, because a stale partner pin is not this repository's file to fix. The review kit gains an extension-boundary-review template requiring hash count, resolution count and conflict count as three separate lines, and an operating rule saying one is never evidence of another. Both boundary files carry the standing-check result. Verified live: security-canon resolves 11 of 11, interface-canon 23 of 25 with the two known Interface and Endpoint pins. make check passes with 58 tests, clean validation and those two 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
307 lines
10 KiB
Python
307 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .api import serve
|
|
from .service import (
|
|
CanonServiceError,
|
|
alignment_template,
|
|
artifact_graph,
|
|
generate_agent_briefs,
|
|
generate_indexes,
|
|
generate_tree,
|
|
inspect_canon,
|
|
list_artifacts,
|
|
list_models,
|
|
list_standards,
|
|
list_views,
|
|
profile_graph,
|
|
profile_inspect,
|
|
profile_validate,
|
|
read_view,
|
|
review_capability_record,
|
|
review_kit,
|
|
validate_canon,
|
|
write_validation_report,
|
|
)
|
|
|
|
|
|
Command = Callable[[argparse.Namespace], dict[str, Any]]
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(prog="info-tech-canon")
|
|
parser.add_argument(
|
|
"--root",
|
|
default="",
|
|
help="Infospace root. Defaults to ./infospace from the repository root.",
|
|
)
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
|
|
inspect = sub.add_parser("inspect", help="Inspect the canon infospace")
|
|
inspect.set_defaults(handler=_inspect)
|
|
|
|
artifacts = sub.add_parser("artifacts", help="List canon artifacts")
|
|
artifacts.add_argument("--kind", default="")
|
|
artifacts.set_defaults(handler=_artifacts)
|
|
|
|
models = sub.add_parser("models", help="List canon model artifacts")
|
|
models.set_defaults(handler=_models)
|
|
|
|
standards = sub.add_parser("standards", help="List canon standard artifacts")
|
|
standards.set_defaults(handler=_standards)
|
|
|
|
review_kit_cmd = sub.add_parser(
|
|
"review-kit",
|
|
help="Read the consumer repository alignment review kit",
|
|
)
|
|
review_kit_cmd.set_defaults(handler=_review_kit)
|
|
|
|
alignment_template_cmd = sub.add_parser(
|
|
"alignment-template",
|
|
help="Read the consumer alignment workplan template",
|
|
)
|
|
alignment_template_cmd.set_defaults(handler=_alignment_template)
|
|
|
|
validate = sub.add_parser("validate", help="Validate the canon infospace")
|
|
validate.add_argument(
|
|
"--write",
|
|
default="",
|
|
help="Write the JSON validation payload to this path.",
|
|
)
|
|
validate.set_defaults(handler=_validate)
|
|
|
|
index = sub.add_parser("index", help="Refresh generated indexes and views")
|
|
index.set_defaults(handler=_index)
|
|
|
|
tree = sub.add_parser("tree", help="Refresh the generated infospace tree")
|
|
tree.set_defaults(handler=_tree)
|
|
|
|
agent_briefs = sub.add_parser("agent-briefs", help="Refresh generated agent briefs")
|
|
agent_briefs.set_defaults(handler=_agent_briefs)
|
|
|
|
views = sub.add_parser("views", help="List or read generated views")
|
|
views.add_argument("name", nargs="?", default="")
|
|
views.set_defaults(handler=_views)
|
|
|
|
graph = sub.add_parser("graph", help="Export the canon artifact graph")
|
|
graph.add_argument("--format", choices=["json", "mermaid"], default="json")
|
|
graph.set_defaults(handler=_graph)
|
|
|
|
profile = sub.add_parser("profile", help="Inspect canon profiles")
|
|
profile_sub = profile.add_subparsers(dest="profile_command", required=True)
|
|
profile_inspect_cmd = profile_sub.add_parser("inspect", help="Inspect a profile")
|
|
profile_inspect_cmd.add_argument("profile")
|
|
profile_inspect_cmd.set_defaults(handler=_profile_inspect)
|
|
profile_validate_cmd = profile_sub.add_parser("validate", help="Validate a profile")
|
|
profile_validate_cmd.add_argument("profile")
|
|
profile_validate_cmd.set_defaults(handler=_profile_validate)
|
|
profile_graph_cmd = profile_sub.add_parser("graph", help="Export a profile graph")
|
|
profile_graph_cmd.add_argument("profile")
|
|
profile_graph_cmd.add_argument("--format", choices=["json", "mermaid"], default="json")
|
|
profile_graph_cmd.set_defaults(handler=_profile_graph)
|
|
|
|
capability_review = sub.add_parser(
|
|
"capability-review",
|
|
help="Review a consumer capability record against the live catalog",
|
|
)
|
|
capability_review.add_argument("record")
|
|
capability_review.set_defaults(handler=_capability_review)
|
|
|
|
emission = sub.add_parser("emission-review", help="Validate a source-owned YAML/JSON cadence declaration")
|
|
emission.add_argument("record")
|
|
emission.set_defaults(handler=_emission_review)
|
|
coverage = sub.add_parser("validation-coverage", help="List implemented checks and explicit limits")
|
|
coverage.set_defaults(handler=_coverage)
|
|
freshness = sub.add_parser("check-generated", help="Check projections without changing source files")
|
|
freshness.set_defaults(handler=_check_generated)
|
|
inventory = sub.add_parser("scope-inventory", help="Derive scope counts from the artifact registry")
|
|
inventory.set_defaults(handler=_scope_inventory)
|
|
concepts = sub.add_parser(
|
|
"concept-coverage",
|
|
help="Measure declared concepts against candidates defined in artifact prose")
|
|
concepts.add_argument("--artifact", help="Limit the report to one artifact id")
|
|
concepts.set_defaults(handler=_concept_coverage)
|
|
imports_cmd = sub.add_parser(
|
|
"import-review",
|
|
help="Resolve a partner import manifest: pinned hashes and concept names")
|
|
imports_cmd.add_argument("manifest")
|
|
imports_cmd.set_defaults(handler=_import_review)
|
|
bundle = sub.add_parser("export-emission-contract", help="Export a content-addressed contract tar")
|
|
bundle.add_argument("destination")
|
|
bundle.set_defaults(handler=_export_emission)
|
|
benchmark = sub.add_parser("benchmark-reads", help="Measure ten uncached inspect calls")
|
|
benchmark.set_defaults(handler=_benchmark_reads)
|
|
|
|
api = sub.add_parser("api", help="Run the read-only local API")
|
|
api.add_argument("--host", default="127.0.0.1")
|
|
api.add_argument("--port", type=int, default=8765)
|
|
api.set_defaults(handler=_api)
|
|
|
|
return parser
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
handler: Command = args.handler
|
|
try:
|
|
result = handler(args)
|
|
except CanonServiceError as exc:
|
|
_print_json(exc.to_dict())
|
|
return 2
|
|
except Exception as exc:
|
|
_print_json(
|
|
{
|
|
"ok": False,
|
|
"error": {
|
|
"code": "unhandled_error",
|
|
"message": str(exc),
|
|
"details": {},
|
|
},
|
|
}
|
|
)
|
|
return 1
|
|
if result:
|
|
_print_json(result)
|
|
return 0 if result.get("ok", False) else 1
|
|
|
|
|
|
def _root(args: argparse.Namespace) -> Path | None:
|
|
return Path(args.root) if args.root else None
|
|
|
|
|
|
def _inspect(args: argparse.Namespace) -> dict[str, Any]:
|
|
return inspect_canon(_root(args))
|
|
|
|
|
|
def _artifacts(args: argparse.Namespace) -> dict[str, Any]:
|
|
return list_artifacts(_root(args), kind=args.kind or None)
|
|
|
|
|
|
def _models(args: argparse.Namespace) -> dict[str, Any]:
|
|
return list_models(_root(args))
|
|
|
|
|
|
def _standards(args: argparse.Namespace) -> dict[str, Any]:
|
|
return list_standards(_root(args))
|
|
|
|
|
|
def _review_kit(args: argparse.Namespace) -> dict[str, Any]:
|
|
return review_kit(_root(args))
|
|
|
|
|
|
def _alignment_template(args: argparse.Namespace) -> dict[str, Any]:
|
|
return alignment_template(_root(args))
|
|
|
|
|
|
def _validate(args: argparse.Namespace) -> dict[str, Any]:
|
|
if args.write:
|
|
return write_validation_report(args.write, _root(args))
|
|
return validate_canon(_root(args))
|
|
|
|
|
|
def _index(args: argparse.Namespace) -> dict[str, Any]:
|
|
return generate_indexes(_root(args))
|
|
|
|
|
|
def _tree(args: argparse.Namespace) -> dict[str, Any]:
|
|
return generate_tree(_root(args))
|
|
|
|
|
|
def _agent_briefs(args: argparse.Namespace) -> dict[str, Any]:
|
|
return generate_agent_briefs(_root(args))
|
|
|
|
|
|
def _views(args: argparse.Namespace) -> dict[str, Any]:
|
|
if args.name:
|
|
return read_view(args.name, _root(args))
|
|
return list_views(_root(args))
|
|
|
|
|
|
def _graph(args: argparse.Namespace) -> dict[str, Any]:
|
|
return artifact_graph(_root(args), output_format=args.format)
|
|
|
|
|
|
def _capability_review(args: argparse.Namespace) -> dict[str, Any]:
|
|
return review_capability_record(args.record, _root(args))
|
|
|
|
|
|
def _emission_review(args):
|
|
from .contracts import review_emission
|
|
return review_emission(args.record, _root(args))
|
|
|
|
|
|
def _coverage(args):
|
|
from .contracts import coverage
|
|
from .service import load_context
|
|
return coverage(load_context(_root(args)))
|
|
|
|
|
|
def _check_generated(args):
|
|
from .maintenance import check_generated
|
|
from .service import load_context
|
|
return check_generated(load_context(_root(args)))
|
|
|
|
|
|
def _scope_inventory(args):
|
|
from .maintenance import scope_inventory
|
|
from .service import load_context
|
|
return dict(scope_inventory(load_context(_root(args))), ok=True)
|
|
|
|
|
|
def _concept_coverage(args):
|
|
from .maintenance import concept_candidates
|
|
from .service import load_context
|
|
|
|
report = concept_candidates(load_context(_root(args)))
|
|
if getattr(args, "artifact", None):
|
|
report = dict(report, artifacts=[item for item in report["artifacts"]
|
|
if item["artifact"] == args.artifact])
|
|
return dict(report, ok=True)
|
|
|
|
|
|
def _import_review(args):
|
|
from .federation import import_manifest_review
|
|
from .service import load_context
|
|
|
|
return import_manifest_review(load_context(_root(args)), args.manifest)
|
|
|
|
|
|
def _export_emission(args):
|
|
from .maintenance import export_emission_bundle
|
|
from .paths import infospace_root
|
|
return export_emission_bundle(infospace_root(_root(args)), Path(args.destination))
|
|
|
|
|
|
def _benchmark_reads(args):
|
|
from .maintenance import benchmark_reads
|
|
from .paths import infospace_root
|
|
return benchmark_reads(infospace_root(_root(args)))
|
|
|
|
|
|
def _profile_inspect(args: argparse.Namespace) -> dict[str, Any]:
|
|
return profile_inspect(args.profile, _root(args))
|
|
|
|
|
|
def _profile_validate(args: argparse.Namespace) -> dict[str, Any]:
|
|
return profile_validate(args.profile, _root(args))
|
|
|
|
|
|
def _profile_graph(args: argparse.Namespace) -> dict[str, Any]:
|
|
return profile_graph(args.profile, _root(args), output_format=args.format)
|
|
|
|
|
|
def _api(args: argparse.Namespace) -> dict[str, Any]:
|
|
serve(host=args.host, port=args.port, root=_root(args))
|
|
return {}
|
|
|
|
|
|
def _print_json(data: dict[str, Any]) -> None:
|
|
json.dump(data, sys.stdout, indent=2, sort_keys=True)
|
|
sys.stdout.write("\n")
|