Credential Models

One delegation credential model, two schema eras: the legacy VC 1.0 shape and the card-era VC 2.0 + ZCAP-LD profile

Credentials

A delegation in KYA-OS is issued as a W3C Verifiable Credential whose type array includes both VerifiableCredential and DelegationCredential. A DelegationCredential carries a permission, not a claim: it says what the delegate may do, on whose authority, within which constraints. Chains of these credentials — one credential per hop — connect every action back to a Responsible Party.

The 1.x protocol line carries this one model in two schema eras (SPEC.md §6):

EraWire shapeRevocationProof suiteDefined in
Legacy VC 1.0 eraVC 1.0; credentialSubject carries only id + delegationStatusList2021Ed25519Signature2020SPEC.md §6.2
Card eraVC 2.0; credentialSubject IS an attenuated ZCAP-LD capabilityW3C Bitstring Status List v1.0DataIntegrityProof (eddsa-jcs-2022)SPEC.md §6.10

Both shapes remain valid for the entire 1.x line. The legacy session profile issues and verifies the VC 1.0 shape; the Entity Card path issues and verifies the VC 2.0 + ZCAP-LD shape. An implementation encountering one never needs to accept the other in its place.


The CRISP constraint envelope

Constraints on a delegation use the CRISP envelope defined in SPEC.md §6.3:

interface DelegationConstraints {
  // Temporal bounds (Unix epoch seconds)
  notBefore?: number;
  notAfter?: number;

  // Simple scope list (tool names, resource patterns)
  scopes?: string[];

  // Audience restriction (server DID or domain)
  audience?: string | string[];

  // Extended CRISP constraints
  crisp?: {
    budget?: {
      unit: "USD" | "ops" | "points";
      cap: number;
      window?: { kind: "rolling" | "fixed"; durationSec: number };
    };
    scopes: Array<{
      resource: string;
      matcher: "exact" | "prefix" | "regex";
      constraints?: Record<string, unknown>;
    }>;
  };
}

In the card era the same discipline is expressed as ZCAP-LD caveats on the capability (for example ValidUntil, MaxAmount), governed by the attenuation invariants below.


Chain attenuation invariants

Delegations form chains: root → … → leaf, one credential per hop, and every chain terminates at a Responsible Party. A chain is valid only if every hop attenuates its parent — any broadening hop invalidates the whole chain, fail-closed:

  • Action subset — a child's allowed actions must be a subset of its parent's
  • Monotone caveats — no parent caveat may be silently dropped; shared caveat types must narrow (child MaxAmount ≤ parent, child ValidUntil ≤ parent); unknown caveat types are replicated verbatim
  • Validity narrowing — child validUntil ≤ parent
  • Continuity — the parent's delegate must equal the child's issuer (you may only re-delegate what was delegated to your key), and the child references its parent by identifier
  • Constant target — the invocationTarget stays constant along the chain
  • Bounded depth — chains must not exceed 10 hops (MAX_DELEGATION_DEPTH)
  • Root rule — the root credential is issued by the Responsible Party and anchors the resource it governs

See Chained Credential for how hops reference each other and how a verifier recomputes the chain.


Issuing a delegation credential

The reference implementation issues legacy-era credentials via @kya-os/mcp/delegation:

import { createDelegationIssuer } from "@kya-os/mcp/delegation";

const issuer = createDelegationIssuer(identity, signVc);

const credential = await issuer.createAndIssueDelegation({
  id: "del-001",
  issuerDid: "did:web:principal.example.com",
  subjectDid: "did:key:z6MkDelegate...",
  constraints: {
    scopes: ["tool:read_file", "tool:list_directory"],
    notAfter: Math.floor(Date.now() / 1000) + 86_400,
    audience: "did:web:mcp-server.example.com",
  },
});

Card-era chains are built and evaluated with the @kya-os/mcp/card delegation helpers (validateDelegationChain, evaluateDelegationChain), which enforce the attenuation invariants and Bitstring revocation on every verification.


Credential Structures