Implement infospace scaffold and service baseline

This commit is contained in:
2026-05-23 03:12:02 +02:00
parent df6238c7e0
commit 9883a99f78
43 changed files with 35986 additions and 28 deletions

View File

@@ -1 +1,41 @@
Building interoperable, adaptable, and extensible information-processing systems.
Building interoperable, adaptable, and extensible information-processing systems.
## Current Service
This repository now implements one concrete infospace under `infospace/`.
The repository root remains the service, governance, and workplan shell.
The first service surface is intentionally small:
- JSON-first CLI commands
- importable Python service functions
- read-only local HTTP API
- artifact loading, checks, and graph summaries backed by `infospace-bench`
## Source-Tree Usage
```bash
PYTHONPATH=src python3 -m info_tech_canon inspect
PYTHONPATH=src python3 -m info_tech_canon artifacts
PYTHONPATH=src python3 -m info_tech_canon models
PYTHONPATH=src python3 -m info_tech_canon standards
PYTHONPATH=src python3 -m info_tech_canon validate
PYTHONPATH=src python3 -m info_tech_canon graph
PYTHONPATH=src python3 -m info_tech_canon api --host 127.0.0.1 --port 8765
```
After package installation, the same commands are available through the
`info-tech-canon` console script.
## API Endpoints
- `GET /health`
- `GET /inspect`
- `GET /artifacts`
- `GET /artifacts?kind=model`
- `GET /models`
- `GET /standards`
- `GET /validate`
- `GET /graph`
- `GET /graph?format=mermaid`
- `GET /profiles/{profile}/inspect`

View File

@@ -1,7 +1,7 @@
repository: info-tech-canon
title: InfoTechCanon
status: seed-kernel
version: RC1-seed
status: service-baseline
version: 0.1.0-scaffold
description: >
An evolving, markdown-first canon for building interoperable, adaptable,
and extensible information-processing systems.
@@ -16,12 +16,15 @@ layout:
- Seed files remain provenance until scaffold migration is reviewed.
service_surface:
planned:
implemented:
- cli
- json
- api
implementation_basis:
- infospace-bench
package: info_tech_canon
cli_module: info_tech_canon.cli
api_module: info_tech_canon.api
classification:
kernel:
@@ -135,8 +138,6 @@ first_proofs:
- caring-kubernetes-rbac
next_actions:
- implement ITC-WP-0001 infospace scaffold
- implement ITC-WP-0002 service surface
- implement ITC-WP-0003 validation and generated views
- implement ITC-WP-0004 small-saas profile proof
- explore ITC-WP-0006 PURPOSES model extension

8
infospace/README.md Normal file
View File

@@ -0,0 +1,8 @@
# InfoTechCanon Infospace
This directory is the single concrete infospace implemented by this repository.
The repository root remains the service, governance, and workplan shell.
The current placement pass copies the seed documents into canonical
`kernel/`, `models/`, and `standards/` paths while keeping `seeds/` as
provenance until the scaffold migration is reviewed.

View File

@@ -0,0 +1,3 @@
# Agent
Agent-facing briefs and interface cards live here.

View File

@@ -0,0 +1,226 @@
artifacts:
- id: kernel/itc-core
path: kernel/InfoTechCanonCore.md
kind: kernel
title: InfoTechCanon Core
provenance:
source_path: seeds/InfoTechCanonCore_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships: []
- id: kernel/itc-kernel-map
path: kernel/InfoTechCanonKernelMap.md
kind: kernel
title: InfoTechCanon Kernel Map
provenance:
source_path: seeds/InfoTechCanonKernelMap_RC1.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: maps
target: kernel/itc-core
- type: maps
target: model/information-space
- type: maps
target: model/landscape
- type: maps
target: model/organization
- type: maps
target: model/governance
- type: maps
target: model/task
- type: maps
target: model/access-control
- type: maps
target: model/security
- type: maps
target: model/data
- type: maps
target: model/devsecops
- type: maps
target: model/network
- type: maps
target: model/observability
- type: maps
target: standard/tagging
- type: maps
target: standard/caring
- id: model/information-space
path: models/information-space/InfoTechCanonInformationSpaceModel.md
kind: model
title: InfoTechCanon Information Space Model
provenance:
source_path: seeds/InfoTechCanonInformationSpaceModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- id: model/landscape
path: models/landscape/InfoTechCanonLandscapeModel.md
kind: model
title: InfoTechCanon Landscape Model
provenance:
source_path: seeds/InfoTechCanonLandscapeModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- id: model/organization
path: models/organization/InfoTechCanonOrganizationModel.md
kind: model
title: InfoTechCanon Organization Model
provenance:
source_path: seeds/InfoTechCanonOrganizationModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- id: model/governance
path: models/governance/InfoTechCanonGovernanceModel.md
kind: model
title: InfoTechCanon Governance Model
provenance:
source_path: seeds/InfoTechCanonGovernanceModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- id: model/task
path: models/task/InfoTechCanonTaskModel.md
kind: model
title: InfoTechCanon Task Model
provenance:
source_path: seeds/InfoTechCanonTaskModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- id: model/access-control
path: models/access-control/InfoTechCanonAccessControlModel.md
kind: model
title: InfoTechCanon Access Control Model
provenance:
source_path: seeds/InfoTechCanonAccessControlModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: uses
target: model/organization
- type: uses
target: model/governance
- id: model/security
path: models/security/InfoTechCanonSecurityModel.md
kind: model
title: InfoTechCanon Security Model
provenance:
source_path: seeds/InfoTechCanonSecurityModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: uses
target: model/access-control
- id: model/data
path: models/data/InfoTechCanonDataModel.md
kind: model
title: InfoTechCanon Data Model
provenance:
source_path: seeds/InfoTechCanonDataModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: uses
target: model/governance
- id: model/devsecops
path: models/devsecops/InfoTechCanonDevSecOpsModel.md
kind: model
title: InfoTechCanon DevSecOps Model
provenance:
source_path: seeds/InfoTechCanonDevSecOpsModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: uses
target: model/security
- id: model/network
path: models/network/InfoTechCanonNetworkModel.md
kind: model
title: InfoTechCanon Network Model
provenance:
source_path: seeds/InfoTechCanonNetworkModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: uses
target: model/security
- id: model/observability
path: models/observability/InfoTechCanonObservabilityModel.md
kind: model
title: InfoTechCanon Observability Model
provenance:
source_path: seeds/InfoTechCanonObservabilityModel_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: uses
target: model/task
- id: standard/tagging
path: standards/tagging/InfoTechCanonTaggingStandard.md
kind: standard
title: InfoTechCanon Tagging Standard
provenance:
source_path: seeds/InfoTechCanonTaggingStandard_RC1_seed.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: imports
target: model/task
- id: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
kind: standard
title: InfoTechCanon CARING Access Governance Standard
provenance:
source_path: seeds/InfoTechCanonCaringAccessGovernanceStandard.md
placement: copied
placement_workplan: ITC-WP-0001
relationships:
- type: conforms_to
target: kernel/itc-core
- type: imports
target: model/organization
- type: imports
target: model/governance
- type: imports
target: model/access-control
- type: imports
target: model/security
- type: imports
target: model/data
- type: imports
target: model/devsecops
- type: imports
target: model/network
- type: imports
target: model/observability
- type: imports
target: model/task
- type: imports
target: standard/tagging

View File

@@ -0,0 +1,3 @@
# Assimilation
Assimilation records for external knowledge and consumer demand live here.

View File

@@ -0,0 +1,3 @@
# Examples
Examples and proof fixtures live here.

50
infospace/infospace.yaml Normal file
View File

@@ -0,0 +1,50 @@
slug: canon
name: InfoTechCanon
topic:
name: InfoTechCanon
domain: Canon
sources: seeds
disciplines:
- name: Canon Kernel
path: kernel/InfoTechCanonCore.md
- name: Kernel Map
path: kernel/InfoTechCanonKernelMap.md
- name: Information Space Model
path: models/information-space/InfoTechCanonInformationSpaceModel.md
- name: Landscape Model
path: models/landscape/InfoTechCanonLandscapeModel.md
- name: Organization Model
path: models/organization/InfoTechCanonOrganizationModel.md
- name: Governance Model
path: models/governance/InfoTechCanonGovernanceModel.md
- name: Task Model
path: models/task/InfoTechCanonTaskModel.md
- name: Access Control Model
path: models/access-control/InfoTechCanonAccessControlModel.md
- name: Security Model
path: models/security/InfoTechCanonSecurityModel.md
- name: Data Model
path: models/data/InfoTechCanonDataModel.md
- name: DevSecOps Model
path: models/devsecops/InfoTechCanonDevSecOpsModel.md
- name: Network Model
path: models/network/InfoTechCanonNetworkModel.md
- name: Observability Model
path: models/observability/InfoTechCanonObservabilityModel.md
- name: Tagging Standard
path: standards/tagging/InfoTechCanonTaggingStandard.md
- name: CARING Access Governance Standard
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
schemas: {}
workflows: []
viability:
redundancy_ratio:
max: 0
coverage_ratio:
min: 1
coherence_components:
max: 1
consistency_cycles:
max: 0
granularity_entropy:
min: 1

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
# Mappings
Mappings to external standards, repositories, and consumer concepts live here.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
# Patterns
Reusable canon patterns live here.

View File

@@ -0,0 +1,3 @@
# Profiles
Application profiles and proof slices live here.

View File

@@ -0,0 +1,27 @@
# Scaffold Placement Report
**Workplan:** ITC-WP-0001
**Date:** 2026-05-23
**Mode:** placement only, no semantic refactor
## Summary
The first implementation pass created the concrete `infospace/` root and
placed the existing seed corpus into the newer `kernel/`, `models/`, and
`standards/` layout. The original files in `seeds/` remain unchanged as
provenance.
## Placement Rules
- Core and Kernel Map are copied to `infospace/kernel/`.
- Broad domain models are copied to `infospace/models/<domain>/`.
- Tagging and CARING are copied to `infospace/standards/<standard>/`.
- `infospace/artifacts/index.yaml` records canonical path, kind, title,
source seed path, and initial graph relationships.
- No semantic edits were made to the copied documents.
## Deferred
- Domain-level profile extraction remains in ITC-WP-0004 and ITC-WP-0006.
- Generated views and stricter validation remain in ITC-WP-0003.
- Consumer repository workplans remain owned by the consumer repositories.

View File

@@ -0,0 +1,3 @@
# Schemas
Schemas and validation contracts live here.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
# Validation
Validation reports and rules live here.

View File

@@ -0,0 +1,3 @@
# Views
Generated and curated views live here.

19
pyproject.toml Normal file
View File

@@ -0,0 +1,19 @@
[project]
name = "info-tech-canon"
version = "0.1.0"
description = "Practical service surface for the InfoTechCanon infospace."
requires-python = ">=3.12"
dependencies = [
"PyYAML>=6",
"infospace-bench @ file:///home/worsch/infospace-bench",
]
[project.scripts]
info-tech-canon = "info_tech_canon.cli:main"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
pythonpath = ["src", "../infospace-bench/src"]
testpaths = ["tests"]

View File

@@ -0,0 +1,23 @@
"""InfoTechCanon service package."""
from .service import (
CanonServiceError,
artifact_graph,
inspect_canon,
list_artifacts,
list_models,
list_standards,
profile_inspect,
validate_canon,
)
__all__ = [
"CanonServiceError",
"artifact_graph",
"inspect_canon",
"list_artifacts",
"list_models",
"list_standards",
"profile_inspect",
"validate_canon",
]

View File

@@ -0,0 +1,3 @@
from .cli import main
raise SystemExit(main())

104
src/info_tech_canon/api.py Normal file
View File

@@ -0,0 +1,104 @@
from __future__ import annotations
import json
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from urllib.parse import parse_qs, urlparse
from .service import (
CanonServiceError,
artifact_graph,
inspect_canon,
list_artifacts,
list_models,
list_standards,
profile_inspect,
validate_canon,
)
def serve(host: str = "127.0.0.1", port: int = 8765, root: Path | None = None) -> None:
handler = _build_handler(root)
server = ThreadingHTTPServer((host, port), handler)
print(f"InfoTechCanon API listening on http://{host}:{port}", flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
def _build_handler(root: Path | None) -> type[BaseHTTPRequestHandler]:
class CanonRequestHandler(BaseHTTPRequestHandler):
def do_GET(self) -> None:
parsed = urlparse(self.path)
query = parse_qs(parsed.query)
try:
status, payload = _route(parsed.path, query, root)
except CanonServiceError as exc:
status, payload = HTTPStatus.BAD_REQUEST, exc.to_dict()
except Exception as exc:
status, payload = HTTPStatus.INTERNAL_SERVER_ERROR, {
"ok": False,
"error": {
"code": "unhandled_error",
"message": str(exc),
"details": {},
},
}
self._send_json(status, payload)
def log_message(self, format: str, *args: object) -> None:
return
def _send_json(self, status: HTTPStatus, payload: dict[str, Any]) -> None:
body = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
self.send_response(status.value)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return CanonRequestHandler
def _route(
path: str,
query: dict[str, list[str]],
root: Path | None,
) -> tuple[HTTPStatus, dict[str, Any]]:
if path == "/health":
return HTTPStatus.OK, {"ok": True, "service": "info-tech-canon"}
if path == "/inspect":
return HTTPStatus.OK, inspect_canon(root)
if path == "/artifacts":
return HTTPStatus.OK, list_artifacts(root, kind=_first(query, "kind"))
if path == "/models":
return HTTPStatus.OK, list_models(root)
if path == "/standards":
return HTTPStatus.OK, list_standards(root)
if path == "/validate":
payload = validate_canon(root)
return (HTTPStatus.OK if payload["ok"] else HTTPStatus.BAD_REQUEST), payload
if path == "/graph":
graph_format = _first(query, "format") or "json"
return HTTPStatus.OK, artifact_graph(root, output_format=graph_format)
if path.startswith("/profiles/") and path.endswith("/inspect"):
profile = path.removeprefix("/profiles/").removesuffix("/inspect").strip("/")
return HTTPStatus.OK, profile_inspect(profile, root)
return HTTPStatus.NOT_FOUND, {
"ok": False,
"error": {
"code": "not_found",
"message": f"Unknown endpoint: {path}",
"details": {"path": path},
},
}
def _first(query: dict[str, list[str]], name: str) -> str | None:
values = query.get(name) or []
return values[0] if values else None

View File

@@ -0,0 +1,65 @@
from __future__ import annotations
import importlib.util
import sys
import types
from pathlib import Path
from types import ModuleType
from typing import Any
BENCH_PACKAGE = "_info_tech_canon_infospace_bench"
BENCH_SOURCE_ROOT = (
Path(__file__).resolve().parents[3] / "infospace-bench" / "src" / "infospace_bench"
)
def _ensure_package() -> ModuleType:
existing = sys.modules.get(BENCH_PACKAGE)
if existing is not None:
return existing
package = types.ModuleType(BENCH_PACKAGE)
package.__path__ = [str(BENCH_SOURCE_ROOT)] # type: ignore[attr-defined]
sys.modules[BENCH_PACKAGE] = package
return package
def _load_module(name: str) -> ModuleType:
_ensure_package()
module_name = f"{BENCH_PACKAGE}.{name}"
existing = sys.modules.get(module_name)
if existing is not None:
return existing
path = BENCH_SOURCE_ROOT / f"{name}.py"
if not path.is_file():
raise RuntimeError(f"Missing infospace-bench module: {path}")
spec = importlib.util.spec_from_file_location(module_name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Unable to load infospace-bench module: {path}")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
errors = _load_module("errors")
models = _load_module("models")
lifecycle = _load_module("lifecycle")
checks = _load_module("checks")
inspection = _load_module("inspection")
Infospace = models.Infospace
KnowledgeArtifact = models.KnowledgeArtifact
load_infospace = lifecycle.load_infospace
run_collection_checks = checks.run_collection_checks
relationship_summary = inspection.relationship_summary
export_mermaid = inspection.export_mermaid
__all__ = [
"Infospace",
"KnowledgeArtifact",
"export_mermaid",
"load_infospace",
"relationship_summary",
"run_collection_checks",
]

134
src/info_tech_canon/cli.py Normal file
View File

@@ -0,0 +1,134 @@
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,
artifact_graph,
inspect_canon,
list_artifacts,
list_models,
list_standards,
profile_inspect,
validate_canon,
)
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)
validate = sub.add_parser("validate", help="Validate the canon infospace")
validate.set_defaults(handler=_validate)
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)
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 _validate(args: argparse.Namespace) -> dict[str, Any]:
return validate_canon(_root(args))
def _graph(args: argparse.Namespace) -> dict[str, Any]:
return artifact_graph(_root(args), output_format=args.format)
def _profile_inspect(args: argparse.Namespace) -> dict[str, Any]:
return profile_inspect(args.profile, _root(args))
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")

View File

@@ -0,0 +1,262 @@
from __future__ import annotations
from collections import Counter
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any
import yaml
from .bench import (
Infospace,
KnowledgeArtifact,
export_mermaid,
load_infospace,
relationship_summary,
run_collection_checks,
)
REPO_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_INFOSPACE_ROOT = REPO_ROOT / "infospace"
class CanonServiceError(Exception):
def __init__(
self,
code: str,
message: str,
details: dict[str, Any] | None = None,
) -> None:
super().__init__(message)
self.code = code
self.message = message
self.details = details or {}
def to_dict(self) -> dict[str, Any]:
return {
"ok": False,
"error": {
"code": self.code,
"message": self.message,
"details": self.details,
},
}
@dataclass(frozen=True)
class CanonContext:
repo_root: Path
infospace_root: Path
infospace: Infospace
def load_context(root: Path | str | None = None) -> CanonContext:
infospace_root = Path(root) if root else DEFAULT_INFOSPACE_ROOT
try:
infospace = load_infospace(infospace_root)
except Exception as exc:
raise CanonServiceError(
"infospace_load_failed",
f"Unable to load infospace at {infospace_root}",
{"root": str(infospace_root), "reason": str(exc)},
) from exc
return CanonContext(
repo_root=REPO_ROOT,
infospace_root=infospace_root,
infospace=infospace,
)
def inspect_canon(root: Path | str | None = None) -> dict[str, Any]:
context = load_context(root)
artifacts = context.infospace.artifacts
kinds = Counter(artifact.kind for artifact in artifacts)
return {
"ok": True,
"repo": {
"slug": "info-tech-canon",
"root": str(context.repo_root),
},
"infospace": {
"slug": context.infospace.config.slug,
"name": context.infospace.config.name,
"root": str(context.infospace_root),
"artifact_count": len(artifacts),
"kinds": dict(sorted(kinds.items())),
},
"service": {
"package": "info_tech_canon",
"contract": "cli-json-api",
},
}
def list_artifacts(
root: Path | str | None = None,
*,
kind: str | None = None,
) -> dict[str, Any]:
context = load_context(root)
artifacts = [
_artifact_to_dict(artifact, context.infospace_root)
for artifact in context.infospace.artifacts
if kind is None or artifact.kind == kind
]
return {
"ok": True,
"count": len(artifacts),
"artifacts": artifacts,
}
def list_models(root: Path | str | None = None) -> dict[str, Any]:
return list_artifacts(root, kind="model")
def list_standards(root: Path | str | None = None) -> dict[str, Any]:
return list_artifacts(root, kind="standard")
def validate_canon(root: Path | str | None = None) -> dict[str, Any]:
context = load_context(root)
errors: list[dict[str, Any]] = []
artifact_ids = {artifact.id for artifact in context.infospace.artifacts}
for artifact in context.infospace.artifacts:
artifact_path = context.infospace_root / artifact.path
if not artifact_path.is_file():
errors.append(
{
"code": "missing_artifact_path",
"artifact_id": artifact.id,
"path": artifact.path,
}
)
for relationship in artifact.relationships:
target = relationship.get("target")
if target not in artifact_ids:
errors.append(
{
"code": "missing_relationship_target",
"artifact_id": artifact.id,
"target": target,
}
)
for discipline in context.infospace.config.disciplines:
discipline_path = context.infospace_root / discipline.path
if not discipline_path.is_file():
errors.append(
{
"code": "missing_discipline_path",
"discipline": discipline.name,
"path": discipline.path,
}
)
checks = run_collection_checks(context.infospace.artifacts)
threshold_errors = _evaluate_thresholds(
checks.metrics,
context.infospace.config.viability,
)
errors.extend(threshold_errors)
return {
"ok": not errors,
"errors": errors,
"metrics": checks.metrics,
"details": checks.details,
}
def artifact_graph(
root: Path | str | None = None,
*,
output_format: str = "json",
) -> dict[str, Any]:
context = load_context(root)
summary = relationship_summary(context.infospace.artifacts)
if output_format == "mermaid":
return {"ok": True, "format": "mermaid", "graph": export_mermaid(summary)}
if output_format != "json":
raise CanonServiceError(
"unsupported_graph_format",
f"Unsupported graph format: {output_format}",
{"supported": ["json", "mermaid"]},
)
return {
"ok": True,
"format": "json",
"graph": {
"node_count": summary.node_count,
"edge_count": summary.edge_count,
"nodes": summary.nodes,
"edges": [asdict(edge) for edge in summary.edges],
"relationship_types": summary.relationship_types,
},
}
def profile_inspect(
profile: str,
root: Path | str | None = None,
) -> dict[str, Any]:
context = load_context(root)
profile_path = context.infospace_root / "profiles" / profile / "profile.yaml"
if not profile_path.is_file():
raise CanonServiceError(
"missing_profile",
f"Profile not found: {profile}",
{"profile": profile, "path": str(profile_path)},
)
with profile_path.open("r", encoding="utf-8") as handle:
data = yaml.safe_load(handle) or {}
if not isinstance(data, dict):
raise CanonServiceError(
"invalid_profile",
f"Profile must be a YAML mapping: {profile}",
{"profile": profile, "path": str(profile_path)},
)
return {"ok": True, "profile": data, "path": str(profile_path)}
def _artifact_to_dict(
artifact: KnowledgeArtifact,
infospace_root: Path,
) -> dict[str, Any]:
data = artifact.to_dict()
data["exists"] = (infospace_root / artifact.path).is_file()
return data
def _evaluate_thresholds(
metrics: dict[str, float],
thresholds: dict[str, Any],
) -> list[dict[str, Any]]:
errors: list[dict[str, Any]] = []
for metric, threshold in thresholds.items():
value = metrics.get(metric)
if value is None:
continue
min_value = getattr(threshold, "min", None)
max_value = getattr(threshold, "max", None)
if min_value is not None and value < min_value:
errors.append(
{
"code": "metric_below_threshold",
"metric": metric,
"value": value,
"min": min_value,
}
)
if max_value is not None and value > max_value:
errors.append(
{
"code": "metric_above_threshold",
"metric": metric,
"value": value,
"max": max_value,
}
)
return errors

26
tests/test_api.py Normal file
View File

@@ -0,0 +1,26 @@
from http import HTTPStatus
from info_tech_canon.api import _route
def test_api_route_inspect() -> None:
status, payload = _route("/inspect", {}, None)
assert status == HTTPStatus.OK
assert payload["ok"] is True
assert payload["infospace"]["slug"] == "canon"
def test_api_route_validate() -> None:
status, payload = _route("/validate", {}, None)
assert status == HTTPStatus.OK
assert payload["ok"] is True
def test_api_route_unknown_endpoint() -> None:
status, payload = _route("/missing", {}, None)
assert status == HTTPStatus.NOT_FOUND
assert payload["ok"] is False
assert payload["error"]["code"] == "not_found"

21
tests/test_cli.py Normal file
View File

@@ -0,0 +1,21 @@
import json
from info_tech_canon.cli import main
def test_cli_inspect_emits_json(capsys) -> None:
exit_code = main(["inspect"])
assert exit_code == 0
payload = json.loads(capsys.readouterr().out)
assert payload["ok"] is True
assert payload["infospace"]["artifact_count"] == 15
def test_cli_missing_profile_uses_structured_error(capsys) -> None:
exit_code = main(["profile", "inspect", "small-saas"])
assert exit_code == 2
payload = json.loads(capsys.readouterr().out)
assert payload["ok"] is False
assert payload["error"]["code"] == "missing_profile"

41
tests/test_service.py Normal file
View File

@@ -0,0 +1,41 @@
from info_tech_canon.service import (
artifact_graph,
inspect_canon,
list_models,
list_standards,
validate_canon,
)
def test_inspect_canon_counts_artifact_kinds() -> None:
payload = inspect_canon()
assert payload["ok"] is True
assert payload["infospace"]["slug"] == "canon"
assert payload["infospace"]["artifact_count"] == 15
assert payload["infospace"]["kinds"] == {
"kernel": 2,
"model": 11,
"standard": 2,
}
def test_model_and_standard_lists_are_filtered() -> None:
assert list_models()["count"] == 11
assert list_standards()["count"] == 2
def test_validate_canon_passes_scaffold() -> None:
payload = validate_canon()
assert payload["ok"] is True
assert payload["errors"] == []
assert payload["details"]["artifact_count"] == 15
def test_graph_exports_relationship_summary() -> None:
payload = artifact_graph()
assert payload["ok"] is True
assert payload["graph"]["node_count"] == 15
assert payload["graph"]["edge_count"] > 15

View File

@@ -4,7 +4,7 @@ type: workplan
title: "Infospace Scaffold And Seed Placement"
domain: canon
repo: info-tech-canon
status: proposed
status: finished
priority: high
created: "2026-05-23"
updated: "2026-05-23"
@@ -51,7 +51,7 @@ infospace/
```task
id: ITC-WP-0001-T01
status: todo
status: done
priority: high
state_hub_task_id: "3a7b29ba-bc15-4ca1-ba61-ec4ffaefa2a1"
```
@@ -64,7 +64,7 @@ state_hub_task_id: "3a7b29ba-bc15-4ca1-ba61-ec4ffaefa2a1"
```task
id: ITC-WP-0001-T02
status: todo
status: done
priority: high
state_hub_task_id: "b5894ec2-e79e-4308-9125-fd10c4b76faf"
```
@@ -79,7 +79,7 @@ state_hub_task_id: "b5894ec2-e79e-4308-9125-fd10c4b76faf"
```task
id: ITC-WP-0001-T03
status: todo
status: done
priority: high
state_hub_task_id: "4826fbad-3dc7-4759-b086-8cb3cf50f6ff"
```
@@ -94,7 +94,7 @@ state_hub_task_id: "4826fbad-3dc7-4759-b086-8cb3cf50f6ff"
```task
id: ITC-WP-0001-T04
status: todo
status: done
priority: medium
state_hub_task_id: "ff5bae40-9e89-4ec7-9f96-6670e140f4fa"
```
@@ -110,3 +110,11 @@ state_hub_task_id: "ff5bae40-9e89-4ec7-9f96-6670e140f4fa"
- The seed documents are available under the newer `kernel/models/standards`
layout.
- Original seed provenance remains traceable.
## Implementation Notes
- Implemented the concrete `infospace/` root with manifest and artifact index.
- Copied seed documents to canonical `kernel/`, `models/`, and `standards/`
paths without semantic edits.
- Preserved source provenance in `infospace/artifacts/index.yaml` and
`infospace/reports/scaffold-placement.md`.

View File

@@ -4,7 +4,7 @@ type: workplan
title: "Service Surface Baseline CLI JSON API"
domain: canon
repo: info-tech-canon
status: proposed
status: finished
priority: high
created: "2026-05-23"
updated: "2026-05-23"
@@ -32,7 +32,7 @@ usable by downstream agents and tools.
```task
id: ITC-WP-0002-T01
status: todo
status: done
priority: high
state_hub_task_id: "0d843b8f-0bba-4bd8-8879-5641d2b50848"
```
@@ -46,7 +46,7 @@ state_hub_task_id: "0d843b8f-0bba-4bd8-8879-5641d2b50848"
```task
id: ITC-WP-0002-T02
status: todo
status: done
priority: high
state_hub_task_id: "030955d2-e6af-48d8-a617-943cf4f10628"
```
@@ -65,7 +65,7 @@ state_hub_task_id: "030955d2-e6af-48d8-a617-943cf4f10628"
```task
id: ITC-WP-0002-T03
status: todo
status: done
priority: high
state_hub_task_id: "a926fa1d-7dc0-4450-84c9-c0d49b3744ea"
```
@@ -78,7 +78,7 @@ state_hub_task_id: "a926fa1d-7dc0-4450-84c9-c0d49b3744ea"
```task
id: ITC-WP-0002-T04
status: todo
status: done
priority: high
state_hub_task_id: "070ef996-cf45-47ed-b9d8-1e313c9b0e22"
```
@@ -100,3 +100,12 @@ state_hub_task_id: "070ef996-cf45-47ed-b9d8-1e313c9b0e22"
- Commands can inspect the `infospace/` root.
- API and CLI outputs share the same service-layer contracts.
- Tests cover the first happy paths and structured failures.
## Implementation Notes
- Added the `info_tech_canon` Python package and `info-tech-canon` console
script definition.
- Added JSON-first CLI commands for inspection, artifact/model/standard lists,
validation, graph export, and profile inspection.
- Added a read-only local HTTP API mirroring the CLI contracts.
- Added service, CLI, and API tests for the baseline behavior.

View File

@@ -1,6 +1,6 @@
repository: info-tech-canon
type: workplan-registry
status: planned
status: active
created: "2026-05-23"
updated: "2026-05-23"
@@ -18,7 +18,7 @@ implementation_decisions:
workplans:
- id: ITC-WP-0001
title: Infospace Scaffold And Seed Placement
status: planned
status: finished
priority: high
path: workplans/ITC-WP-0001-infospace-scaffold-and-seed-placement.md
depends_on: []
@@ -29,7 +29,7 @@ workplans:
- id: ITC-WP-0002
title: Service Surface Baseline CLI JSON API
status: planned
status: finished
priority: high
path: workplans/ITC-WP-0002-service-surface-cli-json-api.md
depends_on:
@@ -42,7 +42,7 @@ workplans:
- id: ITC-WP-0003
title: Validation Indexes And Generated Views
status: planned
status: proposed
priority: high
path: workplans/ITC-WP-0003-validation-indexes-and-generated-views.md
depends_on:
@@ -56,7 +56,7 @@ workplans:
- id: ITC-WP-0004
title: Small SaaS Profile Proof
status: planned
status: proposed
priority: high
path: workplans/ITC-WP-0004-small-saas-profile-proof.md
depends_on:
@@ -70,7 +70,7 @@ workplans:
- id: ITC-WP-0005
title: Retrieval Agent Briefs And Interface Cards
status: planned
status: proposed
priority: medium
path: workplans/ITC-WP-0005-retrieval-agent-briefs-and-interface-cards.md
depends_on:
@@ -83,7 +83,7 @@ workplans:
- id: ITC-WP-0006
title: Purpose And Demand Model Extension
status: planned
status: proposed
priority: high
path: workplans/ITC-WP-0006-purpose-and-demand-model.md
depends_on:
@@ -95,7 +95,7 @@ workplans:
- id: ITC-WP-0007
title: User Engine Evaluation Readiness
status: planned
status: proposed
priority: high
path: workplans/ITC-WP-0007-user-engine-evaluation-readiness.md
depends_on:
@@ -109,7 +109,7 @@ workplans:
- id: ITC-WP-0008
title: Railiance Fabric Conformance Support
status: planned
status: proposed
priority: high
path: workplans/ITC-WP-0008-railiance-fabric-conformance-support.md
depends_on:
@@ -123,7 +123,7 @@ workplans:
- id: ITC-WP-0009
title: Repo Scoping Comparison And Extension
status: planned
status: proposed
priority: high
path: workplans/ITC-WP-0009-repo-scoping-comparison-and-extension.md
depends_on:
@@ -136,7 +136,7 @@ workplans:
- id: ITC-WP-0010
title: CARING Kubernetes RBAC Benchmark
status: planned
status: proposed
priority: medium
path: workplans/ITC-WP-0010-caring-kubernetes-rbac-benchmark.md
depends_on:
@@ -146,4 +146,3 @@ workplans:
- distinct benchmark workspace
- Kubernetes RBAC assimilation
- CARING validation stress test