{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:orbiplex:schema:peer-handshake:v1",
  "title": "PeerHandshake v1",
  "description": "Machine-readable schema for signed peer session establishment over the Node networking baseline. In v1 the signed surface is the deterministic CBOR image of the semantic handshake payload excluding only the `signature` field itself. Framing-only transport metadata may remain outside that signed payload. This contract remains node-scoped in the MVP baseline: it authenticates infrastructure and establishes an encrypted node-to-node session, while participant authentication happens later at the application-message layer over that established channel.",
  "type": "object",
  "additionalProperties": true,
  "x-dia-workflow": "project",
  "x-dia-status": "draft",
  "x-dia-basis": [
    "doc/project/40-proposals/014-node-transport-and-discovery-mvp.md",
    "doc/project/50-requirements/requirements-006.md",
    "doc/project/60-solutions/node.md"
  ],
  "required": [
    "schema/v",
    "handshake/id",
    "handshake/mode",
    "ts",
    "sender/node-id",
    "key/alg",
    "key/public",
    "session/pub",
    "nonce",
    "signature"
  ],
  "properties": {
    "schema/v": {
      "const": 1,
      "description": "Schema version."
    },
    "handshake/id": {
      "type": "string",
      "minLength": 1,
      "description": "Stable identifier of this handshake attempt."
    },
    "handshake/mode": {
      "type": "string",
      "enum": [
        "hello",
        "ack"
      ],
      "description": "Explicit discriminator for the symmetric handshake family. In v1 both `hello` and `ack` remain artifacts of the same schema family, while `ack/of-handshake-id` provides the cryptographic response binding."
    },
    "ack/of-handshake-id": {
      "type": "string",
      "minLength": 1,
      "description": "Reference to the original handshake when `handshake/mode = ack`. This MUST be part of the signed payload."
    },
    "ts": {
      "type": "string",
      "format": "date-time",
      "description": "Timestamp of the handshake artifact."
    },
    "sender/node-id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^node:did:key:z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Stable infrastructure identity of the Node sending this handshake artifact. In v1 this MUST use the canonical `node:did:key:z...` format, and it MUST NOT be replaced with participant-scoped identity material."
    },
    "recipient/node-id": {
      "type": "string",
      "minLength": 1,
      "pattern": "^node:did:key:z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Optional directed handshake recipient. If present, it is part of the signed payload."
    },
    "key/alg": {
      "type": "string",
      "enum": [
        "ed25519"
      ],
      "description": "Algorithm of the sender key."
    },
    "key/public": {
      "type": "string",
      "minLength": 1,
      "pattern": "^z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Canonical did:key fingerprint payload corresponding to `sender/node-id`."
    },
    "session/pub": {
      "type": "string",
      "minLength": 43,
      "maxLength": 43,
      "pattern": "^[A-Za-z0-9_-]{43}$",
      "description": "Fresh per-handshake X25519 public key encoded as raw unpadded base64url for the 32-byte public key. This field is ephemeral session material, not identity material, so it MUST NOT be wrapped as `did:key` or prefixed with multicodec bytes."
    },
    "protocol/version": {
      "type": "string",
      "minLength": 1,
      "description": "Protocol version for interpreting this handshake family. In v1 this is primarily interpretation context and domain-separation input, not mutable handshake business data."
    },
    "transport/profile": {
      "type": "string",
      "enum": [
        "wss"
      ],
      "description": "Framing-level or per-hop transport profile of the current session. This is not part of the signed semantic payload unless asserted as a capability claim."
    },
    "session/intent": {
      "type": "string",
      "enum": [
        "bootstrap",
        "peer-connect",
        "reconnect"
      ],
      "description": "High-level intent of this session attempt."
    },
    "nonce": {
      "type": "string",
      "minLength": 1,
      "description": "Fresh nonce used to bind this session attempt and reduce replay risk. v1 replay protection assumes a roughly `+-30s` clock-skew window and per-peer nonce retention of about `120s`."
    },
    "capabilities/offered": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "pattern": "^(core|role|plugin)/[a-z0-9-]+(?:/[a-z0-9-]+)*$"
      },
      "description": "Optional capability claims offered as part of the signed handshake payload."
    },
    "terms/negotiated": {
      "type": "object",
      "additionalProperties": true,
      "description": "Optional negotiated handshake terms carried inside the signed payload."
    },
    "signature": {
      "$ref": "#/$defs/signature"
    },
    "policy_annotations": {
      "type": "object",
      "additionalProperties": true,
      "description": "Optional local or federation-local annotations that do not change core session semantics."
    }
  },
  "$defs": {
    "signature": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "alg",
        "value"
      ],
      "properties": {
        "alg": {
          "type": "string",
          "enum": [
            "ed25519"
          ]
        },
        "value": {
          "type": "string",
          "minLength": 1
        }
      }
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "handshake/mode": {
            "const": "ack"
          }
        },
        "required": [
          "handshake/mode"
        ]
      },
      "then": {
        "required": [
          "ack/of-handshake-id"
        ]
      }
    }
  ]
}
