Skip to content

About calm

Common Architecture Language Model (CALM) Specification - LinkML Schema


Solution Design

Overview

This package provides a LinkML representation of the FINOS CALM 1.2 specification, enabling CALM architecture documents to participate in the broader Linked Data ecosystem. The design follows a three-stage pipeline:

CALM 1.2 JSON-Schema meta files
        │
        ▼
 calm_to_linkml.py          ← Stage 1: schema generation
        │
        ▼
 src/calm/schema/calm.yaml  ← authoritative LinkML schema (941 lines)
        │
        ├──▶ apply_sssom_overlay.py   ← Stage 2: semantic mapping
        │           │
        │           ▼
        │    src/calm/mappings/*.sssom.tsv
        │    (12 SSSOM mapping sets, 68 links)
        │
        └──▶ gen-project / gen-json-schema / gen-python …  ← Stage 3: artefacts

Stage 1 - Schema Generation (scripts/calm_to_linkml.py)

The CALM specification is authored as a set of JSON-Schema meta files (one per vocabulary domain). calm_to_linkml.py reads those files at build time and emits a single consolidated LinkML YAML schema.

Key design decisions:

Decision Rationale
Autogenerated schema, not hand-authored CALM's JSON-Schema meta is the single source of truth; a generator keeps the LinkML schema in sync across CALM releases without manual editing.
SLOT_ENRICHMENTS dict JSON-Schema properties carry no semantic metadata. Hand-curated overlays add slot_uri, aliases, range overrides, identifier, and minimum_value constraints on top of what can be inferred automatically.
CLASS_ENRICHMENTS dict Adds tree_root, class_uri, in_subset, close_mappings, and per-class slot_usage (e.g. inlined_as_list: true) that cannot be derived from JSON-Schema alone.
Per-class slot_usage for inlining CALM reuses slots (e.g. nodes) in two distinct semantic roles: embedded objects in Architecture/Decision, and string ID references in relationship subtypes. Global inlined_as_list on the slot breaks relationship subtypes; per-class slot_usage applies inlining only where required.
None-sentinel in overlay LinkML's gen-json-schema emits a broken $ref + anyOf[string, string] when a slot has both any_of (from a JSON-Schema anyOf) and inlined_as_list: true. Setting any_of: None in SLOT_ENRICHMENTS deletes the generated any_of key, pinning a single concrete range and producing a valid JSON Schema. See upstream-releases/ISSUE.md for the upstream bug report.
DEFINITION_CLASSES map Many CALM JSON-Schema $defs are anonymous helper types. This map resolves (filename, def-name) pairs to canonical LinkML class names, keeping class naming stable across CALM releases.

Generated schema statistics:

  • 32 classes (8 enums / types, 24 data classes) across 7 subsets: core, controls_framework, flow_modeling, interface_defs, decorators, timeline, units
  • 56 slots, enriched with slot URIs, aliases, identifier flags, and range constraints
  • Schema file: src/calm/schema/calm.yaml (~941 lines)

Stage 2 - Semantic Mapping (scripts/apply_sssom_overlay.py)

CALM concepts are mapped to well-known ontologies and standards using SSSOM (Simple Standard for Sharing Ontological Mappings). Twelve mapping sets are maintained as TSV files under src/calm/mappings/.

Mapping targets (12 files, 68 total mappings):

File Standard
calm-to-attack.sssom.tsv MITRE ATT&CK
calm-to-bpmn.sssom.tsv BPMN (FluxNova model)
calm-to-capec.sssom.tsv MITRE CAPEC
calm-to-cis-controls.sssom.tsv CIS Critical Security Controls
calm-to-dpv.sssom.tsv W3C Data Privacy Vocabulary
calm-to-gist.sssom.tsv Semantic Arts gist ontology
calm-to-iso27001.sssom.tsv ISO/IEC 27001
calm-to-nist-csf-v2.sssom.tsv NIST Cybersecurity Framework v2
calm-to-nist-sp-800-53.sssom.tsv NIST SP 800-53
calm-to-ocsf.sssom.tsv Open Cybersecurity Schema Framework
calm-to-oscal.sssom.tsv NIST OSCAL
calm-to-stix.sssom.tsv STIX 2.1

apply_sssom_overlay.py reads all mapping files and writes exact_mappings, close_mappings, and related_mappings predicates directly into calm.yaml. The overlay is idempotent - it overwrites only mapping keys and leaves all other schema content unchanged.


Stage 3 - Artefact Generation

Standard LinkML generators consume calm.yaml to produce downstream artefacts via just gen-project:

Generator Output
gen-json-schema JSON Schema for validating CALM instance documents
gen-python Python dataclasses with runtime validation
gen-docs MkDocs-ready Markdown (published to docs/elements/)
gen-owl OWL ontology for semantic reasoning
gen-shacl SHACL shapes for RDF graph validation
gen-prefixmap Canonical prefix registry

Data Validation

Test fixtures in tests/data/ validate that the generated schema correctly accepts and rejects CALM instance documents:

  • 23 valid fixtures (tests/data/valid/) - one per major class, exercising required fields and embedded object inlining
  • 25 invalid fixtures (tests/data/invalid/) - one violation per fixture (missing required field, value below minimum_value, or disallowed additional property)
  • 10 valid vendor fixtures (tests/data/chandralanka/valid/) - real-world "in-the-wild" data derived from the CALM examples repository (e-commerce platform architecture with actors, services, databases, and all relationship types)
  • 3 invalid vendor fixtures (tests/data/chandralanka/invalid/) - vendor data with required fields omitted

Tests call linkml-validate --target-class <ClassName> <file.yaml> via subprocess. The target class is derived from the filename stem (ClassName-description.yamlClassName). All 48 parameterised tests run in tests/test_data.py.

The just test recipe runs both the core fixtures and vendor fixtures via linkml-run-examples.


Build Commands

# Regenerate schema from CALM 1.2 JSON-Schema sources
just gen-linkml           # runs calm_to_linkml.py then apply_sssom_overlay.py

# Generate all downstream artefacts (docs, JSON Schema, Python, OWL, ...)
just gen-project

# Run all tests
uv run pytest tests/

# Run only data-validation tests
uv run pytest tests/test_data.py

# Lint the schema
uv run linkml-lint src/calm/schema/calm.yaml

Known Limitations

  • controls slot - CALM's controls property uses a JSON-Schema patternProperties map keyed by control ID. LinkML has no native patternProperties equivalent; the slot is emitted with range: Control (a single nullable object) and the map structure is lost. Instance documents that embed an array of controls will fail schema validation.
  • InterfaceType - The any_of: [InterfaceDefinition, InterfaceType] union on the interfaces slot is collapsed to range: InterfaceDefinition to work around a LinkML upstream bug (see upstream-releases/ISSUE.md). InterfaceType objects are not currently validatable via linkml-validate.
  • additionalProperties - JSON Schema additionalProperties: false constraints are generated for most classes, but linkml-validate enforces them inconsistently for empty/null documents.