2026-05-23 03:12:02 +02:00
|
|
|
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,
|
2026-05-23 07:23:48 +02:00
|
|
|
alignment_template,
|
2026-05-23 03:12:02 +02:00
|
|
|
artifact_graph,
|
2026-05-23 03:32:16 +02:00
|
|
|
generate_agent_briefs,
|
|
|
|
|
generate_indexes,
|
|
|
|
|
generate_tree,
|
2026-05-23 03:12:02 +02:00
|
|
|
inspect_canon,
|
|
|
|
|
list_artifacts,
|
|
|
|
|
list_models,
|
|
|
|
|
list_standards,
|
2026-05-23 03:32:16 +02:00
|
|
|
list_views,
|
2026-05-23 04:26:28 +02:00
|
|
|
profile_graph,
|
2026-05-23 03:12:02 +02:00
|
|
|
profile_inspect,
|
2026-05-23 04:26:28 +02:00
|
|
|
profile_validate,
|
2026-05-23 03:32:16 +02:00
|
|
|
read_view,
|
2026-08-15 19:42:30 +02:00
|
|
|
review_capability_record,
|
2026-05-23 07:23:48 +02:00
|
|
|
review_kit,
|
2026-05-23 03:12:02 +02:00
|
|
|
validate_canon,
|
2026-05-23 03:32:16 +02:00
|
|
|
write_validation_report,
|
2026-05-23 03:12:02 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2026-05-23 07:23:48 +02:00
|
|
|
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)
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
validate = sub.add_parser("validate", help="Validate the canon infospace")
|
2026-05-23 03:32:16 +02:00
|
|
|
validate.add_argument(
|
|
|
|
|
"--write",
|
|
|
|
|
default="",
|
|
|
|
|
help="Write the JSON validation payload to this path.",
|
|
|
|
|
)
|
2026-05-23 03:12:02 +02:00
|
|
|
validate.set_defaults(handler=_validate)
|
|
|
|
|
|
2026-05-23 03:32:16 +02:00
|
|
|
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)
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
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)
|
2026-05-23 04:26:28 +02:00
|
|
|
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)
|
2026-05-23 03:12:02 +02:00
|
|
|
|
2026-08-15 19:42:30 +02:00
|
|
|
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)
|
|
|
|
|
|
2026-09-05 00:50:09 +02:00
|
|
|
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)
|
Measure the concept-declaration gap (INFO-WP-0027-T01)
Adds maintenance.concept_candidates() and the concept-coverage CLI command,
which measure concepts an artifact defines in prose against the concepts it
declares. Extraction covers the bold form, the numbered-heading form that hid
itc-org:Authority, and the concept-table form the kernel map uses; preserved
source under assimilation, seeds and incoming is excluded. Candidates are review
input, never ownership.
Baseline over 31 live artifacts: 113 concepts declared against 690 defined,
leaving 637 defined but undeclared, about 16 percent coverage. The workplan's
519 counted the bold form alone.
Two corrections to the workplan's framing, applied there. Thirteen artifacts
declare nothing rather than twelve: kernel/itc-core defines 57 concepts across
two forms and declares none, and it is the artifact every other artifact imports
from, so it goes first in T02. The gap also reaches further than obscure terms —
Actor is undeclared in the organization model although SecurityCanon imports it
from there by name against a pinned hash.
Three tests cover the extractor, one asserting that Authority appears in the
organization model's undeclared list, so the blind spot that produced finding
F-1 now has a regression test. make check passes with 49 tests.
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
2026-09-20 23:06:09 +02:00
|
|
|
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)
|
2026-09-05 00:50:09 +02:00
|
|
|
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)
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 07:23:48 +02:00
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
def _validate(args: argparse.Namespace) -> dict[str, Any]:
|
2026-05-23 03:32:16 +02:00
|
|
|
if args.write:
|
|
|
|
|
return write_validation_report(args.write, _root(args))
|
2026-05-23 03:12:02 +02:00
|
|
|
return validate_canon(_root(args))
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 03:32:16 +02:00
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
def _graph(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
|
|
return artifact_graph(_root(args), output_format=args.format)
|
|
|
|
|
|
|
|
|
|
|
2026-08-15 19:42:30 +02:00
|
|
|
def _capability_review(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
|
|
return review_capability_record(args.record, _root(args))
|
|
|
|
|
|
|
|
|
|
|
2026-09-05 00:50:09 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
Measure the concept-declaration gap (INFO-WP-0027-T01)
Adds maintenance.concept_candidates() and the concept-coverage CLI command,
which measure concepts an artifact defines in prose against the concepts it
declares. Extraction covers the bold form, the numbered-heading form that hid
itc-org:Authority, and the concept-table form the kernel map uses; preserved
source under assimilation, seeds and incoming is excluded. Candidates are review
input, never ownership.
Baseline over 31 live artifacts: 113 concepts declared against 690 defined,
leaving 637 defined but undeclared, about 16 percent coverage. The workplan's
519 counted the bold form alone.
Two corrections to the workplan's framing, applied there. Thirteen artifacts
declare nothing rather than twelve: kernel/itc-core defines 57 concepts across
two forms and declares none, and it is the artifact every other artifact imports
from, so it goes first in T02. The gap also reaches further than obscure terms —
Actor is undeclared in the organization model although SecurityCanon imports it
from there by name against a pinned hash.
Three tests cover the extractor, one asserting that Authority appears in the
organization model's undeclared list, so the blind spot that produced finding
F-1 now has a regression test. make check passes with 49 tests.
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
2026-09-20 23:06:09 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
2026-09-05 00:50:09 +02:00
|
|
|
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)))
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
def _profile_inspect(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
|
|
return profile_inspect(args.profile, _root(args))
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 04:26:28 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 03:12:02 +02:00
|
|
|
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")
|