Add validation indexes and generated views

This commit is contained in:
2026-05-23 03:32:16 +02:00
parent dc44208c9f
commit c112bf5c74
37 changed files with 2007 additions and 8 deletions

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
PYTHON ?= python3
CANON_CLI = PYTHONPATH=src $(PYTHON) -m info_tech_canon
.PHONY: validate index tree agent-briefs test
validate:
$(CANON_CLI) validate --write infospace/validation/latest.json
index:
$(CANON_CLI) index
tree:
$(CANON_CLI) tree
agent-briefs:
$(CANON_CLI) agent-briefs
test:
$(PYTHON) -m pytest

View File

@@ -21,6 +21,8 @@ PYTHONPATH=src python3 -m info_tech_canon models
PYTHONPATH=src python3 -m info_tech_canon standards PYTHONPATH=src python3 -m info_tech_canon standards
PYTHONPATH=src python3 -m info_tech_canon validate PYTHONPATH=src python3 -m info_tech_canon validate
PYTHONPATH=src python3 -m info_tech_canon graph PYTHONPATH=src python3 -m info_tech_canon graph
PYTHONPATH=src python3 -m info_tech_canon index
PYTHONPATH=src python3 -m info_tech_canon views
PYTHONPATH=src python3 -m info_tech_canon api --host 127.0.0.1 --port 8765 PYTHONPATH=src python3 -m info_tech_canon api --host 127.0.0.1 --port 8765
``` ```
@@ -38,4 +40,15 @@ After package installation, the same commands are available through the
- `GET /validate` - `GET /validate`
- `GET /graph` - `GET /graph`
- `GET /graph?format=mermaid` - `GET /graph?format=mermaid`
- `GET /views`
- `GET /views/{name}`
- `GET /profiles/{profile}/inspect` - `GET /profiles/{profile}/inspect`
## Maintenance
```bash
make validate
make index
make tree
make agent-briefs
```

View File

@@ -108,6 +108,7 @@ planned_directories:
- infospace/assimilation/ - infospace/assimilation/
- infospace/schemas/ - infospace/schemas/
- infospace/views/ - infospace/views/
- infospace/indexes/
- infospace/agent/ - infospace/agent/
- infospace/examples/ - infospace/examples/
- infospace/validation/ - infospace/validation/

View File

@@ -0,0 +1,23 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# Global Agent Brief
This brief summarizes the current canon service surface for agents.
- Infospace slug: `canon`
- Artifact count: 15
- Primary confidence command: `make validate`
- Refresh generated indexes and views with: `make index`
## Useful Commands
- `PYTHONPATH=src python3 -m info_tech_canon inspect`
- `PYTHONPATH=src python3 -m info_tech_canon validate`
- `PYTHONPATH=src python3 -m info_tech_canon graph`
- `PYTHONPATH=src python3 -m info_tech_canon index`
## Consumption Notes
- Treat `seeds/` as provenance.
- Treat `infospace/` as the service-consumable canon root.
- Generated files are marked and can be refreshed deterministically.

View File

@@ -0,0 +1,3 @@
# Indexes
Generated machine-readable indexes live here.

View File

@@ -0,0 +1,153 @@
root: infospace
file_count: 50
files:
- path: README.md
directory: .
name: README.md
- path: agent/README.md
directory: agent
name: README.md
- path: agent/global-agent-brief.md
directory: agent
name: global-agent-brief.md
- path: artifacts/index.yaml
directory: artifacts
name: index.yaml
- path: assimilation/README.md
directory: assimilation
name: README.md
- path: examples/README.md
directory: examples
name: README.md
- path: indexes/README.md
directory: indexes
name: README.md
- path: indexes/artifact-tree.yaml
directory: indexes
name: artifact-tree.yaml
- path: indexes/concept-ownership.yaml
directory: indexes
name: concept-ownership.yaml
- path: indexes/import-matrix.yaml
directory: indexes
name: import-matrix.yaml
- path: infospace.yaml
directory: .
name: infospace.yaml
- path: kernel/InfoTechCanonCore.md
directory: kernel
name: InfoTechCanonCore.md
- path: kernel/InfoTechCanonKernelMap.md
directory: kernel
name: InfoTechCanonKernelMap.md
- path: mappings/README.md
directory: mappings
name: README.md
- path: models/access-control/InfoTechCanonAccessControlModel.md
directory: models/access-control
name: InfoTechCanonAccessControlModel.md
- path: models/data/InfoTechCanonDataModel.md
directory: models/data
name: InfoTechCanonDataModel.md
- path: models/devsecops/InfoTechCanonDevSecOpsModel.md
directory: models/devsecops
name: InfoTechCanonDevSecOpsModel.md
- path: models/governance/InfoTechCanonGovernanceModel.md
directory: models/governance
name: InfoTechCanonGovernanceModel.md
- path: models/information-space/InfoTechCanonInformationSpaceModel.md
directory: models/information-space
name: InfoTechCanonInformationSpaceModel.md
- path: models/landscape/InfoTechCanonLandscapeModel.md
directory: models/landscape
name: InfoTechCanonLandscapeModel.md
- path: models/network/InfoTechCanonNetworkModel.md
directory: models/network
name: InfoTechCanonNetworkModel.md
- path: models/observability/InfoTechCanonObservabilityModel.md
directory: models/observability
name: InfoTechCanonObservabilityModel.md
- path: models/organization/InfoTechCanonOrganizationModel.md
directory: models/organization
name: InfoTechCanonOrganizationModel.md
- path: models/security/InfoTechCanonSecurityModel.md
directory: models/security
name: InfoTechCanonSecurityModel.md
- path: models/task/InfoTechCanonTaskModel.md
directory: models/task
name: InfoTechCanonTaskModel.md
- path: patterns/README.md
directory: patterns
name: README.md
- path: profiles/README.md
directory: profiles
name: README.md
- path: reports/scaffold-placement.md
directory: reports
name: scaffold-placement.md
- path: schemas/README.md
directory: schemas
name: README.md
- path: schemas/agent-brief.schema.yaml
directory: schemas
name: agent-brief.schema.yaml
- path: schemas/assimilation.schema.yaml
directory: schemas
name: assimilation.schema.yaml
- path: schemas/concept.schema.yaml
directory: schemas
name: concept.schema.yaml
- path: schemas/index.yaml
directory: schemas
name: index.yaml
- path: schemas/interface-card.schema.yaml
directory: schemas
name: interface-card.schema.yaml
- path: schemas/mapping.schema.yaml
directory: schemas
name: mapping.schema.yaml
- path: schemas/profile.schema.yaml
directory: schemas
name: profile.schema.yaml
- path: schemas/standard.schema.yaml
directory: schemas
name: standard.schema.yaml
- path: schemas/workplan.schema.yaml
directory: schemas
name: workplan.schema.yaml
- path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
directory: standards/caring
name: InfoTechCanonCaringAccessGovernanceStandard.md
- path: standards/tagging/InfoTechCanonTaggingStandard.md
directory: standards/tagging
name: InfoTechCanonTaggingStandard.md
- path: validation/README.md
directory: validation
name: README.md
- path: validation/latest.json
directory: validation
name: latest.json
- path: views/README.md
directory: views
name: README.md
- path: views/by-concept.md
directory: views
name: by-concept.md
- path: views/by-mapping-target.md
directory: views
name: by-mapping-target.md
- path: views/by-profile.md
directory: views
name: by-profile.md
- path: views/by-standard.md
directory: views
name: by-standard.md
- path: views/import-matrix.md
directory: views
name: import-matrix.md
- path: views/kernel-overview.md
directory: views
name: kernel-overview.md
- path: views/repository-tree.md
directory: views
name: repository-tree.md

View File

@@ -0,0 +1,124 @@
concept_count: 30
concepts:
- concept: InfoTechCanon Core
owner: kernel/itc-core
path: kernel/InfoTechCanonCore.md
source: artifact_title
- concept: InfoTechCanon Kernel Map
owner: kernel/itc-kernel-map
path: kernel/InfoTechCanonKernelMap.md
source: artifact_title
- concept: InfoTechCanon Access Control Model
owner: model/access-control
path: models/access-control/InfoTechCanonAccessControlModel.md
source: artifact_title
- concept: InfoTechCanon Data Model
owner: model/data
path: models/data/InfoTechCanonDataModel.md
source: artifact_title
- concept: InfoTechCanon DevSecOps Model
owner: model/devsecops
path: models/devsecops/InfoTechCanonDevSecOpsModel.md
source: artifact_title
- concept: InfoTechCanon Governance Model
owner: model/governance
path: models/governance/InfoTechCanonGovernanceModel.md
source: artifact_title
- concept: InfoTechCanon Information Space Model
owner: model/information-space
path: models/information-space/InfoTechCanonInformationSpaceModel.md
source: artifact_title
- concept: InfoTechCanon Landscape Model
owner: model/landscape
path: models/landscape/InfoTechCanonLandscapeModel.md
source: artifact_title
- concept: InfoTechCanon Network Model
owner: model/network
path: models/network/InfoTechCanonNetworkModel.md
source: artifact_title
- concept: InfoTechCanon Observability Model
owner: model/observability
path: models/observability/InfoTechCanonObservabilityModel.md
source: artifact_title
- concept: InfoTechCanon Organization Model
owner: model/organization
path: models/organization/InfoTechCanonOrganizationModel.md
source: artifact_title
- concept: InfoTechCanon Security Model
owner: model/security
path: models/security/InfoTechCanonSecurityModel.md
source: artifact_title
- concept: InfoTechCanon Task Model
owner: model/task
path: models/task/InfoTechCanonTaskModel.md
source: artifact_title
- concept: InfoTechCanon CARING Access Governance Standard
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: artifact_title
- concept: CARINGAccessDescriptor
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGCanonicalRole
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGOrganizationRelation
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGPlane
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGCapabilityProfile
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGExposureMode
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGExposureEvent
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGDeclaredAccessMap
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGEffectiveAccessMap
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGDerivedCapability
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGInducedAccess
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGRestrictionPrecedence
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGAnalysisFitnessTest
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGAnalysisProcedure
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: CARINGRedesignProcedure
owner: standard/caring
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
source: frontmatter.owned_concepts
- concept: InfoTechCanon Tagging Standard
owner: standard/tagging
path: standards/tagging/InfoTechCanonTaggingStandard.md
source: artifact_title
duplicate_candidates: []
ownership_conflicts: []

View File

@@ -0,0 +1,137 @@
artifacts:
- kernel/itc-core
- kernel/itc-kernel-map
- model/access-control
- model/data
- model/devsecops
- model/governance
- model/information-space
- model/landscape
- model/network
- model/observability
- model/organization
- model/security
- model/task
- standard/caring
- standard/tagging
rows:
- artifact: kernel/itc-core
targets: {}
- artifact: kernel/itc-kernel-map
targets:
kernel/itc-core:
- maps
model/access-control:
- maps
model/data:
- maps
model/devsecops:
- maps
model/governance:
- maps
model/information-space:
- maps
model/landscape:
- maps
model/network:
- maps
model/observability:
- maps
model/organization:
- maps
model/security:
- maps
model/task:
- maps
standard/caring:
- maps
standard/tagging:
- maps
- artifact: model/access-control
targets:
kernel/itc-core:
- conforms_to
model/governance:
- uses
model/organization:
- uses
- artifact: model/data
targets:
kernel/itc-core:
- conforms_to
model/governance:
- uses
- artifact: model/devsecops
targets:
kernel/itc-core:
- conforms_to
model/security:
- uses
- artifact: model/governance
targets:
kernel/itc-core:
- conforms_to
- artifact: model/information-space
targets:
kernel/itc-core:
- conforms_to
- artifact: model/landscape
targets:
kernel/itc-core:
- conforms_to
- artifact: model/network
targets:
kernel/itc-core:
- conforms_to
model/security:
- uses
- artifact: model/observability
targets:
kernel/itc-core:
- conforms_to
model/task:
- uses
- artifact: model/organization
targets:
kernel/itc-core:
- conforms_to
- artifact: model/security
targets:
kernel/itc-core:
- conforms_to
model/access-control:
- uses
- artifact: model/task
targets:
kernel/itc-core:
- conforms_to
- artifact: standard/caring
targets:
kernel/itc-core:
- conforms_to
model/access-control:
- imports
model/data:
- imports
model/devsecops:
- imports
model/governance:
- imports
model/network:
- imports
model/observability:
- imports
model/organization:
- imports
model/security:
- imports
model/task:
- imports
standard/tagging:
- imports
- artifact: standard/tagging
targets:
kernel/itc-core:
- conforms_to
model/task:
- imports

View File

@@ -35,7 +35,15 @@ disciplines:
path: standards/tagging/InfoTechCanonTaggingStandard.md path: standards/tagging/InfoTechCanonTaggingStandard.md
- name: CARING Access Governance Standard - name: CARING Access Governance Standard
path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md path: standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md
schemas: {} schemas:
standard: schemas/standard.schema.yaml
concept: schemas/concept.schema.yaml
mapping: schemas/mapping.schema.yaml
profile: schemas/profile.schema.yaml
assimilation: schemas/assimilation.schema.yaml
interface-card: schemas/interface-card.schema.yaml
agent-brief: schemas/agent-brief.schema.yaml
workplan: schemas/workplan.schema.yaml
workflows: [] workflows: []
viability: viability:
redundancy_ratio: redundancy_ratio:

View File

@@ -0,0 +1,27 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/agent-brief.schema.yaml
title: InfoTechCanon Agent Brief
type: object
required:
- id
- title
- audience
- purpose
properties:
id:
type: string
title:
type: string
audience:
type: string
purpose:
type: string
commands:
type: array
items:
type: string
entrypoints:
type: array
items:
type: string
additionalProperties: true

View File

@@ -0,0 +1,28 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/assimilation.schema.yaml
title: InfoTechCanon Assimilation Record
type: object
required:
- id
- title
- source
- disposition
properties:
id:
type: string
title:
type: string
source:
type: string
disposition:
enum:
- observe
- map
- adopt
- adapt
- reject
impacts:
type: array
items:
type: string
additionalProperties: true

View File

@@ -0,0 +1,26 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/concept.schema.yaml
title: InfoTechCanon Concept
type: object
required:
- id
- title
- owner
properties:
id:
type: string
title:
type: string
owner:
type: string
definition:
type: string
aliases:
type: array
items:
type: string
relationships:
type: array
items:
type: object
additionalProperties: true

View File

@@ -0,0 +1,17 @@
schemas:
- id: standard
path: standard.schema.yaml
- id: concept
path: concept.schema.yaml
- id: mapping
path: mapping.schema.yaml
- id: profile
path: profile.schema.yaml
- id: assimilation
path: assimilation.schema.yaml
- id: interface-card
path: interface-card.schema.yaml
- id: agent-brief
path: agent-brief.schema.yaml
- id: workplan
path: workplan.schema.yaml

View File

@@ -0,0 +1,25 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/interface-card.schema.yaml
title: Canon Interface Card
type: object
required:
- id
- title
- consumer
- canon_surfaces
properties:
id:
type: string
title:
type: string
consumer:
type: string
canon_surfaces:
type: array
items:
type: string
expectations:
type: array
items:
type: string
additionalProperties: true

View File

@@ -0,0 +1,23 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/mapping.schema.yaml
title: InfoTechCanon Mapping
type: object
required:
- id
- title
- source
- target
properties:
id:
type: string
title:
type: string
source:
type: string
target:
type: string
mapping_type:
type: string
confidence:
type: string
additionalProperties: true

View File

@@ -0,0 +1,24 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/profile.schema.yaml
title: InfoTechCanon Profile
type: object
required:
- id
- title
- scope
properties:
id:
type: string
title:
type: string
scope:
type: string
includes:
type: array
items:
type: string
constraints:
type: array
items:
type: object
additionalProperties: true

View File

@@ -0,0 +1,33 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/standard.schema.yaml
title: InfoTechCanon Standard
type: object
required:
- id
- title
- type
- status
properties:
id:
type: string
title:
type: string
type:
enum:
- standard
- specialized-standard
- model
- kernel
status:
type: string
version:
type: string
imports:
type: array
items:
type: string
owned_concepts:
type: array
items:
type: string
additionalProperties: true

View File

@@ -0,0 +1,38 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: https://info-tech-canon.local/schemas/workplan.schema.yaml
title: State Hub Workplan
type: object
required:
- id
- type
- title
- domain
- repo
- status
properties:
id:
type: string
type:
const: workplan
title:
type: string
domain:
type: string
repo:
type: string
status:
enum:
- proposed
- ready
- active
- blocked
- backlog
- finished
- archived
depends_on_workplans:
type: array
items:
type: string
state_hub_workstream_id:
type: string
additionalProperties: true

View File

@@ -0,0 +1,41 @@
{
"details": {
"artifact_count": 15,
"relationship_count": 45
},
"errors": [],
"metrics": {
"coherence_components": 1.0,
"consistency_cycles": 0.0,
"coverage_ratio": 1.0,
"granularity_entropy": 1.103307408607834,
"redundancy_ratio": 0.0
},
"ok": true,
"warnings": [
{
"code": "missing_optional_concepts_dir",
"path": "infospace/concepts"
},
{
"code": "empty_optional_collection",
"path": "infospace/profiles"
},
{
"code": "empty_optional_collection",
"path": "infospace/patterns"
},
{
"code": "empty_optional_collection",
"path": "infospace/mappings"
},
{
"code": "empty_optional_collection",
"path": "infospace/assimilation"
},
{
"code": "empty_optional_collection",
"path": "infospace/examples"
}
]
}

View File

@@ -0,0 +1,46 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# By Concept
Concept count: **30**
| Concept | Owner | Source |
| --- | --- | --- |
| InfoTechCanon Core | `kernel/itc-core` | `artifact_title` |
| InfoTechCanon Kernel Map | `kernel/itc-kernel-map` | `artifact_title` |
| InfoTechCanon Access Control Model | `model/access-control` | `artifact_title` |
| InfoTechCanon Data Model | `model/data` | `artifact_title` |
| InfoTechCanon DevSecOps Model | `model/devsecops` | `artifact_title` |
| InfoTechCanon Governance Model | `model/governance` | `artifact_title` |
| InfoTechCanon Information Space Model | `model/information-space` | `artifact_title` |
| InfoTechCanon Landscape Model | `model/landscape` | `artifact_title` |
| InfoTechCanon Network Model | `model/network` | `artifact_title` |
| InfoTechCanon Observability Model | `model/observability` | `artifact_title` |
| InfoTechCanon Organization Model | `model/organization` | `artifact_title` |
| InfoTechCanon Security Model | `model/security` | `artifact_title` |
| InfoTechCanon Task Model | `model/task` | `artifact_title` |
| InfoTechCanon CARING Access Governance Standard | `standard/caring` | `artifact_title` |
| CARINGAccessDescriptor | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGCanonicalRole | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGOrganizationRelation | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGPlane | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGCapabilityProfile | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGExposureMode | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGExposureEvent | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGDeclaredAccessMap | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGEffectiveAccessMap | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGDerivedCapability | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGInducedAccess | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGRestrictionPrecedence | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGAnalysisFitnessTest | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGAnalysisProcedure | `standard/caring` | `frontmatter.owned_concepts` |
| CARINGRedesignProcedure | `standard/caring` | `frontmatter.owned_concepts` |
| InfoTechCanon Tagging Standard | `standard/tagging` | `artifact_title` |
## Duplicate Candidates
No duplicate concept candidates detected.
## Ownership Conflicts
No ownership conflicts detected.

View File

@@ -0,0 +1,90 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# By Mapping Target
## `kernel/itc-core`
- `kernel/itc-kernel-map` via `maps`
- `model/access-control` via `conforms_to`
- `model/data` via `conforms_to`
- `model/devsecops` via `conforms_to`
- `model/governance` via `conforms_to`
- `model/information-space` via `conforms_to`
- `model/landscape` via `conforms_to`
- `model/network` via `conforms_to`
- `model/observability` via `conforms_to`
- `model/organization` via `conforms_to`
- `model/security` via `conforms_to`
- `model/task` via `conforms_to`
- `standard/caring` via `conforms_to`
- `standard/tagging` via `conforms_to`
## `model/access-control`
- `kernel/itc-kernel-map` via `maps`
- `model/security` via `uses`
- `standard/caring` via `imports`
## `model/data`
- `kernel/itc-kernel-map` via `maps`
- `standard/caring` via `imports`
## `model/devsecops`
- `kernel/itc-kernel-map` via `maps`
- `standard/caring` via `imports`
## `model/governance`
- `kernel/itc-kernel-map` via `maps`
- `model/access-control` via `uses`
- `model/data` via `uses`
- `standard/caring` via `imports`
## `model/information-space`
- `kernel/itc-kernel-map` via `maps`
## `model/landscape`
- `kernel/itc-kernel-map` via `maps`
## `model/network`
- `kernel/itc-kernel-map` via `maps`
- `standard/caring` via `imports`
## `model/observability`
- `kernel/itc-kernel-map` via `maps`
- `standard/caring` via `imports`
## `model/organization`
- `kernel/itc-kernel-map` via `maps`
- `model/access-control` via `uses`
- `standard/caring` via `imports`
## `model/security`
- `kernel/itc-kernel-map` via `maps`
- `model/devsecops` via `uses`
- `model/network` via `uses`
- `standard/caring` via `imports`
## `model/task`
- `kernel/itc-kernel-map` via `maps`
- `model/observability` via `uses`
- `standard/caring` via `imports`
- `standard/tagging` via `imports`
## `standard/caring`
- `kernel/itc-kernel-map` via `maps`
## `standard/tagging`
- `kernel/itc-kernel-map` via `maps`
- `standard/caring` via `imports`

View File

@@ -0,0 +1,5 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# By Profile
No profiles have been registered yet.

View File

@@ -0,0 +1,31 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# By Standard
## InfoTechCanon Core
- ID: `kernel/itc-core`
- Kind: `kernel`
- Path: `kernel/InfoTechCanonCore.md`
- Relationships: 0
## InfoTechCanon Kernel Map
- ID: `kernel/itc-kernel-map`
- Kind: `kernel`
- Path: `kernel/InfoTechCanonKernelMap.md`
- Relationships: 14
## InfoTechCanon CARING Access Governance Standard
- ID: `standard/caring`
- Kind: `standard`
- Path: `standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md`
- Relationships: 11
## InfoTechCanon Tagging Standard
- ID: `standard/tagging`
- Kind: `standard`
- Path: `standards/tagging/InfoTechCanonTaggingStandard.md`
- Relationships: 2

View File

@@ -0,0 +1,21 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# Import Matrix
| Artifact | `kernel/itc-core` | `kernel/itc-kernel-map` | `model/access-control` | `model/data` | `model/devsecops` | `model/governance` | `model/information-space` | `model/landscape` | `model/network` | `model/observability` | `model/organization` | `model/security` | `model/task` | `standard/caring` | `standard/tagging` |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `kernel/itc-core` | | | | | | | | | | | | | | | |
| `kernel/itc-kernel-map` | `maps` | | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` | `maps` |
| `model/access-control` | `conforms_to` | | | | | `uses` | | | | | `uses` | | | | |
| `model/data` | `conforms_to` | | | | | `uses` | | | | | | | | | |
| `model/devsecops` | `conforms_to` | | | | | | | | | | | `uses` | | | |
| `model/governance` | `conforms_to` | | | | | | | | | | | | | | |
| `model/information-space` | `conforms_to` | | | | | | | | | | | | | | |
| `model/landscape` | `conforms_to` | | | | | | | | | | | | | | |
| `model/network` | `conforms_to` | | | | | | | | | | | `uses` | | | |
| `model/observability` | `conforms_to` | | | | | | | | | | | | `uses` | | |
| `model/organization` | `conforms_to` | | | | | | | | | | | | | | |
| `model/security` | `conforms_to` | | `uses` | | | | | | | | | | | | |
| `model/task` | `conforms_to` | | | | | | | | | | | | | | |
| `standard/caring` | `conforms_to` | | `imports` | `imports` | `imports` | `imports` | | | `imports` | `imports` | `imports` | `imports` | `imports` | | `imports` |
| `standard/tagging` | `conforms_to` | | | | | | | | | | | | `imports` | | |

View File

@@ -0,0 +1,19 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# Kernel Overview
- Infospace: `canon`
- Artifacts: 15
## Artifact Kinds
- `kernel`: 2
- `model`: 11
- `standard`: 2
## Relationship Types
- `conforms_to`: 13
- `imports`: 11
- `maps`: 14
- `uses`: 7

View File

@@ -0,0 +1,56 @@
<!-- GENERATED by info_tech_canon; do not edit by hand. -->
# Repository Tree
File count: **50**
- `README.md`
- `agent/README.md`
- `agent/global-agent-brief.md`
- `artifacts/index.yaml`
- `assimilation/README.md`
- `examples/README.md`
- `indexes/README.md`
- `indexes/artifact-tree.yaml`
- `indexes/concept-ownership.yaml`
- `indexes/import-matrix.yaml`
- `infospace.yaml`
- `kernel/InfoTechCanonCore.md`
- `kernel/InfoTechCanonKernelMap.md`
- `mappings/README.md`
- `models/access-control/InfoTechCanonAccessControlModel.md`
- `models/data/InfoTechCanonDataModel.md`
- `models/devsecops/InfoTechCanonDevSecOpsModel.md`
- `models/governance/InfoTechCanonGovernanceModel.md`
- `models/information-space/InfoTechCanonInformationSpaceModel.md`
- `models/landscape/InfoTechCanonLandscapeModel.md`
- `models/network/InfoTechCanonNetworkModel.md`
- `models/observability/InfoTechCanonObservabilityModel.md`
- `models/organization/InfoTechCanonOrganizationModel.md`
- `models/security/InfoTechCanonSecurityModel.md`
- `models/task/InfoTechCanonTaskModel.md`
- `patterns/README.md`
- `profiles/README.md`
- `reports/scaffold-placement.md`
- `schemas/README.md`
- `schemas/agent-brief.schema.yaml`
- `schemas/assimilation.schema.yaml`
- `schemas/concept.schema.yaml`
- `schemas/index.yaml`
- `schemas/interface-card.schema.yaml`
- `schemas/mapping.schema.yaml`
- `schemas/profile.schema.yaml`
- `schemas/standard.schema.yaml`
- `schemas/workplan.schema.yaml`
- `standards/caring/InfoTechCanonCaringAccessGovernanceStandard.md`
- `standards/tagging/InfoTechCanonTaggingStandard.md`
- `validation/README.md`
- `validation/latest.json`
- `views/README.md`
- `views/by-concept.md`
- `views/by-mapping-target.md`
- `views/by-profile.md`
- `views/by-standard.md`
- `views/import-matrix.md`
- `views/kernel-overview.md`
- `views/repository-tree.md`

View File

@@ -3,21 +3,33 @@
from .service import ( from .service import (
CanonServiceError, CanonServiceError,
artifact_graph, artifact_graph,
generate_agent_briefs,
generate_indexes,
generate_tree,
inspect_canon, inspect_canon,
list_artifacts, list_artifacts,
list_models, list_models,
list_standards, list_standards,
list_views,
profile_inspect, profile_inspect,
read_view,
validate_canon, validate_canon,
write_validation_report,
) )
__all__ = [ __all__ = [
"CanonServiceError", "CanonServiceError",
"artifact_graph", "artifact_graph",
"generate_agent_briefs",
"generate_indexes",
"generate_tree",
"inspect_canon", "inspect_canon",
"list_artifacts", "list_artifacts",
"list_models", "list_models",
"list_standards", "list_standards",
"list_views",
"profile_inspect", "profile_inspect",
"read_view",
"validate_canon", "validate_canon",
"write_validation_report",
] ]

View File

@@ -14,7 +14,9 @@ from .service import (
list_artifacts, list_artifacts,
list_models, list_models,
list_standards, list_standards,
list_views,
profile_inspect, profile_inspect,
read_view,
validate_canon, validate_canon,
) )
@@ -86,6 +88,11 @@ def _route(
if path == "/graph": if path == "/graph":
graph_format = _first(query, "format") or "json" graph_format = _first(query, "format") or "json"
return HTTPStatus.OK, artifact_graph(root, output_format=graph_format) return HTTPStatus.OK, artifact_graph(root, output_format=graph_format)
if path == "/views":
return HTTPStatus.OK, list_views(root)
if path.startswith("/views/"):
name = path.removeprefix("/views/").strip("/")
return HTTPStatus.OK, read_view(name, root)
if path.startswith("/profiles/") and path.endswith("/inspect"): if path.startswith("/profiles/") and path.endswith("/inspect"):
profile = path.removeprefix("/profiles/").removesuffix("/inspect").strip("/") profile = path.removeprefix("/profiles/").removesuffix("/inspect").strip("/")
return HTTPStatus.OK, profile_inspect(profile, root) return HTTPStatus.OK, profile_inspect(profile, root)

View File

@@ -11,12 +11,18 @@ from .api import serve
from .service import ( from .service import (
CanonServiceError, CanonServiceError,
artifact_graph, artifact_graph,
generate_agent_briefs,
generate_indexes,
generate_tree,
inspect_canon, inspect_canon,
list_artifacts, list_artifacts,
list_models, list_models,
list_standards, list_standards,
list_views,
profile_inspect, profile_inspect,
read_view,
validate_canon, validate_canon,
write_validation_report,
) )
@@ -46,8 +52,26 @@ def build_parser() -> argparse.ArgumentParser:
standards.set_defaults(handler=_standards) standards.set_defaults(handler=_standards)
validate = sub.add_parser("validate", help="Validate the canon infospace") validate = sub.add_parser("validate", help="Validate the canon infospace")
validate.add_argument(
"--write",
default="",
help="Write the JSON validation payload to this path.",
)
validate.set_defaults(handler=_validate) validate.set_defaults(handler=_validate)
index = sub.add_parser("index", help="Refresh generated indexes and views")
index.set_defaults(handler=_index)
tree = sub.add_parser("tree", help="Refresh the generated infospace tree")
tree.set_defaults(handler=_tree)
agent_briefs = sub.add_parser("agent-briefs", help="Refresh generated agent briefs")
agent_briefs.set_defaults(handler=_agent_briefs)
views = sub.add_parser("views", help="List or read generated views")
views.add_argument("name", nargs="?", default="")
views.set_defaults(handler=_views)
graph = sub.add_parser("graph", help="Export the canon artifact graph") graph = sub.add_parser("graph", help="Export the canon artifact graph")
graph.add_argument("--format", choices=["json", "mermaid"], default="json") graph.add_argument("--format", choices=["json", "mermaid"], default="json")
graph.set_defaults(handler=_graph) graph.set_defaults(handler=_graph)
@@ -113,9 +137,29 @@ def _standards(args: argparse.Namespace) -> dict[str, Any]:
def _validate(args: argparse.Namespace) -> dict[str, Any]: def _validate(args: argparse.Namespace) -> dict[str, Any]:
if args.write:
return write_validation_report(args.write, _root(args))
return validate_canon(_root(args)) return validate_canon(_root(args))
def _index(args: argparse.Namespace) -> dict[str, Any]:
return generate_indexes(_root(args))
def _tree(args: argparse.Namespace) -> dict[str, Any]:
return generate_tree(_root(args))
def _agent_briefs(args: argparse.Namespace) -> dict[str, Any]:
return generate_agent_briefs(_root(args))
def _views(args: argparse.Namespace) -> dict[str, Any]:
if args.name:
return read_view(args.name, _root(args))
return list_views(_root(args))
def _graph(args: argparse.Namespace) -> dict[str, Any]: def _graph(args: argparse.Namespace) -> dict[str, Any]:
return artifact_graph(_root(args), output_format=args.format) return artifact_graph(_root(args), output_format=args.format)

View File

@@ -0,0 +1,427 @@
from __future__ import annotations
from collections import defaultdict
from pathlib import Path
from typing import Any
import yaml
GENERATED_NOTICE = "<!-- GENERATED by info_tech_canon; do not edit by hand. -->"
def generate_indexes(context: Any) -> dict[str, Any]:
assets: list[dict[str, Any]] = []
ownership = concept_ownership(context)
import_matrix = relationship_matrix(context)
assets.append(
_write_yaml(
context.infospace_root / "indexes" / "concept-ownership.yaml",
ownership,
)
)
assets.append(
_write_yaml(
context.infospace_root / "indexes" / "import-matrix.yaml",
import_matrix,
)
)
assets.append(
_write_yaml(
context.infospace_root / "indexes" / "artifact-tree.yaml",
artifact_tree(context),
)
)
assets.extend(generate_views(context, ownership, import_matrix)["files"])
return _result("index", assets)
def generate_views(
context: Any,
ownership: dict[str, Any] | None = None,
import_matrix: dict[str, Any] | None = None,
) -> dict[str, Any]:
ownership = ownership or concept_ownership(context)
import_matrix = import_matrix or relationship_matrix(context)
files = [
_write_text(
context.infospace_root / "views" / "by-standard.md",
_render_by_standard(context),
),
_write_text(
context.infospace_root / "views" / "by-concept.md",
_render_by_concept(ownership),
),
_write_text(
context.infospace_root / "views" / "by-profile.md",
_render_by_profile(context),
),
_write_text(
context.infospace_root / "views" / "by-mapping-target.md",
_render_by_mapping_target(context),
),
_write_text(
context.infospace_root / "views" / "kernel-overview.md",
_render_kernel_overview(context),
),
_write_text(
context.infospace_root / "views" / "import-matrix.md",
_render_import_matrix(import_matrix),
),
]
return _result("views", files)
def generate_tree(context: Any) -> dict[str, Any]:
tree = artifact_tree(context)
files = [
_write_yaml(context.infospace_root / "indexes" / "artifact-tree.yaml", tree),
_write_text(
context.infospace_root / "views" / "repository-tree.md",
_render_repository_tree(tree),
),
]
return _result("tree", files)
def generate_agent_briefs(context: Any) -> dict[str, Any]:
files = [
_write_text(
context.infospace_root / "agent" / "global-agent-brief.md",
_render_global_agent_brief(context),
)
]
return _result("agent-briefs", files)
def list_generated_views(context: Any) -> dict[str, Any]:
views = []
for path in sorted((context.infospace_root / "views").glob("*.md")):
views.append(
{
"name": path.name,
"path": str(path.relative_to(context.infospace_root)),
"generated": _is_generated(path),
}
)
return {"ok": True, "count": len(views), "views": views}
def read_generated_view(context: Any, name: str) -> dict[str, Any]:
if "/" in name or "\\" in name:
raise ValueError("View name must be a single file name.")
path = context.infospace_root / "views" / name
if not path.is_file():
raise FileNotFoundError(name)
return {
"ok": True,
"name": name,
"path": str(path.relative_to(context.infospace_root)),
"generated": _is_generated(path),
"content": path.read_text(encoding="utf-8"),
}
def concept_ownership(context: Any) -> dict[str, Any]:
concepts: list[dict[str, Any]] = []
for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id):
concepts.append(
{
"concept": artifact.title,
"owner": artifact.id,
"path": artifact.path,
"source": "artifact_title",
}
)
frontmatter = _frontmatter(context.infospace_root / artifact.path)
owned = frontmatter.get("owned_concepts") or []
if isinstance(owned, list):
for concept in owned:
concepts.append(
{
"concept": str(concept),
"owner": artifact.id,
"path": artifact.path,
"source": "frontmatter.owned_concepts",
}
)
by_key: dict[str, list[dict[str, Any]]] = defaultdict(list)
for item in concepts:
by_key[_normalize_concept(item["concept"])].append(item)
duplicates = [
{"normalized": key, "candidates": items}
for key, items in sorted(by_key.items())
if len(items) > 1
]
conflicts = [
{
"normalized": item["normalized"],
"owners": sorted({candidate["owner"] for candidate in item["candidates"]}),
"candidates": item["candidates"],
}
for item in duplicates
if len({candidate["owner"] for candidate in item["candidates"]}) > 1
]
return {
"concept_count": len(concepts),
"concepts": concepts,
"duplicate_candidates": duplicates,
"ownership_conflicts": conflicts,
}
def relationship_matrix(context: Any) -> dict[str, Any]:
artifact_ids = sorted(artifact.id for artifact in context.infospace.artifacts)
rows: list[dict[str, Any]] = []
for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id):
targets: dict[str, list[str]] = {target: [] for target in artifact_ids}
for relationship in artifact.relationships:
target = relationship.get("target")
relation_type = str(relationship.get("type") or "related")
if isinstance(target, str) and target in targets:
targets[target].append(relation_type)
rows.append(
{
"artifact": artifact.id,
"targets": {
target: sorted(types)
for target, types in targets.items()
if types
},
}
)
return {"artifacts": artifact_ids, "rows": rows}
def artifact_tree(context: Any) -> dict[str, Any]:
files: list[dict[str, Any]] = []
for path in sorted(context.infospace_root.rglob("*")):
if path.is_file():
relative = path.relative_to(context.infospace_root)
files.append(
{
"path": str(relative),
"directory": str(relative.parent),
"name": path.name,
}
)
return {"root": "infospace", "file_count": len(files), "files": files}
def _render_by_standard(context: Any) -> str:
lines = _heading("By Standard")
standards = [
artifact
for artifact in context.infospace.artifacts
if artifact.kind in {"kernel", "standard"}
]
for artifact in sorted(standards, key=lambda item: item.id):
lines.extend(
[
f"## {artifact.title}",
"",
f"- ID: `{artifact.id}`",
f"- Kind: `{artifact.kind}`",
f"- Path: `{artifact.path}`",
f"- Relationships: {len(artifact.relationships)}",
"",
]
)
return "\n".join(lines).rstrip() + "\n"
def _render_by_concept(ownership: dict[str, Any]) -> str:
lines = _heading("By Concept")
lines.extend(
[
f"Concept count: **{ownership['concept_count']}**",
"",
"| Concept | Owner | Source |",
"| --- | --- | --- |",
]
)
for concept in ownership["concepts"]:
lines.append(
f"| {concept['concept']} | `{concept['owner']}` | `{concept['source']}` |"
)
lines.extend(["", "## Duplicate Candidates", ""])
duplicates = ownership["duplicate_candidates"]
if not duplicates:
lines.append("No duplicate concept candidates detected.")
else:
for duplicate in duplicates:
lines.append(f"- `{duplicate['normalized']}`")
lines.extend(["", "## Ownership Conflicts", ""])
conflicts = ownership["ownership_conflicts"]
if not conflicts:
lines.append("No ownership conflicts detected.")
else:
for conflict in conflicts:
owners = ", ".join(f"`{owner}`" for owner in conflict["owners"])
lines.append(f"- `{conflict['normalized']}` owned by {owners}")
return "\n".join(lines).rstrip() + "\n"
def _render_by_profile(context: Any) -> str:
lines = _heading("By Profile")
profiles = sorted((context.infospace_root / "profiles").glob("*/profile.yaml"))
if not profiles:
lines.append("No profiles have been registered yet.")
for path in profiles:
lines.extend(
[
f"## {path.parent.name}",
"",
f"- Path: `{path.relative_to(context.infospace_root)}`",
"",
]
)
return "\n".join(lines).rstrip() + "\n"
def _render_by_mapping_target(context: Any) -> str:
incoming: dict[str, list[tuple[str, str]]] = defaultdict(list)
for artifact in context.infospace.artifacts:
for relationship in artifact.relationships:
target = relationship.get("target")
relation_type = str(relationship.get("type") or "related")
if isinstance(target, str):
incoming[target].append((artifact.id, relation_type))
lines = _heading("By Mapping Target")
for target in sorted(incoming):
lines.extend([f"## `{target}`", ""])
for source, relation_type in sorted(incoming[target]):
lines.append(f"- `{source}` via `{relation_type}`")
lines.append("")
return "\n".join(lines).rstrip() + "\n"
def _render_kernel_overview(context: Any) -> str:
kind_counts: dict[str, int] = defaultdict(int)
relationship_counts: dict[str, int] = defaultdict(int)
for artifact in context.infospace.artifacts:
kind_counts[artifact.kind] += 1
for relationship in artifact.relationships:
relationship_counts[str(relationship.get("type") or "related")] += 1
lines = _heading("Kernel Overview")
lines.extend(
[
f"- Infospace: `{context.infospace.config.slug}`",
f"- Artifacts: {len(context.infospace.artifacts)}",
"",
"## Artifact Kinds",
"",
]
)
for kind, count in sorted(kind_counts.items()):
lines.append(f"- `{kind}`: {count}")
lines.extend(["", "## Relationship Types", ""])
for relation_type, count in sorted(relationship_counts.items()):
lines.append(f"- `{relation_type}`: {count}")
return "\n".join(lines).rstrip() + "\n"
def _render_import_matrix(matrix: dict[str, Any]) -> str:
artifacts = matrix["artifacts"]
lines = _heading("Import Matrix")
header = "| Artifact | " + " | ".join(f"`{artifact}`" for artifact in artifacts) + " |"
divider = "| --- | " + " | ".join("---" for _ in artifacts) + " |"
lines.extend([header, divider])
for row in matrix["rows"]:
cells = []
targets = row["targets"]
for artifact in artifacts:
cells.append(", ".join(f"`{item}`" for item in targets.get(artifact, [])))
lines.append(f"| `{row['artifact']}` | " + " | ".join(cells) + " |")
return "\n".join(lines).rstrip() + "\n"
def _render_repository_tree(tree: dict[str, Any]) -> str:
lines = _heading("Repository Tree")
lines.append(f"File count: **{tree['file_count']}**")
lines.append("")
for file_info in tree["files"]:
lines.append(f"- `{file_info['path']}`")
return "\n".join(lines).rstrip() + "\n"
def _render_global_agent_brief(context: Any) -> str:
lines = _heading("Global Agent Brief")
lines.extend(
[
"This brief summarizes the current canon service surface for agents.",
"",
f"- Infospace slug: `{context.infospace.config.slug}`",
f"- Artifact count: {len(context.infospace.artifacts)}",
"- Primary confidence command: `make validate`",
"- Refresh generated indexes and views with: `make index`",
"",
"## Useful Commands",
"",
"- `PYTHONPATH=src python3 -m info_tech_canon inspect`",
"- `PYTHONPATH=src python3 -m info_tech_canon validate`",
"- `PYTHONPATH=src python3 -m info_tech_canon graph`",
"- `PYTHONPATH=src python3 -m info_tech_canon index`",
"",
"## Consumption Notes",
"",
"- Treat `seeds/` as provenance.",
"- Treat `infospace/` as the service-consumable canon root.",
"- Generated files are marked and can be refreshed deterministically.",
]
)
return "\n".join(lines).rstrip() + "\n"
def _heading(title: str) -> list[str]:
return [GENERATED_NOTICE, "", f"# {title}", ""]
def _write_text(path: Path, content: str) -> dict[str, Any]:
path.parent.mkdir(parents=True, exist_ok=True)
old = path.read_text(encoding="utf-8") if path.exists() else None
changed = old != content
if changed:
path.write_text(content, encoding="utf-8")
return {"path": str(path), "changed": changed}
def _write_yaml(path: Path, data: dict[str, Any]) -> dict[str, Any]:
path.parent.mkdir(parents=True, exist_ok=True)
content = yaml.safe_dump(data, sort_keys=False)
return _write_text(path, content)
def _result(kind: str, files: list[dict[str, Any]]) -> dict[str, Any]:
return {
"ok": True,
"kind": kind,
"count": len(files),
"changed": [item for item in files if item["changed"]],
"files": files,
}
def _frontmatter(path: Path) -> dict[str, Any]:
text = path.read_text(encoding="utf-8")
if not text.startswith("---\n"):
return {}
end = text.find("\n---\n", 4)
if end == -1:
return {}
data = yaml.safe_load(text[4:end]) or {}
return data if isinstance(data, dict) else {}
def _normalize_concept(value: str) -> str:
return "-".join(value.lower().replace("_", "-").split())
def _is_generated(path: Path) -> bool:
try:
return path.read_text(encoding="utf-8").startswith(GENERATED_NOTICE)
except FileNotFoundError:
return False

View File

@@ -3,10 +3,12 @@ from __future__ import annotations
from collections import Counter from collections import Counter
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from pathlib import Path from pathlib import Path
import json
from typing import Any from typing import Any
import yaml import yaml
from . import generation
from .bench import ( from .bench import (
Infospace, Infospace,
KnowledgeArtifact, KnowledgeArtifact,
@@ -15,6 +17,7 @@ from .bench import (
relationship_summary, relationship_summary,
run_collection_checks, run_collection_checks,
) )
from .validation import structural_checks
REPO_ROOT = Path(__file__).resolve().parents[2] REPO_ROOT = Path(__file__).resolve().parents[2]
@@ -121,6 +124,7 @@ def list_standards(root: Path | str | None = None) -> dict[str, Any]:
def validate_canon(root: Path | str | None = None) -> dict[str, Any]: def validate_canon(root: Path | str | None = None) -> dict[str, Any]:
context = load_context(root) context = load_context(root)
errors: list[dict[str, Any]] = [] errors: list[dict[str, Any]] = []
warnings: list[dict[str, Any]] = []
artifact_ids = {artifact.id for artifact in context.infospace.artifacts} artifact_ids = {artifact.id for artifact in context.infospace.artifacts}
for artifact in context.infospace.artifacts: for artifact in context.infospace.artifacts:
@@ -161,15 +165,31 @@ def validate_canon(root: Path | str | None = None) -> dict[str, Any]:
context.infospace.config.viability, context.infospace.config.viability,
) )
errors.extend(threshold_errors) errors.extend(threshold_errors)
structural = structural_checks(context)
errors.extend(structural["errors"])
warnings.extend(structural["warnings"])
return { return {
"ok": not errors, "ok": not errors,
"errors": errors, "errors": errors,
"warnings": warnings,
"metrics": checks.metrics, "metrics": checks.metrics,
"details": checks.details, "details": checks.details,
} }
def write_validation_report(
destination: Path | str,
root: Path | str | None = None,
) -> dict[str, Any]:
payload = validate_canon(root)
path = Path(destination)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
payload["report_path"] = str(path)
return payload
def artifact_graph( def artifact_graph(
root: Path | str | None = None, root: Path | str | None = None,
*, *,
@@ -221,6 +241,39 @@ def profile_inspect(
return {"ok": True, "profile": data, "path": str(profile_path)} return {"ok": True, "profile": data, "path": str(profile_path)}
def generate_indexes(root: Path | str | None = None) -> dict[str, Any]:
return generation.generate_indexes(load_context(root))
def generate_tree(root: Path | str | None = None) -> dict[str, Any]:
return generation.generate_tree(load_context(root))
def generate_agent_briefs(root: Path | str | None = None) -> dict[str, Any]:
return generation.generate_agent_briefs(load_context(root))
def list_views(root: Path | str | None = None) -> dict[str, Any]:
return generation.list_generated_views(load_context(root))
def read_view(name: str, root: Path | str | None = None) -> dict[str, Any]:
try:
return generation.read_generated_view(load_context(root), name)
except FileNotFoundError as exc:
raise CanonServiceError(
"missing_view",
f"View not found: {name}",
{"view": name},
) from exc
except ValueError as exc:
raise CanonServiceError(
"invalid_view_name",
str(exc),
{"view": name},
) from exc
def _artifact_to_dict( def _artifact_to_dict(
artifact: KnowledgeArtifact, artifact: KnowledgeArtifact,
infospace_root: Path, infospace_root: Path,

View File

@@ -0,0 +1,360 @@
from __future__ import annotations
from pathlib import Path
from typing import Any
import yaml
REQUIRED_TOP_LEVEL_FILES = (
"README.md",
"INTENT.md",
"SCOPE.md",
"canon.yaml",
"pyproject.toml",
"workplans/index.yaml",
"infospace/infospace.yaml",
"infospace/artifacts/index.yaml",
)
REQUIRED_INFOSPACE_DIRS = (
"kernel",
"models",
"standards",
"profiles",
"patterns",
"mappings",
"assimilation",
"schemas",
"views",
"agent",
"examples",
"validation",
"indexes",
)
OPTIONAL_COLLECTION_DIRS = (
"profiles",
"patterns",
"mappings",
"assimilation",
"examples",
)
REQUIRED_SCHEMAS = (
"standard.schema.yaml",
"concept.schema.yaml",
"mapping.schema.yaml",
"profile.schema.yaml",
"assimilation.schema.yaml",
"interface-card.schema.yaml",
"agent-brief.schema.yaml",
"workplan.schema.yaml",
)
def structural_checks(context: Any) -> dict[str, list[dict[str, Any]]]:
errors: list[dict[str, Any]] = []
warnings: list[dict[str, Any]] = []
_check_required_top_level_files(context.repo_root, errors)
_check_required_infospace_dirs(context.infospace_root, errors)
_check_required_schemas(context.infospace_root, errors)
_check_canon_paths(context.repo_root, context.infospace_root, errors)
_check_artifact_index(context.repo_root, context.infospace_root, errors)
_check_optional_assets(context.infospace_root, warnings)
return {"errors": errors, "warnings": warnings}
def _check_required_top_level_files(
repo_root: Path,
errors: list[dict[str, Any]],
) -> None:
for relative in REQUIRED_TOP_LEVEL_FILES:
if not (repo_root / relative).is_file():
errors.append(
{
"code": "missing_required_file",
"path": relative,
}
)
def _check_required_infospace_dirs(
infospace_root: Path,
errors: list[dict[str, Any]],
) -> None:
for relative in REQUIRED_INFOSPACE_DIRS:
if not (infospace_root / relative).is_dir():
errors.append(
{
"code": "missing_required_infospace_dir",
"path": str(Path("infospace") / relative),
}
)
def _check_required_schemas(
infospace_root: Path,
errors: list[dict[str, Any]],
) -> None:
for filename in REQUIRED_SCHEMAS:
if not (infospace_root / "schemas" / filename).is_file():
errors.append(
{
"code": "missing_schema",
"path": str(Path("infospace") / "schemas" / filename),
}
)
def _check_canon_paths(
repo_root: Path,
infospace_root: Path,
errors: list[dict[str, Any]],
) -> None:
canon_path = repo_root / "canon.yaml"
canon = _read_yaml(canon_path, errors)
if not isinstance(canon, dict):
return
indexed_paths = _artifact_paths_by_path(infospace_root, errors)
for section in ("kernel", "models", "standards"):
items = canon.get(section) or []
if not isinstance(items, list):
errors.append(
{
"code": "invalid_canon_section",
"section": section,
"message": "Expected a list.",
}
)
continue
for item in items:
if not isinstance(item, dict):
errors.append(
{
"code": "invalid_canon_entry",
"section": section,
"message": "Expected a mapping.",
}
)
continue
path = str(item.get("path") or "")
if not path:
errors.append(
{
"code": "missing_canon_path",
"section": section,
"id": item.get("id"),
}
)
continue
if not (repo_root / path).is_file():
errors.append(
{
"code": "missing_canon_path_target",
"section": section,
"id": item.get("id"),
"path": path,
}
)
relative_infospace_path = _strip_infospace_prefix(path)
if relative_infospace_path not in indexed_paths:
errors.append(
{
"code": "canon_path_not_indexed",
"section": section,
"id": item.get("id"),
"path": path,
}
)
def _check_artifact_index(
repo_root: Path,
infospace_root: Path,
errors: list[dict[str, Any]],
) -> None:
index_path = infospace_root / "artifacts" / "index.yaml"
index = _read_yaml(index_path, errors)
if not isinstance(index, dict):
return
artifacts = index.get("artifacts")
if not isinstance(artifacts, list):
errors.append(
{
"code": "invalid_artifact_index",
"path": "infospace/artifacts/index.yaml",
"message": "Expected artifacts list.",
}
)
return
ids: set[str] = set()
for artifact in artifacts:
if not isinstance(artifact, dict):
errors.append(
{
"code": "invalid_artifact_entry",
"message": "Expected artifact mapping.",
}
)
continue
artifact_id = str(artifact.get("id") or "")
if not artifact_id:
errors.append({"code": "missing_artifact_id"})
elif artifact_id in ids:
errors.append(
{
"code": "duplicate_artifact_id",
"artifact_id": artifact_id,
}
)
ids.add(artifact_id)
for field in ("path", "kind", "title"):
if not artifact.get(field):
errors.append(
{
"code": "missing_artifact_field",
"artifact_id": artifact_id,
"field": field,
}
)
relative_path = str(artifact.get("path") or "")
if relative_path and not (infospace_root / relative_path).is_file():
errors.append(
{
"code": "missing_artifact_path",
"artifact_id": artifact_id,
"path": relative_path,
}
)
provenance = artifact.get("provenance") or {}
if isinstance(provenance, dict):
source_path = provenance.get("source_path")
if isinstance(source_path, str) and source_path:
if not (repo_root / source_path).is_file():
errors.append(
{
"code": "missing_provenance_source",
"artifact_id": artifact_id,
"source_path": source_path,
}
)
for artifact in artifacts:
if not isinstance(artifact, dict):
continue
artifact_id = str(artifact.get("id") or "")
relationships = artifact.get("relationships") or []
if not isinstance(relationships, list):
errors.append(
{
"code": "invalid_relationships",
"artifact_id": artifact_id,
"message": "Expected relationship list.",
}
)
continue
for relationship in relationships:
if not isinstance(relationship, dict):
errors.append(
{
"code": "invalid_relationship",
"artifact_id": artifact_id,
}
)
continue
target = relationship.get("target")
if target not in ids:
errors.append(
{
"code": "missing_relationship_target",
"artifact_id": artifact_id,
"target": target,
}
)
def _check_optional_assets(
infospace_root: Path,
warnings: list[dict[str, Any]],
) -> None:
global_brief = infospace_root / "agent" / "global-agent-brief.md"
if not global_brief.is_file():
warnings.append(
{
"code": "missing_optional_agent_brief",
"path": "infospace/agent/global-agent-brief.md",
}
)
concepts_dir = infospace_root / "concepts"
if not concepts_dir.is_dir():
warnings.append(
{
"code": "missing_optional_concepts_dir",
"path": "infospace/concepts",
}
)
for relative in OPTIONAL_COLLECTION_DIRS:
directory = infospace_root / relative
if directory.is_dir() and not _has_substantive_files(directory):
warnings.append(
{
"code": "empty_optional_collection",
"path": str(Path("infospace") / relative),
}
)
def _artifact_paths_by_path(
infospace_root: Path,
errors: list[dict[str, Any]],
) -> set[str]:
index = _read_yaml(infospace_root / "artifacts" / "index.yaml", errors)
if not isinstance(index, dict):
return set()
artifacts = index.get("artifacts") or []
if not isinstance(artifacts, list):
return set()
return {
str(artifact.get("path"))
for artifact in artifacts
if isinstance(artifact, dict) and artifact.get("path")
}
def _read_yaml(path: Path, errors: list[dict[str, Any]]) -> Any:
try:
with path.open("r", encoding="utf-8") as handle:
return yaml.safe_load(handle) or {}
except FileNotFoundError:
errors.append({"code": "missing_yaml", "path": str(path)})
except yaml.YAMLError as exc:
errors.append(
{
"code": "invalid_yaml",
"path": str(path),
"message": str(exc),
}
)
return None
def _strip_infospace_prefix(path: str) -> str:
prefix = "infospace/"
return path[len(prefix) :] if path.startswith(prefix) else path
def _has_substantive_files(directory: Path) -> bool:
for path in directory.rglob("*"):
if path.is_file() and path.name != "README.md":
return True
return False

View File

@@ -1,6 +1,8 @@
from http import HTTPStatus from http import HTTPStatus
import shutil
from info_tech_canon.api import _route from info_tech_canon.api import _route
from info_tech_canon.service import DEFAULT_INFOSPACE_ROOT, generate_indexes
def test_api_route_inspect() -> None: def test_api_route_inspect() -> None:
@@ -24,3 +26,16 @@ def test_api_route_unknown_endpoint() -> None:
assert status == HTTPStatus.NOT_FOUND assert status == HTTPStatus.NOT_FOUND
assert payload["ok"] is False assert payload["ok"] is False
assert payload["error"]["code"] == "not_found" assert payload["error"]["code"] == "not_found"
def test_api_route_reads_generated_view(tmp_path) -> None:
root = tmp_path / "infospace"
shutil.copytree(DEFAULT_INFOSPACE_ROOT, root)
generate_indexes(root)
status, payload = _route("/views/by-standard.md", {}, root)
assert status == HTTPStatus.OK
assert payload["ok"] is True
assert payload["generated"] is True
assert "# By Standard" in payload["content"]

View File

@@ -1,6 +1,8 @@
import json import json
import shutil
from info_tech_canon.cli import main from info_tech_canon.cli import main
from info_tech_canon.service import DEFAULT_INFOSPACE_ROOT
def test_cli_inspect_emits_json(capsys) -> None: def test_cli_inspect_emits_json(capsys) -> None:
@@ -19,3 +21,15 @@ def test_cli_missing_profile_uses_structured_error(capsys) -> None:
payload = json.loads(capsys.readouterr().out) payload = json.loads(capsys.readouterr().out)
assert payload["ok"] is False assert payload["ok"] is False
assert payload["error"]["code"] == "missing_profile" assert payload["error"]["code"] == "missing_profile"
def test_cli_index_generates_views(capsys, tmp_path) -> None:
root = tmp_path / "infospace"
shutil.copytree(DEFAULT_INFOSPACE_ROOT, root)
exit_code = main(["--root", str(root), "index"])
assert exit_code == 0
payload = json.loads(capsys.readouterr().out)
assert payload["ok"] is True
assert (root / "views" / "kernel-overview.md").is_file()

View File

@@ -1,10 +1,15 @@
from info_tech_canon.service import ( from info_tech_canon.service import (
artifact_graph, artifact_graph,
generate_agent_briefs,
generate_indexes,
generate_tree,
inspect_canon, inspect_canon,
list_models, list_models,
list_standards, list_standards,
validate_canon, validate_canon,
) )
from info_tech_canon.service import DEFAULT_INFOSPACE_ROOT
import shutil
def test_inspect_canon_counts_artifact_kinds() -> None: def test_inspect_canon_counts_artifact_kinds() -> None:
@@ -30,6 +35,7 @@ def test_validate_canon_passes_scaffold() -> None:
assert payload["ok"] is True assert payload["ok"] is True
assert payload["errors"] == [] assert payload["errors"] == []
assert "warnings" in payload
assert payload["details"]["artifact_count"] == 15 assert payload["details"]["artifact_count"] == 15
@@ -39,3 +45,21 @@ def test_graph_exports_relationship_summary() -> None:
assert payload["ok"] is True assert payload["ok"] is True
assert payload["graph"]["node_count"] == 15 assert payload["graph"]["node_count"] == 15
assert payload["graph"]["edge_count"] > 15 assert payload["graph"]["edge_count"] > 15
def test_generators_write_expected_assets(tmp_path) -> None:
root = tmp_path / "infospace"
shutil.copytree(DEFAULT_INFOSPACE_ROOT, root)
index_payload = generate_indexes(root)
tree_payload = generate_tree(root)
brief_payload = generate_agent_briefs(root)
assert index_payload["ok"] is True
assert tree_payload["ok"] is True
assert brief_payload["ok"] is True
assert (root / "indexes" / "concept-ownership.yaml").is_file()
assert (root / "views" / "by-standard.md").read_text(
encoding="utf-8"
).startswith("<!-- GENERATED")
assert (root / "agent" / "global-agent-brief.md").is_file()

View File

@@ -4,7 +4,7 @@ type: workplan
title: "Validation Indexes And Generated Views" title: "Validation Indexes And Generated Views"
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"
@@ -33,7 +33,7 @@ inconsistencies should fail visibly.
```task ```task
id: ITC-WP-0003-T01 id: ITC-WP-0003-T01
status: todo status: done
priority: high priority: high
state_hub_task_id: "246d5671-9585-43e1-880c-cc4ce728459f" state_hub_task_id: "246d5671-9585-43e1-880c-cc4ce728459f"
``` ```
@@ -47,7 +47,7 @@ state_hub_task_id: "246d5671-9585-43e1-880c-cc4ce728459f"
```task ```task
id: ITC-WP-0003-T02 id: ITC-WP-0003-T02
status: todo status: done
priority: high priority: high
state_hub_task_id: "8db6058f-754f-4d92-a93f-d4119b2cad55" state_hub_task_id: "8db6058f-754f-4d92-a93f-d4119b2cad55"
``` ```
@@ -62,7 +62,7 @@ state_hub_task_id: "8db6058f-754f-4d92-a93f-d4119b2cad55"
```task ```task
id: ITC-WP-0003-T03 id: ITC-WP-0003-T03
status: todo status: done
priority: high priority: high
state_hub_task_id: "ea3c6e98-fb3c-44fd-9f54-9a934ab62455" state_hub_task_id: "ea3c6e98-fb3c-44fd-9f54-9a934ab62455"
``` ```
@@ -81,7 +81,7 @@ state_hub_task_id: "ea3c6e98-fb3c-44fd-9f54-9a934ab62455"
```task ```task
id: ITC-WP-0003-T04 id: ITC-WP-0003-T04
status: todo status: done
priority: high priority: high
state_hub_task_id: "b33bbda7-8ec6-48b2-a637-571a87473c5a" state_hub_task_id: "b33bbda7-8ec6-48b2-a637-571a87473c5a"
``` ```
@@ -97,7 +97,7 @@ state_hub_task_id: "b33bbda7-8ec6-48b2-a637-571a87473c5a"
```task ```task
id: ITC-WP-0003-T05 id: ITC-WP-0003-T05
status: todo status: done
priority: medium priority: medium
state_hub_task_id: "01469609-91fb-48e3-b6e5-11e6342b29af" state_hub_task_id: "01469609-91fb-48e3-b6e5-11e6342b29af"
``` ```
@@ -113,3 +113,15 @@ state_hub_task_id: "01469609-91fb-48e3-b6e5-11e6342b29af"
- Validation produces machine-readable output. - Validation produces machine-readable output.
- Generated views can be refreshed deterministically. - Generated views can be refreshed deterministically.
- Concept ownership can be inspected even before all concept pages exist. - Concept ownership can be inspected even before all concept pages exist.
## Implementation Notes
- Added `make validate`, `make index`, `make tree`, and `make agent-briefs`.
- Added structural validation for required files, infospace directories,
`canon.yaml` paths, schema presence, artifact index integrity, relationship
targets, and optional collection warnings.
- Added initial schemas for standards, concepts, mappings, profiles,
assimilation records, interface cards, agent briefs, and workplans.
- Added deterministic generated views and indexes for standards, concepts,
profiles, mapping targets, kernel overview, import matrix, artifact tree, and
global agent guidance.

View File

@@ -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: proposed status: finished
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: