{
"$id": "https://cmn.dev/schemas/v1/spore.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "CMN Spore",
"type": "object",
"required": ["$schema", "capsule", "capsule_signature"],
"properties": {
"$schema": {
"const": "https://cmn.dev/schemas/v1/spore.json"
},
"capsule": {
"type": "object",
"required": ["uri", "core", "core_signature", "dist"],
"properties": {
"uri": { "$ref": "#/$defs/cmn_uri" },
"core": { "$ref": "#/$defs/spore_core_released" },
"core_signature": { "$ref": "#/$defs/signature" },
"dist": { "$ref": "#/$defs/dist_array" }
},
"additionalProperties": true
},
"capsule_signature": { "$ref": "#/$defs/signature" }
},
"additionalProperties": true,
"$defs": {
"cmn_uri": {
"type": "string",
"pattern": "^cmn://(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?/[a-z0-9]+\\.[1-9A-HJ-NP-Za-km-z]+$",
"description": "CMN URI format: cmn://{domain}/{algorithm}.{base58}"
},
"hash": {
"type": "string",
"pattern": "^[a-z0-9]+\\.[1-9A-HJ-NP-Za-km-z]+$",
"description": "Hash format: {algorithm}.{base58}, e.g., b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2"
},
"signature": {
"type": "string",
"pattern": "^[a-z0-9]+\\.[1-9A-HJ-NP-Za-km-z]+$",
"description": "Signature format: {algorithm}.{base58}, e.g., ed25519.3yMR7vZQ9hL2xKJd..."
},
"spdx_expression_simple": {
"type": "string",
"minLength": 1,
"pattern": "^[A-Za-z0-9-.+():]+(?:\\s+(?:AND|OR|WITH)\\s+[A-Za-z0-9-.+():]+)*$",
"description": "SPDX license expression (basic pattern validation). Full SPDX parser validation is recommended."
},
"spore_core": {
"type": "object",
"required": ["name", "synopsis", "intent", "license", "tree"],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Opaque publisher-defined identifier. Consumers MUST treat raw id as opaque and MUST NOT use it directly as a filesystem path, shell token, or URL path segment. If a local-safe derivation is unsafe or collides, implementations MUST fall back to the spore hash."
},
"version": {
"type": "string",
"description": "Human-readable version string (e.g., 1.0.0). Optional."
},
"name": { "type": "string", "minLength": 1, "description": "Human-readable display name" },
"domain": {
"type": "string",
"pattern": "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
},
"key": {
"type": "string",
"pattern": "^ed25519\\.[1-9A-HJ-NP-Za-km-z]+$",
"description": "Optional pre-filled author public key. If absent, the publishing implementation MUST populate it from the current signing domain identity during release. If present, it MUST match that identity."
},
"synopsis": { "type": "string", "description": "One-line summary — agent reads this alone and understands what the spore does" },
"intent": { "type": "array", "items": { "type": "string" }, "description": "Multi-paragraph description of this spore's functionality and purpose — what it does, how it works, why it exists. Each array item is a paragraph. Permanent, not cleared on release." },
"license": { "$ref": "#/$defs/spdx_expression_simple" },
"mutations": { "type": "array", "items": { "type": "string" }, "default": [], "description": "What changed relative to the spawned_from parent — describes the mutations applied to derive this spore from its ancestor." },
"size_bytes": false,
"updated_at_epoch_ms": false,
"bonds": {
"type": "array",
"items": { "$ref": "#/$defs/bond" }
},
"tree": { "$ref": "#/$defs/tree" }
},
"additionalProperties": true
},
"spore_core_released": {
"description": "Published spore core — the full spore_core as it appears in a released spore.json. Adds domain, key, size_bytes, updated_at_epoch_ms as required fields with proper type definitions (these are forbidden in the draft spore.core.json).",
"type": "object",
"required": ["name", "domain", "key", "synopsis", "intent", "license", "size_bytes", "updated_at_epoch_ms", "tree"],
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Opaque publisher-defined identifier. Consumers MUST treat raw id as opaque and MUST NOT use it directly as a filesystem path, shell token, or URL path segment. If a local-safe derivation is unsafe or collides, implementations MUST fall back to the spore hash."
},
"version": {
"type": "string",
"description": "Human-readable version string (e.g., 1.0.0). Optional."
},
"name": { "type": "string", "minLength": 1, "description": "Human-readable display name" },
"domain": {
"type": "string",
"pattern": "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
},
"key": {
"type": "string",
"pattern": "^ed25519\\.[1-9A-HJ-NP-Za-km-z]+$",
"description": "Author's Ed25519 public key. Publishing implementations populate this from the current signing domain identity during release."
},
"synopsis": { "type": "string", "description": "One-line summary — agent reads this alone and understands what the spore does" },
"intent": { "type": "array", "items": { "type": "string" }, "description": "Multi-paragraph description of this spore's functionality and purpose — what it does, how it works, why it exists. Each array item is a paragraph. Permanent, not cleared on release." },
"license": { "$ref": "#/$defs/spdx_expression_simple" },
"mutations": { "type": "array", "items": { "type": "string" }, "default": [], "description": "What changed relative to the spawned_from parent — describes the mutations applied to derive this spore from its ancestor." },
"size_bytes": { "type": "integer", "minimum": 0, "description": "Total uncompressed source size in bytes — sum of all blob content sizes from tree hash computation. Populated by release tooling, not present in spore.core.json draft." },
"updated_at_epoch_ms": { "type": "integer", "minimum": 0, "description": "Content update timestamp (milliseconds since Unix epoch). Recommended source: latest Git commit time for the source tree, with max file mtime as fallback." },
"bonds": {
"type": "array",
"items": { "$ref": "#/$defs/bond" }
},
"tree": { "$ref": "#/$defs/tree" }
},
"additionalProperties": true
},
"bond": {
"type": "object",
"required": ["uri", "relation"],
"properties": {
"uri": { "$ref": "#/$defs/cmn_uri" },
"relation": {
"type": "string",
"minLength": 1,
"description": "Relationship type. Predefined: spawned_from, absorbed_from, depends_on, follows, extends. Custom types allowed."
},
"id": {
"type": "string",
"minLength": 1,
"description": "Optional opaque alias for this bond. Consumers MUST NOT use raw id directly as a filesystem path, shell token, or URL path segment. If a local-safe derivation is unsafe or collides, implementations MUST fall back to the bond target hash."
},
"reason": {
"type": "string",
"minLength": 1,
"description": "Optional explanation of why this bond exists and what role it plays. Typically omitted for spawned_from (use mutations field instead)."
},
"with": {
"type": "object",
"description": "Bond-specific parameters whose schema is defined by the bonded spore's convention. Opaque to the CMN protocol."
}
},
"additionalProperties": true
},
"tree": {
"type": "object",
"required": ["algorithm"],
"properties": {
"algorithm": { "type": "string", "description": "Tree hash algorithm (e.g., blob_tree_blake3_nfc)" },
"exclude_names": { "type": "array", "items": { "type": "string" }, "description": "Directories or files to skip during tree hashing. SHOULD include .git and .cmn at minimum." },
"follow_rules": { "type": "array", "items": { "type": "string" }, "description": "Paths to ignore-style rule files (e.g., .gitignore) honored during tree hashing." }
},
"additionalProperties": true
},
"dist_array": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/dist_entry" }
},
"dist_type": {
"type": "string",
"minLength": 1,
"pattern": "^[a-z0-9][a-z0-9._-]*$",
"description": "Distribution protocol identifier"
},
"dist_archive": {
"type": "object",
"required": ["type"],
"properties": {
"type": { "const": "archive" },
"filename": { "type": "string", "minLength": 1, "description": "Deprecated. Archive URL is now resolved via endpoints archive url template + spore hash. Ignored by clients." }
},
"additionalProperties": true
},
"dist_git": {
"type": "object",
"required": ["type", "url"],
"properties": {
"type": { "const": "git" },
"url": { "type": "string", "minLength": 1, "description": "Git repository URL" },
"ref": { "type": "string", "minLength": 1, "description": "Optional git ref (tag/branch/commit)" }
},
"additionalProperties": true
},
"dist_ipfs": {
"type": "object",
"required": ["type", "cid"],
"properties": {
"type": { "const": "ipfs" },
"cid": { "type": "string", "minLength": 1, "description": "IPFS CID or ipfs:// URI" }
},
"additionalProperties": true
},
"dist_extension": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"allOf": [
{ "$ref": "#/$defs/dist_type" },
{ "not": { "enum": ["archive", "git", "ipfs"] } }
],
"description": "Extension distribution protocol identifier (e.g., s3, bittorrent, rsync)"
}
},
"additionalProperties": true
},
"dist_entry": {
"oneOf": [
{ "$ref": "#/$defs/dist_archive" },
{ "$ref": "#/$defs/dist_git" },
{ "$ref": "#/$defs/dist_ipfs" },
{ "$ref": "#/$defs/dist_extension" }
]
}
}
}