{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://observerprotocol.org/schemas/delegation/v2.1.json",
  "title": "Delegation Credential Schema v2.1",
  "description": "JSON Schema for DelegationCredential Verifiable Credentials aligned with AIP v0.8 draft-1. Extends v2 with: snake_case canonicalisation of actionScope; cumulative_budget (advisory, window enum locked to credential_validity); reserved-advisory allowed_counterparty_types and geographic_restriction with constrained shapes; allowed_merchant_categories held pending categories-namespace collision; tradingMandate v0.8 sub-objects (counterparty, temporal, geographic, velocity); additionalProperties closed on actionScope and credentialSubject. The v2 URL (delegation/v2.json) remains live, immutable, and frozen at its prior content; this is a new sibling URL per the schema immutability policy (see aip repo SCHEMA_POLICY.md). Credentials previously issued against v2.json continue to validate against v2.json forever.",
  "type": "object",
  "required": [
    "@context",
    "id",
    "type",
    "issuer",
    "validFrom",
    "validUntil",
    "credentialSubject",
    "credentialSchema",
    "proof"
  ],
  "properties": {
    "@context": {
      "type": "array",
      "minItems": 1,
      "items": { "type": "string" },
      "contains": { "enum": ["https://www.w3.org/ns/credentials/v2"] }
    },
    "id": { "type": "string", "format": "uri", "pattern": "^https://|^urn:uuid:" },
    "type": {
      "type": "array",
      "minItems": 2,
      "contains": { "const": "VerifiableCredential" }
    },
    "issuer": { "type": "string", "pattern": "^did:[a-z]+:.+" },
    "validFrom": { "type": "string", "format": "date-time" },
    "validUntil": { "type": "string", "format": "date-time" },
    "credentialSubject": {
      "type": "object",
      "required": ["id", "actionScope", "delegationScope", "enforcementMode", "authorizationLevel"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string", "pattern": "^did:[a-z]+:.+" },

        "authorizationLevel": {
          "type": "string",
          "enum": ["one-time", "recurring", "policy"],
          "description": "Authorization scope granted by the principal. Level 1 (one-time) authorizes a single transaction. Level 2 (recurring) authorizes repeat transactions with one counterparty up to a ceiling. Level 3 (policy) references a broader standing policy."
        },

        "authorizationConfig": {
          "type": "object",
          "description": "Level-specific configuration. Exactly one of oneTime, recurring, or policy must be present, matching authorizationLevel. Enforced via if/then at the credentialSubject level.",
          "properties": {
            "oneTime": {
              "type": "object",
              "description": "Level 1: single-transaction authorization. The credential is consumed after one successful settlement.",
              "properties": {
                "counterparty_did": {
                  "type": "string",
                  "pattern": "^did:",
                  "description": "DID of the specific counterparty for this transaction"
                },
                "amount": {
                  "type": "string",
                  "description": "Exact transaction amount (e.g. '50.00')"
                },
                "currency": {
                  "type": "string",
                  "description": "ISO-4217 currency or asset code (e.g. 'USD', 'USDT')"
                },
                "rail": {
                  "type": "string",
                  "description": "Settlement rail for this transaction (e.g. 'usdt-trc20', 'lightning')"
                },
                "execution_deadline": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Credential expires if not used by this time. Short window (e.g. 15 minutes)."
                },
                "purchase_description": {
                  "type": "string",
                  "description": "Human-readable description of what is being purchased"
                }
              },
              "required": ["counterparty_did", "amount", "currency", "rail"]
            },

            "recurring": {
              "type": "object",
              "description": "Level 2: counterparty-scoped recurring authorization. Authorizes future transactions with one specific counterparty up to a ceiling per period.",
              "properties": {
                "counterparty_did": {
                  "type": "string",
                  "pattern": "^did:",
                  "description": "DID of the specific counterparty"
                },
                "ceiling_amount": {
                  "type": "string",
                  "description": "Maximum aggregate spend per period (e.g. '500.00')"
                },
                "ceiling_currency": {
                  "type": "string",
                  "description": "ISO-4217 currency for ceiling"
                },
                "per_transaction_max": {
                  "type": "string",
                  "description": "Optional per-transaction cap within the ceiling"
                },
                "period": {
                  "type": "string",
                  "description": "ISO-8601 duration for ceiling reset (e.g. 'P30D' for monthly)"
                },
                "valid_until": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When this recurring authorization expires entirely"
                },
                "allowed_rails": {
                  "type": "array",
                  "items": { "type": "string" },
                  "description": "Rails permitted for recurring transactions with this counterparty"
                }
              },
              "required": ["counterparty_did", "ceiling_amount", "ceiling_currency", "period"]
            },

            "policy": {
              "type": "object",
              "description": "Level 3: broader standing policy. The policy_id references an internal policy document stored in Sovereign. Counterparties see only the policy_id and the external credential fields — never the full policy logic.",
              "properties": {
                "policy_id": {
                  "type": "string",
                  "description": "Reference to the internal policy document in Sovereign"
                },
                "rail_preference": {
                  "type": "array",
                  "items": { "type": "string" },
                  "description": "Priority-ordered rail preference list. First match against counterparty's accepted rails wins. E.g. ['usdt-trc20', 'lightning'] means prefer USDT, fall back to Lightning."
                },
                "per_rail_caps": {
                  "type": "object",
                  "description": "Per-rail spending caps. Keys are rail identifiers.",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "per_transaction": { "type": "string", "description": "Max per single transaction on this rail" },
                      "aggregate": { "type": "string", "description": "Max aggregate spend on this rail per period" },
                      "period": { "type": "string", "description": "ISO-8601 duration for aggregate reset" },
                      "currency": { "type": "string" }
                    }
                  }
                },
                "escalation_threshold": {
                  "type": "object",
                  "description": "Transactions above this amount trigger human notification before execution",
                  "properties": {
                    "amount": { "type": "string" },
                    "currency": { "type": "string" }
                  }
                },
                "fallback_rules": {
                  "type": "array",
                  "description": "Conditional rail selection rules evaluated in order",
                  "items": {
                    "type": "object",
                    "properties": {
                      "condition": { "type": "string", "description": "Condition expression (e.g. 'usdt_balance < amount')" },
                      "then_rail": { "type": "string", "description": "Rail to use if condition is true" }
                    },
                    "required": ["condition", "then_rail"]
                  }
                },
                "time_windows": {
                  "type": "object",
                  "description": "Aggregate caps per time window across all counterparties",
                  "properties": {
                    "daily": { "type": "object", "properties": { "amount": { "type": "string" }, "currency": { "type": "string" } } },
                    "weekly": { "type": "object", "properties": { "amount": { "type": "string" }, "currency": { "type": "string" } } },
                    "monthly": { "type": "object", "properties": { "amount": { "type": "string" }, "currency": { "type": "string" } } }
                  }
                }
              },
              "required": ["policy_id", "rail_preference"]
            }
          }
        },

        "actionScope": {
          "type": "object",
          "description": "Spending-mandate surface. See aip-v0.8-draft-1.md §1.1–§1.3. Closed shape: additionalProperties is false; new fields require a numbered-draft schema update.",
          "properties": {
            "allowed_rails": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Binding. Proposed action's rail MUST be in this list."
            },
            "per_transaction_ceiling": {
              "type": "object",
              "description": "Binding. Per-action amount ceiling, same-currency comparison only — no FX. See §1.2 and §3.2.",
              "properties": {
                "amount": { "type": "string", "description": "Decimal amount as string." },
                "currency": { "type": "string", "description": "ISO 4217 code, token symbol, or rail-native unit." }
              },
              "required": ["amount", "currency"],
              "additionalProperties": false
            },
            "allowed_transaction_categories": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Binding. Proposed transaction-category MUST be in this list."
            },
            "cumulative_budget": {
              "type": "object",
              "description": "Advisory. Declared total spend cap over the credential's validity window. v0.8 does NOT define binding enforcement (§1.2). MUST NOT ground a deny verdict.",
              "properties": {
                "amount": { "type": "string", "description": "Decimal amount as string." },
                "currency": { "type": "string", "description": "Must match per_transaction_ceiling.currency for advisory accumulation; cross-currency prior spends reported as uncountable." },
                "window": {
                  "type": "string",
                  "enum": ["credential_validity"],
                  "description": "v0.8 locks this to the credential's validFrom→validUntil envelope. Rolling-/calendar-window semantics are reserved for a future draft."
                }
              },
              "required": ["amount", "currency", "window"],
              "additionalProperties": false
            },
            "allowed_counterparty_types": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": ["verified_merchant", "kyb_verified_org", "peer_agent", "sovereign_self_attested"]
              },
              "description": "Reserved — ADVISORY in v0.8 (§1.3). Declaration of intended counterparty classes; MUST NOT ground a deny verdict."
            },
            "geographic_restriction": {
              "type": "object",
              "description": "Reserved — ADVISORY in v0.8 (§1.3). Spending-surface geographic intent; MUST NOT ground a deny verdict. Trading-surface geographic enforcement (§2.3) is unaffected.",
              "properties": {
                "allowed": {
                  "type": "array",
                  "items": { "type": "string", "pattern": "^[A-Z]{2}$" },
                  "description": "ISO 3166-1 alpha-2 country codes."
                },
                "disallowed": {
                  "type": "array",
                  "items": { "type": "string", "pattern": "^[A-Z]{2}$" },
                  "description": "ISO 3166-1 alpha-2 country codes."
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        "delegationScope": {
          "type": "object",
          "required": ["may_delegate_further"],
          "properties": {
            "may_delegate_further": { "type": "boolean" },
            "max_child_action_scope": { "type": "object" },
            "may_delegate_delegation_authority": { "type": "boolean" },
            "allowed_child_subject_types": { "type": "array", "items": { "type": "string" } }
          }
        },
        "acl": {
          "type": "object",
          "properties": {
            "revocation_authority": { "type": "array", "items": { "type": "string", "pattern": "^did:" } },
            "modification_authority": { "type": "array", "items": { "type": "string", "pattern": "^did:" } }
          }
        },
        "enforcementMode": { "type": "string", "enum": ["protocol_native", "pre_transaction_check"] },
        "parentDelegationId": { "type": ["string", "null"], "format": "uri" },
        "kybCredentialId": { "type": ["string", "null"], "format": "uri" },
        "tradingMandate": {
          "type": "object",
          "description": "AIP v0.7 — optional trading-domain mandate. Expresses the scope of a delegated agent's trading authority. Verification is unchanged; the field is signed under the existing credential proof. See aip-v0.7-draft-1.md.",
          "properties": {
            "allowedVenues": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Closed list of venue identifiers the agent MAY transact on. A venue not present is denied."
            },
            "allowedInstruments": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Closed list of permitted assets, pairs, or instrument classes."
            },
            "maxNotionalPerOrder": {
              "type": "integer",
              "minimum": 0,
              "description": "Maximum notional value of any single order, denominated by `unit`."
            },
            "maxPosition": {
              "type": "integer",
              "minimum": 0,
              "description": "Maximum aggregate open exposure under this mandate, denominated by `unit`."
            },
            "unit": {
              "type": "string",
              "description": "Denomination currency or asset code for maxNotionalPerOrder and maxPosition. REQUIRED whenever either is present; verifiers MUST NOT infer units."
            },
            "dailyDrawdownCap": {
              "type": "object",
              "required": ["limit", "type", "window"],
              "properties": {
                "limit": { "type": "number", "minimum": 0 },
                "type": { "type": "string", "enum": ["percent", "absolute"] },
                "window": { "type": "string", "description": "Duration string, canonical form '<integer>h' (e.g. '24h')." }
              }
            },
            "counterparty": {
              "type": "object",
              "description": "AIP v0.8 — counterparty controls. See aip-v0.8-draft-1.md §2.1.",
              "properties": {
                "allowList": {
                  "type": "array",
                  "items": { "type": "string" },
                  "description": "Closed list of permitted counterparties. Entries SHOULD be DIDs; raw rail-specific addresses accepted as fallback."
                },
                "blockList": {
                  "type": "array",
                  "items": { "type": "string" },
                  "description": "Closed list of denied counterparties. DID or address strings."
                },
                "requireIssuerClassIn": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": ["op_first_party", "sovereign_self_attested", "third_party_kyb", "partner", "peer_agent"]
                  },
                  "description": "Counterparty's OP-tracked issuer_class MUST be an element of this set. Set semantics, not a numeric tier."
                }
              }
            },
            "temporal": {
              "type": "object",
              "description": "AIP v0.8 — time-of-day window constraint beyond the credential's validUntil. See aip-v0.8-draft-1.md §2.2.",
              "properties": {
                "allowedTimeWindows": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": ["start", "end", "timezone"],
                    "properties": {
                      "start": { "type": "string", "pattern": "^[0-2][0-9]:[0-5][0-9]$", "description": "HH:MM 24-hour." },
                      "end": { "type": "string", "pattern": "^[0-2][0-9]:[0-5][0-9]$", "description": "HH:MM 24-hour." },
                      "timezone": { "type": "string", "description": "IANA timezone name (e.g. 'UTC', 'America/New_York')." },
                      "daysOfWeek": {
                        "type": "array",
                        "items": { "type": "string", "enum": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] },
                        "description": "Days the window applies. Defaults to all days when absent."
                      }
                    }
                  }
                }
              }
            },
            "geographic": {
              "type": "object",
              "description": "AIP v0.8 — jurisdictional constraints on counterparties. Requires counterparty attestation data carrying jurisdiction. See aip-v0.8-draft-1.md §2.3.",
              "properties": {
                "blockedJurisdictions": {
                  "type": "array",
                  "items": { "type": "string", "pattern": "^[A-Z]{2}$" },
                  "description": "ISO 3166-1 alpha-2 country codes; counterparty MUST NOT belong to any. Fail-open if jurisdiction unknown."
                },
                "allowedJurisdictionsOnly": {
                  "type": "array",
                  "items": { "type": "string", "pattern": "^[A-Z]{2}$" },
                  "description": "ISO 3166-1 alpha-2 country codes; if present, counterparty MUST belong to one of these. Fail-closed if jurisdiction unknown."
                }
              }
            },
            "velocity": {
              "type": "object",
              "description": "AIP v0.8 — aggregate volume velocity caps. Stateful; SHOULD be enforced server-side or by a stateful embedded evaluator. Cap values are denominated by the sibling tradingMandate.unit field. See aip-v0.8-draft-1.md §2.4.",
              "properties": {
                "dailyVolumeCap": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Maximum aggregate transacted volume in any 24h rolling window."
                },
                "monthlyVolumeCap": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Maximum aggregate transacted volume in any 30d rolling window."
                }
              }
            }
          },
          "dependencies": {
            "maxNotionalPerOrder": ["unit"],
            "maxPosition": ["unit"]
          }
        }
      },
      "allOf": [
        {
          "if": { "properties": { "authorizationLevel": { "const": "one-time" } } },
          "then": {
            "properties": {
              "authorizationConfig": {
                "required": ["oneTime"],
                "properties": { "recurring": false, "policy": false }
              }
            },
            "required": ["authorizationConfig"]
          }
        },
        {
          "if": { "properties": { "authorizationLevel": { "const": "recurring" } } },
          "then": {
            "properties": {
              "authorizationConfig": {
                "required": ["recurring"],
                "properties": { "oneTime": false, "policy": false }
              }
            },
            "required": ["authorizationConfig"]
          }
        },
        {
          "if": { "properties": { "authorizationLevel": { "const": "policy" } } },
          "then": {
            "properties": {
              "authorizationConfig": {
                "required": ["policy"],
                "properties": { "oneTime": false, "recurring": false }
              }
            },
            "required": ["authorizationConfig"]
          }
        }
      ]
    },
    "credentialSchema": {
      "type": "object",
      "required": ["id", "type"],
      "properties": {
        "id": { "type": "string", "pattern": "^https://observerprotocol.org/schemas/" },
        "type": { "const": "JsonSchema" }
      }
    },
    "credentialStatus": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "type", "statusPurpose", "statusListIndex", "statusListCredential"],
        "properties": {
          "id": { "type": "string", "format": "uri" },
          "type": { "type": "string", "const": "BitstringStatusListEntry" },
          "statusPurpose": { "type": "string", "enum": ["revocation", "suspension"] },
          "statusListIndex": { "type": "string", "pattern": "^[0-9]+$" },
          "statusListCredential": { "type": "string", "format": "uri" }
        },
        "additionalProperties": false
      }
    },
    "proof": {
      "type": "object",
      "required": ["type", "created", "verificationMethod", "proofPurpose", "proofValue"],
      "properties": {
        "type": { "const": "Ed25519Signature2026" },
        "created": { "type": "string", "format": "date-time" },
        "verificationMethod": { "type": "string", "pattern": "^did:[a-z]+:.+#.+" },
        "proofPurpose": { "const": "assertionMethod" },
        "proofValue": { "type": "string", "pattern": "^[1-9A-HJ-NP-Za-km-z]+$" }
      }
    }
  },
  "additionalProperties": false
}
