generated from coulomb/repo-seed
Implement infospace scaffold and service baseline
This commit is contained in:
42
README.md
42
README.md
@@ -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`
|
||||||
|
|||||||
11
canon.yaml
11
canon.yaml
@@ -1,7 +1,7 @@
|
|||||||
repository: info-tech-canon
|
repository: info-tech-canon
|
||||||
title: InfoTechCanon
|
title: InfoTechCanon
|
||||||
status: seed-kernel
|
status: service-baseline
|
||||||
version: RC1-seed
|
version: 0.1.0-scaffold
|
||||||
description: >
|
description: >
|
||||||
An evolving, markdown-first canon for building interoperable, adaptable,
|
An evolving, markdown-first canon for building interoperable, adaptable,
|
||||||
and extensible information-processing systems.
|
and extensible information-processing systems.
|
||||||
@@ -16,12 +16,15 @@ layout:
|
|||||||
- Seed files remain provenance until scaffold migration is reviewed.
|
- Seed files remain provenance until scaffold migration is reviewed.
|
||||||
|
|
||||||
service_surface:
|
service_surface:
|
||||||
planned:
|
implemented:
|
||||||
- cli
|
- cli
|
||||||
- json
|
- json
|
||||||
- api
|
- api
|
||||||
implementation_basis:
|
implementation_basis:
|
||||||
- infospace-bench
|
- infospace-bench
|
||||||
|
package: info_tech_canon
|
||||||
|
cli_module: info_tech_canon.cli
|
||||||
|
api_module: info_tech_canon.api
|
||||||
|
|
||||||
classification:
|
classification:
|
||||||
kernel:
|
kernel:
|
||||||
@@ -135,8 +138,6 @@ first_proofs:
|
|||||||
- caring-kubernetes-rbac
|
- caring-kubernetes-rbac
|
||||||
|
|
||||||
next_actions:
|
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-0003 validation and generated views
|
||||||
- implement ITC-WP-0004 small-saas profile proof
|
- implement ITC-WP-0004 small-saas profile proof
|
||||||
- explore ITC-WP-0006 PURPOSES model extension
|
- explore ITC-WP-0006 PURPOSES model extension
|
||||||
|
|||||||
8
infospace/README.md
Normal file
8
infospace/README.md
Normal 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.
|
||||||
3
infospace/agent/README.md
Normal file
3
infospace/agent/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Agent
|
||||||
|
|
||||||
|
Agent-facing briefs and interface cards live here.
|
||||||
226
infospace/artifacts/index.yaml
Normal file
226
infospace/artifacts/index.yaml
Normal 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
|
||||||
3
infospace/assimilation/README.md
Normal file
3
infospace/assimilation/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Assimilation
|
||||||
|
|
||||||
|
Assimilation records for external knowledge and consumer demand live here.
|
||||||
3
infospace/examples/README.md
Normal file
3
infospace/examples/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
Examples and proof fixtures live here.
|
||||||
50
infospace/infospace.yaml
Normal file
50
infospace/infospace.yaml
Normal 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
|
||||||
1982
infospace/kernel/InfoTechCanonCore.md
Normal file
1982
infospace/kernel/InfoTechCanonCore.md
Normal file
File diff suppressed because it is too large
Load Diff
1565
infospace/kernel/InfoTechCanonKernelMap.md
Normal file
1565
infospace/kernel/InfoTechCanonKernelMap.md
Normal file
File diff suppressed because it is too large
Load Diff
3
infospace/mappings/README.md
Normal file
3
infospace/mappings/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Mappings
|
||||||
|
|
||||||
|
Mappings to external standards, repositories, and consumer concepts live here.
|
||||||
2302
infospace/models/access-control/InfoTechCanonAccessControlModel.md
Normal file
2302
infospace/models/access-control/InfoTechCanonAccessControlModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2180
infospace/models/data/InfoTechCanonDataModel.md
Normal file
2180
infospace/models/data/InfoTechCanonDataModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2407
infospace/models/devsecops/InfoTechCanonDevSecOpsModel.md
Normal file
2407
infospace/models/devsecops/InfoTechCanonDevSecOpsModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2181
infospace/models/governance/InfoTechCanonGovernanceModel.md
Normal file
2181
infospace/models/governance/InfoTechCanonGovernanceModel.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1938
infospace/models/landscape/InfoTechCanonLandscapeModel.md
Normal file
1938
infospace/models/landscape/InfoTechCanonLandscapeModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2433
infospace/models/network/InfoTechCanonNetworkModel.md
Normal file
2433
infospace/models/network/InfoTechCanonNetworkModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2272
infospace/models/observability/InfoTechCanonObservabilityModel.md
Normal file
2272
infospace/models/observability/InfoTechCanonObservabilityModel.md
Normal file
File diff suppressed because it is too large
Load Diff
1750
infospace/models/organization/InfoTechCanonOrganizationModel.md
Normal file
1750
infospace/models/organization/InfoTechCanonOrganizationModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2234
infospace/models/security/InfoTechCanonSecurityModel.md
Normal file
2234
infospace/models/security/InfoTechCanonSecurityModel.md
Normal file
File diff suppressed because it is too large
Load Diff
2100
infospace/models/task/InfoTechCanonTaskModel.md
Normal file
2100
infospace/models/task/InfoTechCanonTaskModel.md
Normal file
File diff suppressed because it is too large
Load Diff
3
infospace/patterns/README.md
Normal file
3
infospace/patterns/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Patterns
|
||||||
|
|
||||||
|
Reusable canon patterns live here.
|
||||||
3
infospace/profiles/README.md
Normal file
3
infospace/profiles/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Profiles
|
||||||
|
|
||||||
|
Application profiles and proof slices live here.
|
||||||
27
infospace/reports/scaffold-placement.md
Normal file
27
infospace/reports/scaffold-placement.md
Normal 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.
|
||||||
3
infospace/schemas/README.md
Normal file
3
infospace/schemas/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Schemas
|
||||||
|
|
||||||
|
Schemas and validation contracts live here.
|
||||||
File diff suppressed because it is too large
Load Diff
2037
infospace/standards/tagging/InfoTechCanonTaggingStandard.md
Normal file
2037
infospace/standards/tagging/InfoTechCanonTaggingStandard.md
Normal file
File diff suppressed because it is too large
Load Diff
3
infospace/validation/README.md
Normal file
3
infospace/validation/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Validation
|
||||||
|
|
||||||
|
Validation reports and rules live here.
|
||||||
3
infospace/views/README.md
Normal file
3
infospace/views/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Views
|
||||||
|
|
||||||
|
Generated and curated views live here.
|
||||||
19
pyproject.toml
Normal file
19
pyproject.toml
Normal 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"]
|
||||||
23
src/info_tech_canon/__init__.py
Normal file
23
src/info_tech_canon/__init__.py
Normal 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",
|
||||||
|
]
|
||||||
3
src/info_tech_canon/__main__.py
Normal file
3
src/info_tech_canon/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .cli import main
|
||||||
|
|
||||||
|
raise SystemExit(main())
|
||||||
104
src/info_tech_canon/api.py
Normal file
104
src/info_tech_canon/api.py
Normal 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
|
||||||
65
src/info_tech_canon/bench.py
Normal file
65
src/info_tech_canon/bench.py
Normal 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
134
src/info_tech_canon/cli.py
Normal 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")
|
||||||
262
src/info_tech_canon/service.py
Normal file
262
src/info_tech_canon/service.py
Normal 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
26
tests/test_api.py
Normal 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
21
tests/test_cli.py
Normal 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
41
tests/test_service.py
Normal 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
|
||||||
@@ -4,7 +4,7 @@ type: workplan
|
|||||||
title: "Infospace Scaffold And Seed Placement"
|
title: "Infospace Scaffold And Seed Placement"
|
||||||
domain: canon
|
domain: canon
|
||||||
repo: info-tech-canon
|
repo: info-tech-canon
|
||||||
status: proposed
|
status: finished
|
||||||
priority: high
|
priority: high
|
||||||
created: "2026-05-23"
|
created: "2026-05-23"
|
||||||
updated: "2026-05-23"
|
updated: "2026-05-23"
|
||||||
@@ -51,7 +51,7 @@ infospace/
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0001-T01
|
id: ITC-WP-0001-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "3a7b29ba-bc15-4ca1-ba61-ec4ffaefa2a1"
|
state_hub_task_id: "3a7b29ba-bc15-4ca1-ba61-ec4ffaefa2a1"
|
||||||
```
|
```
|
||||||
@@ -64,7 +64,7 @@ state_hub_task_id: "3a7b29ba-bc15-4ca1-ba61-ec4ffaefa2a1"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0001-T02
|
id: ITC-WP-0001-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "b5894ec2-e79e-4308-9125-fd10c4b76faf"
|
state_hub_task_id: "b5894ec2-e79e-4308-9125-fd10c4b76faf"
|
||||||
```
|
```
|
||||||
@@ -79,7 +79,7 @@ state_hub_task_id: "b5894ec2-e79e-4308-9125-fd10c4b76faf"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0001-T03
|
id: ITC-WP-0001-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "4826fbad-3dc7-4759-b086-8cb3cf50f6ff"
|
state_hub_task_id: "4826fbad-3dc7-4759-b086-8cb3cf50f6ff"
|
||||||
```
|
```
|
||||||
@@ -94,7 +94,7 @@ state_hub_task_id: "4826fbad-3dc7-4759-b086-8cb3cf50f6ff"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0001-T04
|
id: ITC-WP-0001-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "ff5bae40-9e89-4ec7-9f96-6670e140f4fa"
|
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`
|
- The seed documents are available under the newer `kernel/models/standards`
|
||||||
layout.
|
layout.
|
||||||
- Original seed provenance remains traceable.
|
- 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`.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ type: workplan
|
|||||||
title: "Service Surface Baseline CLI JSON API"
|
title: "Service Surface Baseline CLI JSON API"
|
||||||
domain: canon
|
domain: canon
|
||||||
repo: info-tech-canon
|
repo: info-tech-canon
|
||||||
status: proposed
|
status: finished
|
||||||
priority: high
|
priority: high
|
||||||
created: "2026-05-23"
|
created: "2026-05-23"
|
||||||
updated: "2026-05-23"
|
updated: "2026-05-23"
|
||||||
@@ -32,7 +32,7 @@ usable by downstream agents and tools.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0002-T01
|
id: ITC-WP-0002-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "0d843b8f-0bba-4bd8-8879-5641d2b50848"
|
state_hub_task_id: "0d843b8f-0bba-4bd8-8879-5641d2b50848"
|
||||||
```
|
```
|
||||||
@@ -46,7 +46,7 @@ state_hub_task_id: "0d843b8f-0bba-4bd8-8879-5641d2b50848"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0002-T02
|
id: ITC-WP-0002-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "030955d2-e6af-48d8-a617-943cf4f10628"
|
state_hub_task_id: "030955d2-e6af-48d8-a617-943cf4f10628"
|
||||||
```
|
```
|
||||||
@@ -65,7 +65,7 @@ state_hub_task_id: "030955d2-e6af-48d8-a617-943cf4f10628"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0002-T03
|
id: ITC-WP-0002-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "a926fa1d-7dc0-4450-84c9-c0d49b3744ea"
|
state_hub_task_id: "a926fa1d-7dc0-4450-84c9-c0d49b3744ea"
|
||||||
```
|
```
|
||||||
@@ -78,7 +78,7 @@ state_hub_task_id: "a926fa1d-7dc0-4450-84c9-c0d49b3744ea"
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: ITC-WP-0002-T04
|
id: ITC-WP-0002-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "070ef996-cf45-47ed-b9d8-1e313c9b0e22"
|
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.
|
- Commands can inspect the `infospace/` root.
|
||||||
- API and CLI outputs share the same service-layer contracts.
|
- API and CLI outputs share the same service-layer contracts.
|
||||||
- Tests cover the first happy paths and structured failures.
|
- 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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repository: info-tech-canon
|
repository: info-tech-canon
|
||||||
type: workplan-registry
|
type: workplan-registry
|
||||||
status: planned
|
status: active
|
||||||
created: "2026-05-23"
|
created: "2026-05-23"
|
||||||
updated: "2026-05-23"
|
updated: "2026-05-23"
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ implementation_decisions:
|
|||||||
workplans:
|
workplans:
|
||||||
- id: ITC-WP-0001
|
- id: ITC-WP-0001
|
||||||
title: Infospace Scaffold And Seed Placement
|
title: Infospace Scaffold And Seed Placement
|
||||||
status: planned
|
status: finished
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0001-infospace-scaffold-and-seed-placement.md
|
path: workplans/ITC-WP-0001-infospace-scaffold-and-seed-placement.md
|
||||||
depends_on: []
|
depends_on: []
|
||||||
@@ -29,7 +29,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0002
|
- id: ITC-WP-0002
|
||||||
title: Service Surface Baseline CLI JSON API
|
title: Service Surface Baseline CLI JSON API
|
||||||
status: planned
|
status: finished
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0002-service-surface-cli-json-api.md
|
path: workplans/ITC-WP-0002-service-surface-cli-json-api.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -42,7 +42,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0003
|
- id: ITC-WP-0003
|
||||||
title: Validation Indexes And Generated Views
|
title: Validation Indexes And Generated Views
|
||||||
status: planned
|
status: proposed
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0003-validation-indexes-and-generated-views.md
|
path: workplans/ITC-WP-0003-validation-indexes-and-generated-views.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -56,7 +56,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0004
|
- id: ITC-WP-0004
|
||||||
title: Small SaaS Profile Proof
|
title: Small SaaS Profile Proof
|
||||||
status: planned
|
status: proposed
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0004-small-saas-profile-proof.md
|
path: workplans/ITC-WP-0004-small-saas-profile-proof.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -70,7 +70,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0005
|
- id: ITC-WP-0005
|
||||||
title: Retrieval Agent Briefs And Interface Cards
|
title: Retrieval Agent Briefs And Interface Cards
|
||||||
status: planned
|
status: proposed
|
||||||
priority: medium
|
priority: medium
|
||||||
path: workplans/ITC-WP-0005-retrieval-agent-briefs-and-interface-cards.md
|
path: workplans/ITC-WP-0005-retrieval-agent-briefs-and-interface-cards.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -83,7 +83,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0006
|
- id: ITC-WP-0006
|
||||||
title: Purpose And Demand Model Extension
|
title: Purpose And Demand Model Extension
|
||||||
status: planned
|
status: proposed
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0006-purpose-and-demand-model.md
|
path: workplans/ITC-WP-0006-purpose-and-demand-model.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -95,7 +95,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0007
|
- id: ITC-WP-0007
|
||||||
title: User Engine Evaluation Readiness
|
title: User Engine Evaluation Readiness
|
||||||
status: planned
|
status: proposed
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0007-user-engine-evaluation-readiness.md
|
path: workplans/ITC-WP-0007-user-engine-evaluation-readiness.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -109,7 +109,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0008
|
- id: ITC-WP-0008
|
||||||
title: Railiance Fabric Conformance Support
|
title: Railiance Fabric Conformance Support
|
||||||
status: planned
|
status: proposed
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0008-railiance-fabric-conformance-support.md
|
path: workplans/ITC-WP-0008-railiance-fabric-conformance-support.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -123,7 +123,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0009
|
- id: ITC-WP-0009
|
||||||
title: Repo Scoping Comparison And Extension
|
title: Repo Scoping Comparison And Extension
|
||||||
status: planned
|
status: proposed
|
||||||
priority: high
|
priority: high
|
||||||
path: workplans/ITC-WP-0009-repo-scoping-comparison-and-extension.md
|
path: workplans/ITC-WP-0009-repo-scoping-comparison-and-extension.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -136,7 +136,7 @@ workplans:
|
|||||||
|
|
||||||
- id: ITC-WP-0010
|
- id: ITC-WP-0010
|
||||||
title: CARING Kubernetes RBAC Benchmark
|
title: CARING Kubernetes RBAC Benchmark
|
||||||
status: planned
|
status: proposed
|
||||||
priority: medium
|
priority: medium
|
||||||
path: workplans/ITC-WP-0010-caring-kubernetes-rbac-benchmark.md
|
path: workplans/ITC-WP-0010-caring-kubernetes-rbac-benchmark.md
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -146,4 +146,3 @@ workplans:
|
|||||||
- distinct benchmark workspace
|
- distinct benchmark workspace
|
||||||
- Kubernetes RBAC assimilation
|
- Kubernetes RBAC assimilation
|
||||||
- CARING validation stress test
|
- CARING validation stress test
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user