{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "$id": "https://real-life.org/rltp/v1/schemas/encounter-credential-0.25.schema.json",
 "title": "RLTP Encounter Credential (rltp-encounter@0.29)",
 "description": "Normative wire format per RLTP Encounter Layer 0.29, section 7.2. WIRE BREAK AT 0.25 (the twenty-eighth casting, the DTG adoption cast; the twenty-ninth changes no wire byte): the credential is a conformant DTG RelationshipCredential per DTGWG Core Credentials WD01 - the DTG context joins the pinned @context (three elements, in order) and the type array carries VerifiableCredential + DTGCredential + RelationshipCredential + EncounterCredential (order-insensitive, EncounterCredential = concrete RLTP hint per the WD01 PHC pattern). Issuer and credentialSubject.id are PAIR anchors (fresh always, spec 4.4), satisfying WD01 R-DID uniqueness strictly. Root and credentialSubject remain CLOSED; validity-controlling VC properties absent by construction (immutability 7.3; WD01 marks validUntil OPTIONAL). Size bounds re-measured at generation time (spec 7.5/15).",
 "type": "object",
 "required": [
  "@context",
  "type",
  "issuer",
  "validFrom",
  "credentialSubject",
  "proof"
 ],
 "additionalProperties": false,
 "properties": {
  "@context": {
   "type": "array",
   "minItems": 3,
   "maxItems": 3,
   "prefixItems": [
    {
     "const": "https://www.w3.org/ns/credentials/v2"
    },
    {
     "const": "https://firstperson.network/credentials/dtg/v1"
    },
    {
     "const": "https://real-life.org/rltp/v1"
    }
   ],
   "items": false
  },
  "type": {
   "type": "array",
   "description": "order-insensitive: four members, four distinct contains-constants - every slot pinned",
   "minItems": 4,
   "maxItems": 4,
   "allOf": [
    {
     "contains": {
      "const": "VerifiableCredential"
     }
    },
    {
     "contains": {
      "const": "DTGCredential"
     }
    },
    {
     "contains": {
      "const": "RelationshipCredential"
     }
    },
    {
     "contains": {
      "const": "EncounterCredential"
     }
    }
   ]
  },
  "issuer": {
   "$ref": "#/$defs/didKey"
  },
  "validFrom": {
   "$ref": "#/$defs/rfc3339utc"
  },
  "credentialSubject": {
   "type": "object",
   "required": [
    "id",
    "format",
    "ceremony",
    "challenge",
    "enactmentBinding"
   ],
   "additionalProperties": false,
   "properties": {
    "id": {
     "$ref": "#/$defs/didKey"
    },
    "format": {
     "const": "rltp-encounter-credential/0.25"
    },
    "ceremony": {
     "$ref": "#/$defs/ceremonyId"
    },
    "challenge": {
     "$ref": "#/$defs/challenge"
    },
    "enactmentBinding": {
     "$ref": "#/$defs/multibaseSha256"
    },
    "channel": {
     "type": "string",
     "maxLength": 64
    },
    "commitment": {
     "type": "object",
     "additionalProperties": false,
     "required": [
      "suite",
      "value"
     ],
     "properties": {
      "suite": {
       "type": "string",
       "pattern": "^[a-z0-9-]+@[0-9]+$",
       "maxLength": 32,
       "description": "commitment suite identifier from the (initially empty) suite registry; unassigned semantics in 0.24"
      },
      "value": {
       "type": "string",
       "pattern": "^z[1-9A-HJ-NP-Za-km-z]{1,96}$"
      }
     },
     "description": "pre-wired ZK seam (spec 7.2): suite names the future interpretation so later assignment is registration, not re-interpretation; verifiers MUST accept-and-ignore"
    }
   }
  },
  "proof": {
   "$ref": "#/$defs/dataIntegrityProof"
  }
 },
 "$defs": {
  "didKey": {
   "type": "string",
   "pattern": "^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]{44}$",
   "description": "did:key over an Ed25519 public key; MUST additionally decode to multicodec ed25519-pub + 32 bytes (spec 2.3)"
  },
  "ceremonyId": {
   "type": "string",
   "description": "registered ceremony identifier and version, at most 56 characters: label at most 48, each version part at most 3 digits (spec 7.5). The registered label of this profile is 19 characters; the bound leaves room for future registrations while keeping the field finite, which the credential's size guarantee requires.",
   "maxLength": 56,
   "pattern": "^[a-z0-9-]{1,48}@[0-9]{1,3}\\.[0-9]{1,3}$",
   "examples": [
    "encounter-scan@0.24"
   ]
  },
  "challenge": {
   "type": "string",
   "description": "22-88 base64url characters, at least 128 bits of cryptographically random material; UUID v4 is non-conformant",
   "pattern": "^[A-Za-z0-9_-]{22,88}$"
  },
  "multibaseSha256": {
   "type": "string",
   "pattern": "^(u[A-Za-z0-9_-]{45}[AQgw]|z[1-9A-HJ-NP-Za-km-z]{44,48})$",
   "description": "multibase-encoded multihash (sha2-256: 0x12 0x20 + 32 bytes) over JCS-canonicalized input; emit 'u' (46 chars after header), accept 'u'/'z' per CID 1.0; decoded multihash verified at parse"
  },
  "rfc3339utc": {
   "type": "string",
   "description": "RFC3339 UTC 'Z', seconds 00-59, AT MOST 3 fractional-second digits (spec 2.3) \u2014 24 characters maximum; syntactic gate, calendar validity is a parse-time check. Fractional seconds carry no protocol meaning, and spec 2.3 makes that a rule rather than an assertion: EVERY Encounter comparison \u2014 every comparison the Encounter Layer requires, and every comparison a companion document delegates to it, but NOT a companion's own time windows \u2014 normalizes EVERY operand, artifact timestamps, stored t_ch, and the local 'now', to whole seconds by truncation toward the past (delete the '.' and everything between it and the 'Z') before comparing, and the section 9 parameters are added afterwards. Because those parameters are whole seconds, truncation can only ever widen acceptance at a boundary, never narrow it. Normalization governs comparison ONLY; the bytes hashed, signed and digested are always the artifact's own, so an implementation that truncates before JCS breaks the proof. Producers SHOULD emit whole seconds (one instant, one serialization, one digest). The length bound itself is byte economy: an unbounded fraction is an unbounded field in a size-capped artifact (7.5).",
   "maxLength": 24,
   "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]{1,3})?Z$"
  },
  "dataIntegrityProof": {
   "type": "object",
   "required": [
    "type",
    "cryptosuite",
    "created",
    "verificationMethod",
    "proofPurpose",
    "proofValue",
    "@context"
   ],
   "additionalProperties": false,
   "properties": {
    "type": {
     "const": "DataIntegrityProof"
    },
    "cryptosuite": {
     "const": "eddsa-jcs-2022"
    },
    "created": {
     "$ref": "#/$defs/rfc3339utc"
    },
    "verificationMethod": {
     "type": "string",
     "pattern": "^did:key:z6Mk[1-9A-HJ-NP-Za-km-z]{44}#z6Mk[1-9A-HJ-NP-Za-km-z]{44}$"
    },
    "proofPurpose": {
     "const": "assertionMethod"
    },
    "proofValue": {
     "type": "string",
     "description": "multibase base58btc of an Ed25519 signature, which is exactly 64 bytes (RFC 8032): 'z' plus 64 to 88 base58 characters, so 65 to 89 characters and nothing else. Upper end: 58^87 <= 2^512-1 < 58^88, so 88 digits suffice and 89 are never needed. Lower end: base58btc renders each leading zero byte as one '1', so the all-zero 64-byte string is the shortest at 64. A value outside this interval is not the encoding of 64 bytes and could never have verified (spec 2.3).",
     "minLength": 65,
     "maxLength": 89,
     "pattern": "^z[1-9A-HJ-NP-Za-km-z]+$"
    },
    "@context": {
     "type": "array",
     "minItems": 3,
     "maxItems": 3,
     "prefixItems": [
      {
       "const": "https://www.w3.org/ns/credentials/v2"
      },
      {
       "const": "https://firstperson.network/credentials/dtg/v1"
      },
      {
       "const": "https://real-life.org/rltp/v1"
      }
     ],
     "items": false,
     "description": "W3C DI-EDDSA copies the document @context into the embedded proof (0.27 repair); with the DTG adoption the pinned array has three elements"
    }
   }
  }
 }
}
