{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://aidid.im/schemas/delegation/delegation-payload.schema.json",
  "title": "AIDID Delegation Payload (parsed)",
  "description": "The PARSED object of a sub-DID document's `service[]` entry whose `service_type = \"AididDelegation\"`. On the wire the field is the *whitespace-free* canonical JSON text produced by `canonicalizeDelegationForSigning` — not an object. Field order is fixed and every field is present, including the ones whose value is `null` (a missing key makes the canonical text un-reproducible, so it is a wire error). `parseDelegationPayload` accepts both the JSON text and an already-parsed object and returns `{ ok: true, payload }` or `{ ok: false, error, message }`; an unknown `version` is always rejected (fail-closed).",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "version",
    "serviceType",
    "parentDid",
    "subDid",
    "label",
    "purpose",
    "note",
    "scope",
    "agentRef",
    "createdAt",
    "expiresAt",
    "status",
    "revokedAt"
  ],
  "properties": {
    "version": {
      "const": "aidid-delegation/1",
      "description": "Wire version. `parseDelegationPayload` rejects any other value, including future versions."
    },
    "serviceType": {
      "const": "AididDelegation",
      "description": "Mirrors the DID document `service_type`. Always present in the canonical text (`parseDelegationPayload` tolerates its absence on input, but the canonical serializer always emits it)."
    },
    "parentDid": {
      "type": "string",
      "pattern": "^did:msg:[A-Za-z0-9._-]+$",
      "description": "Delegating parent DID (the authority)."
    },
    "subDid": {
      "type": "string",
      "pattern": "^did:msg:[A-Za-z0-9._-]+$",
      "description": "Delegated sub-DID identifier. A delegation only authorizes this exact DID."
    },
    "label": {
      "type": "string",
      "pattern": "^[A-Za-z0-9._-]{1,64}$",
      "description": "Human-readable sub-DID label. Participates in identifier derivation (`deriveSubDidIdentifier`), so it is part of the signed text."
    },
    "purpose": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120,
      "description": "Required purpose. `lib/subDid.ts` rejects more than `SUB_DID_MAX_PURPOSE_LENGTH` (120) characters instead of truncating."
    },
    "note": {
      "type": ["string", "null"],
      "maxLength": 400,
      "description": "Optional free-form note (`SUB_DID_MAX_NOTE_LENGTH` = 400). `null` when absent; `null` is a real, present key."
    },
    "scope": {
      "type": "object",
      "additionalProperties": false,
      "required": ["resources", "maxUsesPerDay", "quotaSemantics"],
      "properties": {
        "resources": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9:_./-]{0,63}$"
          },
          "description": "Resource allow-list. Order is semantic and preserved; duplicates are dropped by `normalizeDelegationResources` (first occurrence wins)."
        },
        "maxUsesPerDay": {
          "type": ["integer", "null"],
          "exclusiveMinimum": 0,
          "description": "DECLARED daily cap only. It is metadata; nothing on chain enforces it and `verifyDelegation`/`evaluateDelegation` never count against it (see `quotaSemantics`)."
        },
        "quotaSemantics": {
          "const": "declared-not-enforced",
          "description": "Fixed marker: the quota is declared and deliberately NOT enforced on chain or by the SDK."
        }
      },
      "description": "Declared authorization scope (resource allow-list + declared quota)."
    },
    "agentRef": {
      "type": ["string", "null"],
      "maxLength": 200,
      "description": "Optional bound AI-agent identifier (`agent_registry_v1` key or agent DID). `buildDelegationPayload` rejects an empty/whitespace-only string (`SUB_DID_MAX_AGENT_REF_LENGTH` = 200); `null` means no agent is bound."
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "description": "ISO-8601 timestamp. Part of the signed canonical text."
    },
    "expiresAt": {
      "type": ["string", "null"],
      "format": "date-time",
      "description": "ISO-8601 expiry, or `null` for no expiry (still revocable). Creation/update rejects past timestamps and any value more than 1 year ahead (`SUB_DID_MAX_TTL_MS`)."
    },
    "status": {
      "enum": ["active", "revoked", "deactivated"],
      "description": "Lifecycle status. `revoked` and `deactivated` are terminal; re-granting requires a new sub-DID (new label)."
    },
    "revokedAt": {
      "type": ["string", "null"],
      "format": "date-time",
      "description": "ISO-8601 timestamp of revocation, or `null`."
    },
    "keyMode": {
      "enum": ["parent-delegated", "own-key"],
      "description": "Key mode. `parent-delegated` (default, and what old records imply) reuses the parent DID's key material. `own-key` is the PRE-STAGE-B protocol-level offline self-held key: the parent DID binds the agent's own Dilithium-5 public key with its own signature over the canonical text. It is NOT a production/on-chain binding, has NO revocation and NO quota. `parseDelegationPayload` defaults a missing value to `parent-delegated` and always returns this field."
    },
    "agentPublicKeyMultibase": {
      "type": ["string", "null"],
      "pattern": "^z[A-Za-z0-9+/]+={0,2}$",
      "description": "The agent's Dilithium-5 public key as `z` + standard base64. Required (and must decode to exactly 2592 bytes) when `keyMode` is `own-key`; must be `null` for `parent-delegated`. `parseDelegationPayload` always returns this field, normalised to `null` for `parent-delegated`."
    }
  },
  "examples": [
    {
      "version": "aidid-delegation/1",
      "serviceType": "AididDelegation",
      "parentDid": "did:msg:7CouhO1PzOdSzT1NP1v3Pg",
      "subDid": "did:msg:Q3kR2sVn0pLxWmYtZbA1c",
      "label": "agent-scout",
      "purpose": "Let the research agent read the membership endpoint",
      "note": null,
      "scope": {
        "resources": ["login", "vc:membership"],
        "maxUsesPerDay": 20,
        "quotaSemantics": "declared-not-enforced"
      },
      "agentRef": "agent:scout-1",
      "createdAt": "2026-10-11T08:00:00.000Z",
      "expiresAt": "2027-01-01T00:00:00.000Z",
      "status": "active",
      "revokedAt": null
    },
    {
      "version": "aidid-delegation/1",
      "serviceType": "AididDelegation",
      "parentDid": "did:msg:7CouhO1PzOdSzT1NP1v3Pg",
      "subDid": "did:msg:Q3kR2sVn0pLxWmYtZbA1c",
      "label": "sub-1",
      "purpose": "Offline demo delegation",
      "note": "revoked after the offline demo",
      "scope": {
        "resources": ["login"],
        "maxUsesPerDay": null,
        "quotaSemantics": "declared-not-enforced"
      },
      "agentRef": null,
      "createdAt": "2026-10-11T08:00:00.000Z",
      "expiresAt": null,
      "status": "revoked",
      "revokedAt": "2026-10-11T09:00:00.000Z"
    },
    {
      "version": "aidid-delegation/1",
      "serviceType": "AididDelegation",
      "parentDid": "did:msg:7CouhO1PzOdSzT1NP1v3Pg",
      "subDid": "did:msg:Z1nQk4bV8rTs2pLxWmYtZa",
      "label": "agent-ownkey",
      "purpose": "Protocol-level offline self-held key (pre-stage-B)",
      "note": null,
      "scope": {
        "resources": ["login"],
        "maxUsesPerDay": null,
        "quotaSemantics": "declared-not-enforced"
      },
      "agentRef": "agent:ownkey-1",
      "createdAt": "2026-10-11T08:00:00.000Z",
      "expiresAt": null,
      "status": "active",
      "revokedAt": null,
      "keyMode": "own-key",
      "agentPublicKeyMultibase": "zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    }
  ],
  "x-aidid": {
    "spec": {
      "doc": "AIDID_母子DID与授权规范_v1.md",
      "section": "§3.1",
      "version": "v1.14"
    },
    "status": "implemented",
    "source": [
      "frontend/lib/subDid.ts#parseDelegationPayload",
      "frontend/lib/subDid.ts#canonicalizeDelegationForSigning",
      "frontend/packages/aidid-login-sdk/src/delegation.js#buildDelegationPayload",
      "frontend/packages/aidid-login-sdk/src/delegation.js#parseDelegationPayload"
    ],
    "enumBindings": [
      {
        "pointer": "/properties/status/enum",
        "source": "DELEGATION_STATUSES"
      },
      {
        "pointer": "/properties/version/const",
        "source": "AIDID_DELEGATION_VERSION"
      },
      {
        "pointer": "/properties/serviceType/const",
        "source": "AIDID_DELEGATION_SERVICE_TYPE"
      },
      {
        "pointer": "/properties/scope/properties/quotaSemantics/const",
        "source": "DELEGATION_QUOTA_SEMANTICS"
      },
      {
        "pointer": "/properties/keyMode/enum",
        "source": "DELEGATION_KEY_MODES"
      }
    ],
    "note": "① This schema is the object AFTER `JSON.parse`. The actual wire value is its whitespace-free canonical JSON text with a fixed field order: version, serviceType, parentDid, subDid, label, purpose, note, scope{resources, maxUsesPerDay, quotaSemantics}, agentRef, createdAt, expiresAt, status, revokedAt. Every key is present even when `null`; the parent DID signs exactly that text and a verifier must rebuild it byte-for-byte. ② `keyMode` / `agentPublicKeyMultibase` are NOT in `required`: `parseDelegationPayload` always normalises both fields into its output (old input without them ⇒ `parent-delegated` / `null`), but the canonical TEXT only appends them when `keyMode === 'own-key'` — a `parent-delegated` (or legacy) payload serialises byte-for-byte identically to before own-key existed, so already-signed delegations keep verifying. ③ `scope.maxUsesPerDay` is a DECLARED value only — nothing counts or enforces it (quotaSemantics = \"declared-not-enforced\"); an RP that wants a cap must implement counting/limiting itself. ④ `expiresAt` has a hard upper bound of 1 year (`SUB_DID_MAX_TTL_MS`); `null` means no expiry but the grant is still revocable. ⑤ `status` is terminal at `revoked`/`deactivated` — revoking/deactivating is irreversible; re-granting requires a new sub-DID with a new label. ⑥ `own-key` is a protocol-level offline capability: the parent binds the agent key by signature, and a verifier must opt in (`allowOwnKey`) because there is no on-chain revocation.",
    "honestLimit": "Stage A parent-delegated sub-DIDs have no independent key: this payload carries authorization semantics only. `own-key` adds a protocol-level offline self-held key (parent signature over the agent public key), but there is no on-chain authoritative binding, no revocation and no quota — those remain stage B. The payload does not carry funds or a chain-side allowance mechanism."
  }
}
