Decorations
Decorations provide a way to attach additional metadata to IR nodes without modifying the core IR structure. They are stored separately from the IR in a layered, composable system.
Design Principles
- Separation of Concerns: Core IR remains clean; decorations are additive metadata
- Layered: Multiple decoration sources with defined precedence
- Flattened: FQName keys for direct lookup, not mirrored directory structure
- Type-Safe: Decoration values are validated against Morphir-defined schemas
- Deep Merge: Same node/decoration from multiple layers are deep-merged
VFS Structure
.morphir-dist/
├── format.json
├── pkg/ # Core IR
├── deps/ # Dependencies
└── deco/ # Decorations (flattened, layered)
├── format.json # Decoration system metadata
│
├── schemas/ # Local schema cache
│ ├── documentation.ir.json
│ ├── deprecated.ir.json
│ └── tags.ir.json
│
└── layers/ # Decoration layers
├── core/ # Built-in (from source)
│ ├── manifest.json
│ ├── documentation.json
│ └── deprecated.json
│
├── tooling/ # Generated by tools
│ ├── manifest.json
│ ├── type-info.json
│ └── complexity.json
│
└── user/ # User-defined
├── manifest.json
├── documentation.json
├── deprecated.json
└── tags.json
File Formats
Decoration Format File (deco/format.json)
Top-level metadata for the decoration system.
{
"formatVersion": "4.0.0",
"schemaRegistry": {
"documentation": {
"displayName": "Documentation",
"description": "Structured documentation for IR nodes",
"localPath": "schemas/documentation.ir.json",
"remoteRef": "morphir://registry/decorations/documentation@1.0.0",
"entryPoint": "morphir/decorations:documentation#documentation",
"cachedAt": "2026-01-16T12:00:00Z"
},
"deprecated": {
"displayName": "Deprecated",
"description": "Mark nodes as deprecated with migration info",
"localPath": "schemas/deprecated.ir.json",
"remoteRef": "morphir://registry/decorations/deprecated@1.0.0",
"entryPoint": "morphir/decorations:deprecated#deprecation"
},
"tags": {
"displayName": "Tags",
"description": "Arbitrary string tags for categorization",
"localPath": "schemas/tags.ir.json",
"entryPoint": "morphir/decorations:tags#tag-set"
}
},
"layers": ["core", "tooling", "user"],
"layerPriority": {
"core": 0,
"tooling": 50,
"user": 100
}
}
Layer Manifest (layers/{layer}/manifest.json)
Metadata for a specific decoration layer.
{
"formatVersion": "4.0.0",
"layer": "user",
"displayName": "User Annotations",
"description": "User-defined decorations and overrides",
"priority": 100,
"decorationTypes": ["documentation", "deprecated", "tags"],
"createdAt": "2026-01-16T12:00:00Z",
"updatedAt": "2026-01-16T14:30:00Z"
}
Decoration Values (layers/{layer}/{decoration-type}.json)
Flattened decoration values keyed by FQName.
{
"formatVersion": "4.0.0",
"decorationType": "documentation",
"layer": "user",
"values": {
"my-org/project:domain#user": {
"summary": "Represents a user in the system",
"details": [
"Contains identity and contact information.",
"Immutable after creation."
],
"examples": [
{
"title": "Basic user",
"code": "{ email = \"alice@example.com\", name = \"Alice\" }"
}
]
},
"my-org/project:domain#create-user": {
"summary": "Creates a new user account",
"params": {
"email": "A valid email address",
"name": "The user's display name"
},
"returns": "The created User record",
"throws": ["InvalidEmail", "DuplicateUser"]
},
"my-org/project:domain": {
"summary": "Core domain types and operations",
"moduleDoc": true
}
}
}
Deprecated Decoration Example
{
"formatVersion": "4.0.0",
"decorationType": "deprecated",
"layer": "user",
"values": {
"my-org/project:legacy#old-calculate": {
"since": "2.0.0",
"replacement": "my-org/project:domain#calculate",
"reason": "Performance improvements and bug fixes",
"removalVersion": "3.0.0",
"migrationGuide": "https://docs.example.com/migrate-calculate"
},
"my-org/project:legacy#legacy-user": {
"since": "1.5.0",
"replacement": "my-org/project:domain#user",
"reason": "Renamed for consistency"
}
}
}
Tags Decoration Example
{
"formatVersion": "4.0.0",
"decorationType": "tags",
"layer": "user",
"values": {
"my-org/project:domain#user": ["pii", "core-domain", "audited"],
"my-org/project:domain#calculate-price": ["business-rule", "pricing"],
"my-org/project:reporting#generate-report": ["slow", "async", "batch"]
}
}
Gleam Type Definitions
// === decorations.gleam ===
/// Schema reference - local cache with optional remote source
pub type SchemaRef {
SchemaRef(
display_name: String,
description: Option(String),
local_path: String, // Path within deco/schemas/
remote_ref: Option(String), // Registry reference
entry_point: FQName, // Type defining decoration shape
cached_at: Option(String), // ISO 8601 timestamp
)
}
/// Decoration format metadata
pub type DecorationFormat {
DecorationFormat(
format_version: String,
schema_registry: Dict(String, SchemaRef),
layers: List(String),
layer_priority: Dict(String, Int),
)
}
/// Layer manifest
pub type LayerManifest {
LayerManifest(
format_version: String,
layer: String,
display_name: String,
description: Option(String),
priority: Int,
decoration_types: List(String),
created_at: String,
updated_at: String,
)
}
/// Decoration values file
pub type DecorationValuesFile {
DecorationValuesFile(
format_version: String,
decoration_type: String,
layer: String,
values: Dict(FQName, Dynamic), // Values validated against schema
)
}
/// Node path for decoration targeting
pub type DecorationTarget {
/// Type definition: "my-org/project:domain#user"
TypeTarget(fqname: FQName)
/// Value definition: "my-org/project:domain#create-user"
ValueTarget(fqname: FQName)
/// Module: "my-org/project:domain"
ModuleTarget(package: PackagePath, module: ModulePath)
/// Package: "my-org/project"
PackageTarget(package: PackagePath)
}
Merge Semantics
When the same node has the same decoration type across multiple layers, values are deep-merged with higher priority layers winning on conflicts.
Merge Rules
| Value Type | Merge Behavior |
|---|---|
| Primitive (string, number, bool) | Higher priority wins |
| Object | Deep merge recursively |
| Array | Concatenate (higher priority first), dedupe optionally |
| Null | Higher priority wins (can "unset" lower layer) |
Merge Example
Layer: core (priority 0)
{
"my-org/project:domain#user": {
"summary": "A user",
"details": ["From source"]
}
}
Layer: user (priority 100)
{
"my-org/project:domain#user": {
"summary": "Represents a user in the system",
"examples": [{ "title": "Basic", "code": "..." }]
}
}
Merged Result:
{
"my-org/project:domain#user": {
"summary": "Represents a user in the system", // user wins (higher priority)
"details": ["From source"], // from core (not in user)
"examples": [{ "title": "Basic", "code": "..." }] // from user (not in core)
}
}
Merge Algorithm
/// Deep merge decoration values with priority
pub fn merge_decoration_values(
layers: List(#(Int, Dict(FQName, Dynamic))), // (priority, values)
) -> Dict(FQName, Dynamic) {
layers
|> list.sort(fn(a, b) { int.compare(a.0, b.0) }) // Sort by priority (ascending)
|> list.fold(dict.new(), fn(acc, layer) {
let #(_, values) = layer
dict.fold(values, acc, fn(acc, key, value) {
case dict.get(acc, key) {
Ok(existing) -> dict.insert(acc, key, deep_merge(existing, value))
Error(_) -> dict.insert(acc, key, value)
}
})
})
}
/// Deep merge two dynamic values
fn deep_merge(base: Dynamic, override: Dynamic) -> Dynamic {
// If both are objects, merge recursively
// If both are arrays, concatenate
// Otherwise, override wins
...
}
Layer Semantics
Standard Layers
| Layer | Priority | Source | Description |
|---|---|---|---|
core | 0 | Compilation | Extracted from source (doc comments, annotations) |
tooling | 50 | Analysis tools | Generated by linters, analyzers, type inference |
user | 100 | Manual | User-authored decorations |
Custom Layers
Projects can define additional layers with custom priorities:
{
"layers": ["core", "team-standards", "tooling", "user", "overrides"],
"layerPriority": {
"core": 0,
"team-standards": 25,
"tooling": 50,
"user": 100,
"overrides": 200
}
}
Schema Management
Local Cache
Schemas are cached locally in deco/schemas/ for offline access and performance:
deco/schemas/
├── documentation.ir.json # Full Morphir IR for documentation type
├── deprecated.ir.json # Full Morphir IR for deprecated type
└── tags.ir.json # Full Morphir IR for tags type
Remote Registry Reference
Schemas can reference a remote registry for updates:
{
"localPath": "schemas/documentation.ir.json",
"remoteRef": "morphir://registry/decorations/documentation@1.0.0"
}
Schema Sync
# Sync schemas from registry
morphir decoration schema sync
# Update specific schema
morphir decoration schema update documentation
# Validate local schemas against registry
morphir decoration schema verify
JSON-RPC Methods
deco/read
Read merged decorations for a node.
{
"method": "deco/read",
"params": {
"target": "my-org/project:domain#user",
"decorationType": "documentation" // optional, null = all types
}
}
Response:
{
"result": {
"target": "my-org/project:domain#user",
"decorations": {
"documentation": {
"summary": "Represents a user in the system",
"details": ["..."]
},
"tags": ["pii", "core-domain"]
},
"sources": {
"documentation": ["core", "user"],
"tags": ["user"]
}
}
}
deco/write
Write a decoration value to a specific layer.
{
"method": "deco/write",
"params": {
"target": "my-org/project:domain#user",
"decorationType": "documentation",
"layer": "user",
"value": {
"summary": "Updated documentation"
}
}
}
deco/list
List all decorated nodes.
{
"method": "deco/list",
"params": {
"decorationType": "deprecated", // optional filter
"layer": "user" // optional filter
}
}
deco/layers
List available layers with metadata.
{
"method": "deco/layers",
"params": {}
}
Common Decoration Types
Documentation
type alias Documentation =
{ summary : String
, details : List String
, examples : List Example
, params : Dict String String
, returns : Maybe String
, throws : List String
, see : List String
, since : Maybe String
}
type alias Example =
{ title : String
, code : String
, language : Maybe String
}
Deprecated
type alias Deprecation =
{ since : String
, replacement : Maybe FQName
, reason : Maybe String
, removalVersion : Maybe String
, migrationGuide : Maybe String
}
Tags
type alias TagSet = List String
Alias (Code Generation)
type alias Alias =
{ name : String
, target : Maybe String -- "typescript", "scala", etc.
}
Native Hint
type alias NativeHint =
{ platform : String
, implementation : String
, importPath : Maybe String
}
Validation
Decoration values are validated against their schemas:
# Validate all decorations
morphir decoration validate
# Validate specific layer
morphir decoration validate --layer user
# Validate specific type
morphir decoration validate --type documentation
Validation checks:
- Value conforms to decoration schema
- Target FQName exists in IR
- No orphaned decorations (target was deleted)
- Schema versions are compatible
Migration from Legacy Format
The legacy format stored all decorations in a single file:
// Old: my-decoration-values.json
{
"My.Package:Foo:bar": { ... }
}
Migration to VFS format:
morphir decoration migrate --from my-decoration-values.json --layer user --type documentation
This creates:
deco/layers/user/documentation.json