finos / finos/architecture-as-code
[calm-spec] Decorator mechanism — close meta-schema and CLI gaps (core.json, sidecar wrapper, extension schemas, calm validate)
- Dominant language
- TypeScript
- Stars
- 399
- Forks
- 138
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 37
Description
## TL;DR for maintainers
CALM 1.2 **published the decorator concept** (`decorators.json` + [`core-concepts/decorators`](https://calm.finos.org/core-concepts/decorators) + worked deployment/Kubernetes examples in docs) but **did not finish the meta-schema and CLI plumbing**. Result:
1. Producers writing the documented patterns (AIGF, deployment, threat-model, …) cannot embed decorators in arch files without violating strict validation against the **same** meta-schema CALM publishes.
2. Sidecar form has no canonical wrapper — every tool guesses.
3. Worked examples in docs are not published as schemas — producers/consumers cannot share contracts.
4. `calm validate` is decorator-blind — nothing catches malformed decorators, dangling refs, or broken `target` paths in CI.
These gaps were exposed first by threat-model work for the [FINOS AI Reference Architecture Library](https://github.com/finos/ai-reference-architecture-library) (#2551), but the fix is **generic spec hygiene**. Closing them unblocks every decorator use case — present and future. Nothing here is FINOS-AI-ref-arch-group-specific.
This issue is **separate from**:
- #2550 — CalmStudio + MCP server flat-vs-nested relationship shape (different bug)
- #2551 — CalmStudio UI support for threat-model decorators (consumer-side feature)
---
## Why decorators matter — the bigger picture
The decorator concept is FINOS-CALM's answer to a class of problem that every architecture-as-code system eventually hits: **cross-cutting concerns that don't fit on one node**. Examples that already exist or are imminent:
| Decorator type | Real-world driver | Status |
|---|---|---|
| `aigf-governance` | FINOS AI Governance Framework — applied score + control coverage across all AI nodes in an architecture | Already shipped in CalmStudio (`apps/studio/src/lib/io/export.ts`) |
| `deployment` / Kubernetes / AWS ECS / Azure Container Apps | Track when/where/how components are deployed; bind to runtime observability dashboards | Documented worked example in `decorators.md`; schemas not published |
| `threat-model` | Map [FINOS AI Reference Architecture Library threat models](https://github.com/finos/ai-reference-architecture-library/blob/main/Library/reference_architecture/agentic_ai/multi_agent/tm/tm_ma_ref_arch_apr_2026.md) onto architecture (43 threats × 36 controls × 8 layers for the multi-agent case) | New (drives #2551) |
| `control-catalog` | Single source of truth for control IDs referenced by threat decorators, AIGF mitigations, FSI control mappings (SR 11-7, DORA, MAR) | New (companion to threat-model) |
| `regulatory-mapping` | Map nodes to clauses in DORA, EU AI Act, SR 11-7, MiCA, MAR, GDPR, ISO/IEC 27001, NIST AI RMF | Inevitable for FSI deployments |
| `mitre-attack` / `mitre-atlas` | Map MITRE ATT&CK or ATLAS techniques to nodes for adversary emulation and red-team coverage | Used by every mature security architecture program; CALM should be a first-class home |
| `sbom` | Attach Software Bill of Materials to specific nodes (model, MCP server, agent runtime) | SBOM is mandated by NIST SP 800-218 / EO 14028 — every CALM consumer in regulated industries needs this |
| `business-metadata` | Owner, cost-centre, criticality tier, RTO/RPO, on-call rotation | Standard ops requirement — currently bolted on with `metadata` field, which is unstructured |
| `slo` / `latency-budget` | Attach SLO targets and budgets to services or relationships | Common in modern platform architectures |
| `data-lineage` | Bind data-asset nodes to upstream/downstream lineage references in OpenLineage / DataHub / Marquez | Adjacent ecosystem already standardises this — CALM benefits by becoming a join point |
| `cost` | Attach cloud cost attribution / FinOps tags | Increasingly standard |
| `compliance-evidence` | Bind nodes to evidence URLs (test reports, audit logs, attestation artifacts) — complements existing `evidence.json` | Already gestating in the spec (`evidence.json`); decorators are the right home for the binding metadata |
**Every one of these benefits from the same four gap fixes.** None of them are achievable cleanly today.
---
## Why decorator pattern beats alternatives
The same information *could* be modelled in other ways. Each alternative has a documented downside the decorator pattern explicitly solves:
| Alternative | Problem |
|---|---|
| **Bigger `node.controls` field per node** | Forces duplication across N nodes for any cross-cutting concern (e.g. a threat that affects 5 nodes appears 5 times). No two-way navigation (control → nodes). Mixes enforcement (`controls`) with risk catalog (`threats`) — conceptually different. |
| **`metadata` free-form field** | No schema, no contract, no tooling support. Already in heavy unstructured use; that's the disease, not the cure. |
| **External non-CALM files (proprietary JSON/YAML overlays)** | No canonical link back to arch element `unique-id`s. Drift the moment arch evolves. Every vendor invents their own. |
| **Embedded as new top-level CALM fields** (e.g. `threats:`, `deployments:`) | Doesn't scale — every new cross-cutting concern needs a spec change to `core.json`. Decorators were the answer to this exact problem. |
| **Custom node-types** (e.g. node-type `threat-target`) | Conflates the catalog with the graph. Nodes represent system components, not policy/governance overlays. |
The decorator approach is **already the right answer** — it just needs spec finishing.
---
## Concrete evidence of the gaps
### Gap 1 — `core.json` rejects embedded `decorators[]`
[`calm/release/1.2/meta/core.json`](calm/release/1.2/meta/core.json) root:
```json
{
"type": "object",
"properties": {
"nodes": {...}, "relationships": {...}, "metadata": {...},
"controls": {...}, "flows": {...}, "adrs": {...},
"additionalProperties": {...}
},
"additionalProperties": false
}
```
`decorators` is **not in `properties`**, root `additionalProperties: false`. Any strict ajv validator rejects an architecture with `decorators[]` at root.
**Reproduction** — strict ajv 8.17 + 2020-12 dialect:
```js
import Ajv2020 from "ajv/dist/2020.js";
const ajv = new Ajv2020({ allErrors: true, strict: true });
ajv.addSchema(JSON.parse(readFileSync("core.json", "utf-8")));
const v = ajv.getSchema("https://calm.finos.org/release/1.2/meta/core.json");
v({ "nodes": [...], "relationships": [...], "decorators": [...] })
// → false, instancePath: "", message: "must NOT have additional properties (decorators)"
```
**Direct consequence:** CalmStudio's `aigf-governance-overlay` decorator, written via `apps/studio/src/lib/io/export.ts:103` and `packages/mcp-server/src/aigf-helpers.ts:82-93`, lands as an embedded `decorators[]` in the produced `.calm.json`. The output is **technically invalid against CALM's own published meta-schema**. It works in the wild only because no one currently runs strict ajv against `calm.json`. The moment `calm validate` (Gap 4) gains decorator awareness, or any third-party validator does, **every CalmStudio AIGF artefact in the field becomes invalid**.
This is not a hypothetical — it's already shipping broken.
### Gap 2 — Sidecar file wrapper format undefined
[`decorators.md`](docs/docs/core-concepts/decorators.md) shows a **single decorator** with `"$schema": "https://calm.finos.org/release/1.2/meta/kubernetes.decorator.schema.json"`. Nothing in the spec defines a file containing multiple decorators. Today, producers/consumers must choose between three uncoordinated conventions:
- `{ "decorators": [...] }` — CalmStudio's convention (mirrors arch's embedded form)
- `[ ... ]` — raw array at JSON root
- One decorator per file (forces N files for a 10-decorator threat model)
Without a published canonical, **interop is by accident**. The CalmStudio threat-model artefact written for this work uses the `{ decorators: [...] }` form; another tool authored to the docs' "one decorator per file" convention won't load it.
### Gap 3 — Extension schemas in docs, not in `release/1.2/meta/`
```
release/1.2/meta/
├── calm.json ✓
├── core.json ✓
├── decorators.json ✓ (base only — type free-form, data open)
├── control.json ✓
├── control-requirement.json ✓
├── interface.json ✓
├── flow.json ✓
├── evidence.json ✓
├── units.json ✓
├── timeline.json ✓
└── (no deployment.decorator.schema.json)
└── (no kubernetes.decorator.schema.json)
└── (no threat-model.decorator.schema.json)
└── (no control-catalog.decorator.schema.json)
```
The deployment + Kubernetes examples in [`decorators.md`](https://calm.finos.org/core-concepts/decorators#example-deployment-decorators) — including full JSON Schema bodies — are **example-only**. Anyone implementing a producer or validator for `type: "deployment"` has nowhere to ref. Anyone consuming threat-model decorators has nowhere to ref. The schemas exist as documentation but not as resolvable URIs.
Net effect: producers free-form `data`, consumers free-form parsing, divergence by default.
### Gap 4 — `calm validate` decorator-blind
```bash
$ grep -r decorator cli/src/
(no matches)
```
The official CLI tool [`@finos/calm-cli`](cli/) has zero decorator awareness. It will not:
- Validate sidecar decorator files against base or extension schemas
- Validate `decorators[]` arrays embedded in architecture files
- Resolve `applies-to` cross-references (dangling `unique-id`)
- Resolve `target[]` file references (missing file or wrong path)
- Resolve `data.threats[].affected-nodes` or analogous extension-specific node refs
In CI today, decorator errors are invisible until a downstream consumer (CalmStudio, calm-hub, third-party) fails at runtime. There is no shift-left.
---
## Production-readiness implications
The four gaps above mean CALM 1.2 cannot claim production-grade support for any of the use cases listed in the "Why decorators matter" table. Concretely:
| Stakeholder | Pain today |
|---|---|
| **FINOS AI Governance Framework consumers** (AIGF) | Producers (CalmStudio, eventually others) ship decorators that fail strict validation. Either the spec relaxes (this issue) or AIGF adopts sidecar form (migration cost; documentation rework). |
| **FINOS AI Reference Architecture Library** (multi-agent threat model is the first concrete published model, with more to follow for single-agent and other patterns) | Cannot ship architectures with embedded threats. Sidecar works but no `threat-model.decorator.schema.json` to validate against. Every consumer reinvents the data contract. |
| **FSI consumers** (banks running CALM internally with regulatory mappings — DORA, SR 11-7, MAR, MiCA) | No canonical `regulatory-mapping` decorator schema. Each bank invents its own. Cross-bank shared patterns become impossible. |
| **Tooling vendors** (Studio, IDE extensions, governance dashboards) | Cannot rely on `decorators[]` being valid in arch files — must defensively validate against the world they actually receive, not the spec. |
| **CI/CD integrators** | Cannot use `calm validate` as a gate — false negatives on decorator errors. |
| **Spec authors** | Worked examples in `decorators.md` are aspirational; readers who try to follow them produce non-conformant files. |
---
## Migration safety
Closing these gaps is **backwards-compatible**:
| Change | Existing valid file impact | Existing invalid file impact |
|---|---|---|
| Add `decorators` to `core.json` `properties` (Option A) | None — files without `decorators` still validate | Files with `decorators[]` (CalmStudio AIGF output today) **become valid** — improvement, no breakage |
| Mandate sidecar (Option B) | None | Existing CalmStudio AIGF outputs become invalid; migration script required |
| Publish canonical sidecar wrapper | None | None (new contract; producers opt-in over time) |
| Publish extension schemas | None — `decorators.json` base remains permissive | None — extension schemas are opt-in via `$schema` ref |
| Extend `calm validate` | None on valid files | False-negative errors stop being silent → CI surfaces real issues |
**Recommend Option A** — preserves existing producer behaviour, sidecar still works, zero breakage.
---
## Industry standards alignment
CALM benefits from being a credible bridge to adjacent industry standards. Decorators are the natural binding mechanism for:
| Standard | Decorator type that would bind it |
|---|---|
| MITRE ATT&CK / ATLAS | `mitre-attack`, `mitre-atlas` |
| NIST AI RMF | `nist-ai-rmf` |
| ISO/IEC 27001, 42001 | `iso-control-mapping` |
| OWASP LLM Top 10, OWASP ML Top 10 | `owasp-llm-mapping` |
| FINOS AI Governance Framework | `aigf-governance` (already exists) |
| EU AI Act risk-tier annotation | `eu-ai-act-tier` |
| DORA ICT risk classification | `dora-ict-classification` |
| SBOM / SLSA provenance | `sbom`, `slsa-provenance` |
| OpenLineage | `data-lineage` |
| OpenTelemetry resource attributes | `otel-resource` |
None of these need to be in CALM 1.2 core. They just need the decorator mechanism to **work**. Gap 1-4 close that.
---
## Acceptance criteria
- [ ] Decision recorded in this issue: Option A (allow embedded) vs Option B (sidecar-only)
- [ ] If A: `core.json` adds `decorators` to root `properties` (`type: array`, items `$ref decorators.json#/defs/decorator`); existing CalmStudio AIGF output becomes spec-compliant
- [ ] If B: doc updated to explicitly forbid embedded; migration guide for CalmStudio AIGF
- [ ] Canonical sidecar wrapper format published (single decorator per file vs `{ decorators: [...] }` vs array root)
- [ ] At minimum these four extension schemas published under `release/1.2/meta/`:
- [ ] `deployment.decorator.schema.json` (matches existing doc example)
- [ ] `kubernetes.decorator.schema.json` (matches existing doc example)
- [ ] `threat-model.decorator.schema.json` (new — driven by #2551)
- [ ] `control-catalog.decorator.schema.json` (new — companion to threat-model)
- [ ] `calm validate` validates decorators (embedded + sidecar) including cross-ref resolution and `target[]` file existence
- [ ] New `calm validate-decorators [--against ]` subcommand
- [ ] `decorators.md` worked example updated for canonical sidecar form (and embedded form if Option A chosen)
- [ ] At least one **non-AIGF** worked example published in docs (threat-model recommended given it's the immediate driver and exercises a realistic, multi-layer `applies-to`)
---
## References
- Decorators concept page: https://calm.finos.org/core-concepts/decorators
- Base decorator schema (already published): https://calm.finos.org/release/1.2/meta/decorators.json
- Architecture root schema (the one that blocks embedded today): https://calm.finos.org/release/1.2/meta/core.json
- FINOS AI Governance Framework: https://air-governance-framework.finos.org/
- FINOS AI Reference Architecture Library: https://github.com/finos/ai-reference-architecture-library
- Multi-Agent Reference Architecture (driving threat-model use case): https://github.com/finos/ai-reference-architecture-library/blob/main/Library/reference_architecture/agentic_ai/multi_agent/ra/ma_ref_arch_jan_2026.md
- Multi-Agent Threat Model: https://github.com/finos/ai-reference-architecture-library/blob/main/Library/reference_architecture/agentic_ai/multi_agent/tm/tm_ma_ref_arch_apr_2026.md
- Related (downstream consumers): #2550 (Studio relationship shape bug), #2551 (Studio threat-model decorator support)
Contributor guide
Assessment
This issue has not been assessed yet.