CALM Architecture Guide

Overview

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.

What is CALM?

CALM is a FINOS standard for machine-readable architecture definitions. In SEA-Forge™, CALM serves as:

Quick Start

Prerequisites

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 CALM Architecture

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 Architecture

Validate the generated architecture against the SEA-Forge™ pattern:

1
just calm-validate

This checks:

Full Pipeline

Run generation and validation together:

1
just calm-pipeline

Directory Structure

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

Generation Process

How SDS Maps to CALM

The sds_to_calm.py tool transforms SDS specifications into CALM architecture:

1
SDS YAML Files → sds_to_calm.py → CALM Architecture JSON

Node Generation

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"
    }
  ]
}

Relationship Generation

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"
}

Protocol Mapping

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

Validation Process

Pattern-Based Validation

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>"
      }
    }
  ]
}

Validation Rules

The CALM CLI validates:

  1. Schema Compliance: JSON structure matches CALM schema
  2. Required Metadata: All nodes have required fields
  3. Interface Definitions: Valid protocols and ports
  4. Relationship Integrity: Valid source/destination references
  5. Pattern Conformance: Architecture matches defined patterns

Reading Validation Output

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

CLI Commands

Generate Architecture

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

Validate Architecture

1
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

Generate Documentation

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:

Check Status

1
just calm-status

Displays:

Integration with CI/CD

Pre-commit Hook

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
}

GitHub Actions

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

Spec Pipeline Integration

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>

Common Workflows

Adding a New Service

  1. Create SDS specification:
    1
    2
    
    # Create specs for new bounded context
    python tools/sea_new_context.py notification
    
  2. Edit SDS with service details:
    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
    
  3. Generate and validate:
    1
    
    just calm-pipeline
    
  4. Review generated node:
    1
    
    cat architecture/generated/sea-forge.arch.json | jq '.nodes[] | select(.metadata."bounded-context" == "notification")'
    

Updating Service Dependencies

  1. Update SDS dependencies section: ```yaml dependencies:
  2. Regenerate architecture:
    1
    
    just calm-generate
    
  3. Validate relationships:
    1
    
    just calm-validate
    

Viewing Architecture Graph

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

Troubleshooting

Generation Issues

Problem: No nodes generated

1
2
🏛️ Generating CALM architecture from SDS...
Generated 0 nodes, 0 relationships

Solution:

Validation Failures

Problem: Missing required metadata

1
Error: Node 'my-service' missing required metadata field: 'sds-id'

Solution:

Problem: Invalid protocol

1
Error: Interface protocol 'custom' not in allowed list

Solution:

Problem: Broken relationship reference

1
Error: Relationship references unknown node: 'unknown-service'

Solution:

Schema Validation Errors

Problem: JSON schema validation fails

1
Error: Additional property 'custom_field' not allowed

Solution:

Best Practices

1. Generate Frequently

Run CALM generation after every SDS change:

1
2
# After editing any SDS file
just calm-pipeline

2. Keep Patterns Updated

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>"
  }
}

3. Version Control

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"

4. Document Controls

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"]
    }
  }
}

5. CI/CD Integration

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

Advanced Usage

Custom Metadata

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"
  }
}

Multi-Architecture Support

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

Programmatic Access

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}")

Specifications

Tools

Workflows

Support

For issues with CALM generation or validation:

  1. Check this guide’s troubleshooting section
  2. Review FINOS CALM specification: https://github.com/finos/architecture-as-code
  3. Run just doctor to verify environment
  4. Check SDS format against examples in docs/specs/