This guide explains how to use CALM (Common Architecture Language Model) for architecture-as-code governance in SEA-Forge™. CALM enables formal architectural definition, validation, and automated compliance checking as part of the spec-first delivery pipeline.
CALM is a FINOS standard for machine-readable architecture definitions. In SEA-Forge™, CALM serves as:
Ensure your environment is set up:
1
2
3
4
5
6
7
8
9
# Run the doctor script to verify dependencies
just doctor
# Install CALM CLI (if not already installed)
# Option 1: Via mise (recommended for global access)
mise install
# Option 2: Via pnpm (local to project)
pnpm install
Note: CALM CLI is configured in .mise.toml and will be available globally when using mise.
Generate the CALM architecture model from your SDS specifications:
1
just calm-generate
This reads all *.sds.yaml files from docs/specs/ and generates architecture/generated/sea-forge.arch.json.
Validate the generated architecture against the SEA-Forge™ pattern:
1
just calm-validate
This checks:
Run generation and validation together:
1
just calm-pipeline
1
2
3
4
5
6
architecture/
├── patterns/
│ └── sea-forge.pattern.json # Validation pattern (required structure)
├── generated/
│ └── sea-forge.arch.json # Generated from SDS files
└── README.md # Quick reference
The sds_to_calm.py tool transforms SDS specifications into CALM architecture:
1
SDS YAML Files → sds_to_calm.py → CALM Architecture JSON
Each SDS file with a service section generates a CALM node:
SDS Example (llm-provider.sds.yaml):
1
2
3
4
5
6
7
8
9
10
11
12
metadata:
id: SDS-049
title: LLM Provider Service
bounded_context: llm-provider
framework: FastAPI
service:
name: LLM Provider Service
description: Unified interface for LLM providers
ports:
- name: LlmProviderPort
type: interface
Generated CALM Node:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"unique-id": "llm-provider-service",
"name": "LLM Provider Service",
"description": "Service for llm-provider bounded context (SDS: SDS-049)",
"node-type": "service",
"metadata": {
"bounded-context": "llm-provider",
"sds-id": "SDS-049",
"source-file": "docs/specs/llm-provider/llm-provider.sds.yaml",
"framework": "FastAPI"
},
"interfaces": [
{
"unique-id": "llm-provider-service-http",
"protocol": "HTTP",
"port": 8085,
"host": "localhost"
}
]
}
Dependencies between services create CALM relationships:
SDS Example:
1
2
3
dependencies:
- service: llm-provider
reason: Chat completion and embeddings
Generated CALM Relationship:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"unique-id": "cognitive-extension-to-llm-provider",
"description": "Chat completion and embeddings",
"relationship-type": {
"connects": {
"source": {
"node": "cognitive-extension-service",
"interface": "cognitive-extension-service-http"
},
"destination": {
"node": "llm-provider-service",
"interface": "llm-provider-service-http"
}
}
},
"protocol": "HTTP"
}
The generator normalizes protocol names to CALM-compliant values:
| SDS Protocol | CALM Protocol |
|---|---|
http |
HTTP |
https |
HTTPS |
nats |
AMQP |
grpc |
HTTP |
websocket |
WebSocket |
internal |
TCP |
resp |
TCP |
The architecture/patterns/sea-forge.pattern.json defines the required structure for SEA-Forge™ architecture:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"$schema": "https://calm.finos.org/release/1.0-rc2/meta/calm.json",
"$id": "https://sea-forge.dev/patterns/sea-forge.pattern.json",
"nodes": [
{
"unique-id": "sea-forge-service-pattern",
"node-type": "service",
"metadata": {
"bounded-context": "<required>",
"sds-id": "<required>",
"framework": "<required>"
}
}
]
}
The CALM CLI validates:
Success:
1
2
🏛️ Validating CALM architecture...
✅ CALM validation passed
Failure Example:
1
2
3
4
5
🏛️ Validating CALM architecture...
Error: Validation failed
- Node 'payment-service' missing required metadata field: 'sds-id'
- Relationship 'order-to-payment' references unknown node: 'payment-gateway'
- Interface protocol 'MQTT' not in allowed list
1
just calm-generate
Options (via tools/sds_to_calm.py):
1
2
3
4
python tools/sds_to_calm.py \
--input docs/specs \
--output architecture/generated/sea-forge.arch.json \
--pattern architecture/patterns/sea-forge.pattern.json
--input: Directory to scan for SDS files--output: Output path for generated CALM JSON--pattern: Pattern file to validate against1
just calm-validate
Runs the CALM CLI validator:
1
2
3
pnpm exec calm validate \
--architecture architecture/generated/sea-forge.arch.json \
--pattern architecture/patterns/sea-forge.pattern.json
1
just calm-docify
Generates architecture documentation website:
1
2
3
pnpm exec calm docify \
--architecture architecture/generated/sea-forge.arch.json \
--output architecture/docs/
Creates:
index.html - Architecture overviewnodes/ - Individual node pagesrelationships/ - Relationship diagramscontrols/ - Compliance status1
just calm-status
Displays:
CALM validation is part of the pre-commit workflow:
1
2
3
4
5
6
7
8
#!/bin/bash
# .githooks/pre-commit
# Generate and validate CALM architecture
just calm-pipeline || {
echo "❌ CALM validation failed"
exit 1
}
CALM validation runs in CI:
1
2
3
4
5
# .github/workflows/ci.yml
- name: Validate CALM Architecture
run: |
just calm-generate
just calm-validate
CALM generation is part of the spec-first pipeline:
1
ADR → PRD → SDS → CALM Architecture → Generated Code
Run the full pipeline:
1
just pipeline <context>
1
2
# Create specs for new bounded context
python tools/sea_new_context.py notification
1
2
3
4
5
6
7
8
9
10
11
12
# docs/specs/notification/notification.sds.yaml
metadata:
id: SDS-055
title: Notification Service
bounded_context: notification
framework: FastAPI
service:
name: Notification Service
ports:
- name: NotificationPort
type: interface
1
just calm-pipeline
1
cat architecture/generated/sea-forge.arch.json | jq '.nodes[] | select(.metadata."bounded-context" == "notification")'
1
just calm-generate
1
just calm-validate
Generate a visual representation:
1
2
just calm-docify
open architecture/docs/index.html
Or use the CALM CLI directly:
1
2
3
4
pnpm exec calm visualize \
--architecture architecture/generated/sea-forge.arch.json \
--format svg \
--output architecture.svg
Problem: No nodes generated
1
2
🏛️ Generating CALM architecture from SDS...
Generated 0 nodes, 0 relationships
Solution:
ls docs/specs/**/*.sds.yamlservice: sectionpython tools/sds_to_calm.py --input docs/specs --output architecture/generated/sea-forge.arch.json --verboseProblem: Missing required metadata
1
Error: Node 'my-service' missing required metadata field: 'sds-id'
Solution:
id to SDS metadata sectionjust calm-generateProblem: Invalid protocol
1
Error: Interface protocol 'custom' not in allowed list
Solution:
http, https, nats, grpc, websocketVALID_PROTOCOLS in tools/sds_to_calm.py if neededProblem: Broken relationship reference
1
Error: Relationship references unknown node: 'unknown-service'
Solution:
just calm-generateProblem: JSON schema validation fails
1
Error: Additional property 'custom_field' not allowed
Solution:
metadata sectionRun CALM generation after every SDS change:
1
2
# After editing any SDS file
just calm-pipeline
Update sea-forge.pattern.json when adding new metadata requirements:
1
2
3
4
5
6
7
8
{
"metadata": {
"bounded-context": "<required>",
"sds-id": "<required>",
"framework": "<required>",
"new-required-field": "<required>"
}
}
Commit generated CALM files to track architectural evolution:
1
2
git add architecture/generated/sea-forge.arch.json
git commit -m "arch: update CALM model for notification service"
Add architectural controls to validate compliance:
1
2
3
4
5
6
7
8
{
"controls": {
"data-encryption": {
"description": "All data must be encrypted at rest",
"applies-to": ["database"]
}
}
}
Make CALM validation mandatory:
1
2
3
4
# GitHub Actions
- name: CALM Validation
run: just calm-validate
continue-on-error: false # Fail build on validation error
Extend nodes with custom metadata in SDS:
1
2
3
4
5
6
metadata:
id: SDS-049
custom:
team: platform-engineering
cost-center: CC-1234
compliance-tier: tier-1
Appears in CALM as:
1
2
3
4
5
6
7
8
{
"metadata": {
"sds-id": "SDS-049",
"team": "platform-engineering",
"cost-center": "CC-1234",
"compliance-tier": "tier-1"
}
}
Generate separate architectures for different environments:
1
2
3
4
5
6
7
8
9
10
11
# Production
python tools/sds_to_calm.py \
--input docs/specs \
--output architecture/generated/production.arch.json \
--filter production
# Development
python tools/sds_to_calm.py \
--input docs/specs \
--output architecture/generated/development.arch.json \
--filter development
Load and query CALM architecture in Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
from pathlib import Path
# Load architecture
arch = json.loads(Path("architecture/generated/sea-forge.arch.json").read_text())
# Query nodes
for node in arch["nodes"]:
print(f"{node['name']}: {node['metadata']['bounded-context']}")
# Find relationships
for rel in arch.get("relationships", []):
source = rel["relationship-type"]["connects"]["source"]["node"]
dest = rel["relationship-type"]["connects"]["destination"]["node"]
print(f"{source} → {dest}")
For issues with CALM generation or validation:
just doctor to verify environmentdocs/specs/