{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:orbiplex:schema:capability-passport:v1",
  "title": "CapabilityPassport v1",
  "description": "Signed capability or consent artifact naming a capability profile for a target Node. Direct signatures are verified against the public key embedded in `issuer/participant_id`. Delegated signatures are verified against `issuer_delegation.proxy_key` after the inline delegation proof verifies against the same issuer. The signed payload is the deterministic canonical JSON of the artifact with `signature` and `issuer_delegation` omitted. Trust derives from profile-specific local policy, not from the passport alone: infrastructure profiles such as `network-ledger` may require a sovereign operator issuer, while consent profiles such as `node-primary-operator` may require a matching Node acceptance artifact.",
  "type": "object",
  "additionalProperties": true,
  "x-dia-workflow": "project",
  "x-dia-status": "draft",
  "x-dia-basis": [
    "doc/project/40-proposals/024-capability-passports-and-network-ledger-delegation.md",
    "doc/project/40-proposals/025-seed-directory-as-capability-catalog.md",
    "doc/project/20-memos/participant-assurance-levels.md",
    "doc/project/40-proposals/034-node-operator-binding-and-derived-node-assurance.md",
    "doc/project/40-proposals/038-key-roles-and-key-use-taxonomy.md",
    "doc/project/60-solutions/006-capability-binding/006-capability-binding.md",
    "doc/project/60-solutions/005-sealer/005-sealer.md"
  ],
  "required": [
    "schema",
    "passport_id",
    "node_id",
    "capability_id",
    "scope",
    "issued_at",
    "issuer/participant_id",
    "issuer/node_id",
    "revocation_ref",
    "signature"
  ],
  "properties": {
    "schema": {
      "const": "capability-passport.v1",
      "description": "Schema discriminator. MUST be exactly `capability-passport.v1`."
    },
    "passport_id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^passport:capability:",
      "description": "Stable unique identifier for this passport. MUST use the `passport:capability:` prefix."
    },
    "node_id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^node:did:key:z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Target Node receiving the delegated capability. MUST match the `node:did:key:z...` canonical format."
    },
    "capability_id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^[~]?[a-z0-9][a-z0-9_/-]*(?:@(participant|node|org):did:key:z[1-9A-HJ-NP-Za-km-z]+)?$",
      "description": "Capability identifier. Formal global profiles use bare kebab-case identifiers such as `network-ledger`, `seed-directory`, `escrow`, or `node-primary-operator`. Sovereign/private profiles may add an identity anchor, e.g. `audio-transcription@participant:did:key:z...`, with an optional leading `~` for informal profiles. See Proposal 024 and Proposal 025 for naming and advertisement projection."
    },
    "capability_profile": {
      "$ref": "#/$defs/capabilityProfile",
      "description": "Optional human and machine description of the capability profile. This is signed with the passport when present, but it is metadata: trust still derives from passport verification and local policy."
    },
    "scope": {
      "type": "object",
      "additionalProperties": true,
      "description": "Capability-specific parameters constraining the delegation. MAY be empty (`{}`). Receivers MUST tolerate unknown keys. Non-key-use passports (e.g. `network-ledger`, `node-primary-operator`) MAY continue to use free-form scope fields such as `federation/id`. Key-use passports (Sealer, Memarium space, community key) SHOULD populate the typed `allowed_callers[]` and `profiles[]` fields defined below; this schema validates their shape when present, but does not require them at the envelope level. Key-use verifiers (`capability-binding`) semantically require both a matching `allowed_callers[]` entry and at least one authorizing recognized profile in `profiles[]` before emitting `AuthorizationDecision::Authorized`. See proposal 038 §Tightened `capability-passport.v1` Scope for Key-Use Authorization.",
      "properties": {
        "allowed_callers": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/allowedCaller" },
          "description": "Typed caller constraints used by key-use passports. A key-use verifier MUST find at least one entry whose `subject_key` overlaps the resolved `CallerBinding.subject_keys` (and whose `label`/`kind` match when present) before authorizing. Profiles MUST NOT override caller binding."
        },
        "profiles": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/scopeProfileV1" },
          "description": "Typed key-use profile objects. Composition rule is OR across recognized profiles: at least one recognized profile, on its own terms, MUST authorize the requested `(grant_type, target, …)`. Fields MUST NOT combine across different profiles. Unknown profile discriminators MAY coexist but MUST NOT grant access."
        }
      }
    },
    "issued_at": {
      "type": "string",
      "format": "date-time",
      "description": "RFC 3339 timestamp at which this passport was issued."
    },
    "expires_at": {
      "type": ["string", "null"],
      "format": "date-time",
      "description": "RFC 3339 timestamp after which this passport MUST be treated as expired. `null` means no explicit expiry; receivers SHOULD apply a locally configured maximum TTL."
    },
    "issuer/participant_id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^participant:did:key:z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Canonical `participant:did:key:z...` identifier of the issuing participant. The required authority level is determined by the capability profile. Infrastructure profiles such as `network-ledger` and `seed-directory` usually require a software-pinned sovereign operator; the `node-primary-operator` consent profile requires this participant to match the operator named by the binding policy and to be accepted by the target Node."
    },
    "issuer/node_id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^node:did:key:z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Node on which the issuing participant acted when signing this passport."
    },
    "revocation_ref": {
      "type": ["string", "null"],
      "minLength": 1,
      "description": "Optional reference to an out-of-band revocation endpoint or log for this passport. `null` if no external revocation surface is provided; consumers SHOULD still poll the Seed Directory revocation log."
    },
    "issuer_delegation": {
      "$ref": "#/$defs/delegationProof",
      "description": "Optional compact inline proof authorising a proxy key to sign this artifact for `issuer/participant_id`. Verifiers MUST verify this proof before checking the artifact signature with `proxy_key`; it is excluded from the artifact signature payload."
    },
    "signature": {
      "$ref": "#/$defs/ed25519Signature"
    },
    "policy_annotations": {
      "type": "object",
      "additionalProperties": true,
      "description": "Optional informational annotations. MUST NOT alter core passport semantics."
    }
  },
  "$defs": {
    "capabilityProfile": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "compatible_with": {
          "type": "string",
          "minLength": 1,
          "description": "Optional formal capability id whose public contract this profile claims to implement. Sovereign capability ids without a leading `~` SHOULD set this field; informal `~...@...` capability ids SHOULD omit it unless they intentionally claim a compatibility subset."
        },
        "display/name": {
          "type": "string",
          "minLength": 1,
          "description": "Short human-readable capability name intended for UI display. It MUST NOT replace `capability_id` as protocol identity."
        },
        "description": {
          "type": "string",
          "minLength": 1,
          "description": "Human-readable capability explanation."
        },
        "lang": {
          "type": "string",
          "pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
          "description": "BCP 47-style language tag for human-readable profile fields."
        },
        "doc/ref": {
          "type": "string",
          "pattern": "^orbiplex:blob:sha256:[A-Za-z0-9_-]+$",
          "description": "Optional content-addressed Orbiplex reference to a human-readable capability document."
        },
        "doc/url": {
          "type": "string",
          "format": "uri",
          "description": "Optional convenience URL for human-readable documentation. This is not a protocol dependency."
        },
        "schema/id": {
          "type": "string",
          "minLength": 1,
          "description": "Stable logical identifier of the machine-readable capability schema."
        },
        "schema/ref": {
          "type": "string",
          "pattern": "^orbiplex:blob:sha256:[A-Za-z0-9_-]+$",
          "description": "Content-addressed Orbiplex reference to a `capability-schema.v1` artifact retrievable over the peer protocol, Seed Directory, cache, or archivist surface."
        },
        "schema/media-type": {
          "type": "string",
          "minLength": 1,
          "description": "Media type of the capability schema content, for example `application/schema+json`."
        }
      }
    },
    "delegationProof": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "delegation_id",
        "proxy_key",
        "principal_key",
        "grants",
        "expires_at",
        "principal_signature"
      ],
      "description": "Compact bearer credential copied out of a `key-delegation.v1` registration artifact and embedded beside a proxy-key signature. Its own principal signature covers only the compact proof payload.",
      "properties": {
        "delegation_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^delegation:key:",
          "description": "Stable identifier of the key delegation whose compact proof is embedded here."
        },
        "proxy_key": {
          "type": "string",
          "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]+$",
          "description": "Delegated Ed25519 public key in DID Key form. This key signs the surrounding artifact."
        },
        "principal_key": {
          "type": "string",
          "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]+$",
          "description": "Original participant public key in DID Key form. Verifiers derive `participant:did:key:...` from it and require equality with `issuer/participant_id`."
        },
        "grants": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "description": "Open grant map. For capability passport issuance the relevant entry is `signing/capability: [<capability_id> | \"*\", ...]`."
        },
        "expires_at": {
          "type": "string",
          "format": "date-time",
          "description": "RFC 3339 timestamp after which this proof MUST be rejected."
        },
        "principal_signature": {
          "type": "string",
          "minLength": 1,
          "description": "Base64url Ed25519 signature by `principal_key` over the canonical proof payload (`delegation_id`, `proxy_key`, `principal_key`, `grants`, `expires_at`)."
        }
      }
    },
    "ed25519Signature": {
      "type": "object",
      "additionalProperties": true,
      "required": ["alg", "value"],
      "description": "Ed25519 signature over the deterministic canonical JSON of the passport with the `signature` and `issuer_delegation` fields omitted entirely from the signed payload. Object keys are sorted lexicographically; no insignificant whitespace; arrays left in original order.",
      "properties": {
        "alg": {
          "const": "ed25519",
          "description": "Signature algorithm. MUST be `ed25519` in v1."
        },
        "value": {
          "type": "string",
          "minLength": 1,
          "description": "Base64url-encoded (no padding) Ed25519 signature bytes."
        }
      }
    },
    "allowedCaller": {
      "type": "object",
      "additionalProperties": false,
      "required": ["subject_key"],
      "description": "One caller entry recognized by a key-use passport. Verifier matches entries against a resolved `CallerBinding` produced by the `caller-binding` crate: `subject_key` MUST overlap `CallerBinding.subject_keys`; `label` MUST match `CallerBinding.caller_label` when present; `kind` MUST match `CallerBinding.subject_kind` when present. Only public-key material appears here; binding entries MUST NOT embed secrets.",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "http-module",
            "in-process-module",
            "operator",
            "participant",
            "node",
            "org"
          ],
          "description": "Optional caller-kind discriminator matching `CallerSubjectKind`. When present, constrains the resolved binding's subject kind."
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "Optional human-readable caller label used for audit and operator diagnosis. When present, must match the resolved `CallerBinding.caller_label`."
        },
        "subject_key": {
          "type": "string",
          "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]+$",
          "description": "Public subject key in DID Key form. Required."
        }
      }
    },
    "scopeProfileV1": {
      "type": "object",
      "additionalProperties": true,
      "required": ["profile"],
      "description": "One key-use profile entry in `scope.profiles[]`. The `profile` field is the discriminator. Recognized discriminators (`sealer-access@v1`, `memarium-space-access@v1`, `memarium-declassify@v1`, `community-key-access@v1`) trigger profile-specific shape validation below. Unknown discriminators are tolerated by the schema but MUST NOT grant access at the verifier.",
      "properties": {
        "profile": {
          "type": "string",
          "minLength": 1,
          "description": "Profile discriminator. Recognized values: `sealer-access@v1`, `memarium-space-access@v1`, `memarium-declassify@v1`, `community-key-access@v1`."
        }
      },
      "allOf": [
        {
          "if": {
            "properties": { "profile": { "const": "sealer-access@v1" } },
            "required": ["profile"]
          },
          "then": { "$ref": "#/$defs/sealerAccessProfileV1" }
        },
        {
          "if": {
            "properties": { "profile": { "const": "memarium-space-access@v1" } },
            "required": ["profile"]
          },
          "then": { "$ref": "#/$defs/memariumSpaceAccessProfileV1" }
        },
        {
          "if": {
            "properties": { "profile": { "const": "memarium-declassify@v1" } },
            "required": ["profile"]
          },
          "then": { "$ref": "#/$defs/memariumDeclassifyProfileV1" }
        },
        {
          "if": {
            "properties": { "profile": { "const": "community-key-access@v1" } },
            "required": ["profile"]
          },
          "then": { "$ref": "#/$defs/communityKeyAccessProfileV1" }
        }
      ]
    },
    "sealerAccessProfileV1": {
      "type": "object",
      "additionalProperties": true,
      "required": ["profile", "grants", "max_revocation_staleness_seconds"],
      "description": "Authorizes Sealer AEAD operations on bounded semantic key identifiers as known to the operator. `key_ref_prefixes` and grant targets match values such as `key:community:...`; signer-layer wrapping such as `proxy:` is not part of the authorization tag. The dispatch layer is responsible for translating concrete signer `KeyRef` values before evaluation. See proposal 038 §Profile `sealer-access@v1`.",
      "properties": {
        "profile": { "const": "sealer-access@v1" },
        "grants": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string", "minLength": 1 }
          },
          "description": "Map of sealer grant-type -> target[]. Recognized grant types: `sealer/seal`, `sealer/open`, `sealer/derive-aead-key`. Targets MUST be semantic key identifier strings (for example `key:community:...`) or the wildcard `*` (wildcard permitted only for operator/root/break-glass/test profiles)."
        },
        "key_ref_prefixes": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Optional prefix constraint over semantic key identifiers. When present, every non-wildcard target MUST share a prefix with at least one entry. Signer-layer wrappers such as `proxy:` are not matched here."
        },
        "suites": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1, "pattern": "^[a-z0-9][a-z0-9_-]*@v[0-9]+$" },
          "description": "Optional AEAD ciphersuite constraint. When present, restricts the allowed `CiphersuiteId` on matched operations."
        },
        "max_revocation_staleness_seconds": {
          "type": "integer",
          "minimum": 1,
          "description": "Per-profile maximum acceptable revocation-view staleness (seconds). The effective runtime bound is `min(this, local verifier T_max)`."
        }
      }
    },
    "memariumSpaceAccessProfileV1": {
      "type": "object",
      "additionalProperties": true,
      "required": ["profile", "grants", "spaces", "max_revocation_staleness_seconds"],
      "description": "Authorizes Memarium space-level operations at a layer above Sealer. See proposal 038 §Profile `memarium-space-access@v1`.",
      "properties": {
        "profile": { "const": "memarium-space-access@v1" },
        "grants": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string", "minLength": 1 }
          },
          "description": "Map of memarium grant-type -> target[]. Recognized grant types: `memarium/read`, `memarium/write`, `memarium/index`, `memarium/cache`, `memarium/promote`, `memarium/forget`, `memarium/crisis-status`, `memarium/crisis-resolve`. Target entries are Memarium space names or the wildcard `*`."
        },
        "spaces": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Enumerates the Memarium space names this profile applies to (e.g. `community`, `crisis`)."
        },
        "community_ids": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Optional restriction to a bounded set of community identifiers."
        },
        "entry_kinds": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Optional restriction to a bounded set of Memarium entry kinds."
        },
        "max_revocation_staleness_seconds": {
          "type": "integer",
          "minimum": 1,
          "description": "Per-profile maximum acceptable revocation-view staleness (seconds)."
        }
      }
    },
    "memariumDeclassifyProfileV1": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "profile",
        "grants",
        "spaces",
        "surfaces",
        "topic_classes",
        "modes",
        "from_tiers",
        "to_tiers",
        "max_revocation_staleness_seconds"
      ],
      "description": "Authorizes append-only Memarium declassification policy facts. This profile is deliberately separate from `memarium-space-access@v1`: declassification binds not only to a space, but also to surface, topic class, mode, and tier transition.",
      "properties": {
        "profile": { "const": "memarium-declassify@v1" },
        "grants": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string", "minLength": 1 }
          },
          "description": "Map of memarium grant-type -> target[]. Recognized grant type: `memarium/declassify`. Target entries are Memarium space names or the wildcard `*`."
        },
        "spaces": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 }
        },
        "surfaces": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "enum": ["agora", "whisper", "inac", "export", "bus"] }
        },
        "topic_classes": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 }
        },
        "modes": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "enum": ["one-shot", "persistent-for-topic-class"] }
        },
        "from_tiers": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "enum": ["Personal", "Community", "Public"] }
        },
        "to_tiers": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "enum": ["Personal", "Community", "Public"] }
        },
        "community_ids": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Optional restriction to a bounded set of community identifiers."
        },
        "entry_kinds": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Optional restriction to a bounded set of Memarium entry kinds."
        },
        "max_revocation_staleness_seconds": {
          "type": "integer",
          "minimum": 1,
          "description": "Per-profile maximum acceptable revocation-view staleness (seconds)."
        }
      }
    },
    "communityKeyAccessProfileV1": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "profile",
        "grants",
        "community_ids",
        "max_revocation_staleness_seconds"
      ],
      "description": "Authorizes community key material reception, rotation, and distribution decisions at a layer above Sealer. See proposal 038 §Profile `community-key-access@v1`.",
      "properties": {
        "profile": { "const": "community-key-access@v1" },
        "grants": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string", "minLength": 1 }
          },
          "description": "Map of community grant-type -> target[]. Recognized grant types: `community/key-receive`, `community/key-rotate`, `community/key-distribute`. Target entries are community identifiers or the wildcard `*`."
        },
        "community_ids": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Enumerates the community identifiers this profile applies to."
        },
        "key_domains": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Optional restriction to bounded key domains (e.g. `space:community`, `domain:curation`, `domain:crisis:region:pl`)."
        },
        "epoch_range": {
          "type": "object",
          "additionalProperties": false,
          "required": ["min", "max"],
          "properties": {
            "min": { "type": "integer", "minimum": 0 },
            "max": { "type": "integer", "minimum": 0 }
          },
          "description": "Optional restriction to a bounded epoch window. Verifier rejects operations with `epoch` outside `[min, max]`."
        },
        "max_revocation_staleness_seconds": {
          "type": "integer",
          "minimum": 1,
          "description": "Per-profile maximum acceptable revocation-view staleness (seconds)."
        }
      }
    }
  },
  "allOf": [
    {
      "description": "passport_id must begin with `passport:capability:` — enforced via pattern above, redundant allOf kept for explicitness.",
      "if": {
        "required": ["passport_id"]
      },
      "then": {
        "properties": {
          "passport_id": {
            "pattern": "^passport:capability:"
          }
        }
      }
    }
  ]
}
