{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://echo.cc/schemas/envelope-v1.json",
  "title": "echo.cc v1 producer contract",
  "description": "Canonical records produced by echo.cc. Omit unset optional fields. Structural validation only: use ecc validate for byte limits, controls, trace IDs, source/member equality, path rules, and expiry relationships. JSON parsing must reject duplicate keys.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "specversion": {
      "const": "1.0"
    },
    "id": {
      "$ref": "#/$defs/ulid"
    },
    "source": {
      "$ref": "#/$defs/member"
    },
    "type": {
      "enum": [
        "cc.echo.message.v1",
        "cc.echo.presence.v1",
        "cc.echo.ack.v1",
        "cc.echo.hold.v1",
        "cc.echo.sealed.v1"
      ]
    },
    "time": {
      "$ref": "#/$defs/timestamp"
    },
    "datacontenttype": {
      "const": "application/json"
    },
    "net": {
      "$ref": "#/$defs/token"
    },
    "expiresat": {
      "$ref": "#/$defs/timestamp"
    },
    "traceparent": {
      "type": "string",
      "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$"
    },
    "tracestate": {
      "type": "string",
      "maxLength": 512
    },
    "data": {
      "type": "object"
    },
    "certid": {
      "$ref": "#/$defs/ulid"
    },
    "signature": {
      "type": "string",
      "pattern": "^[0-9a-f]{128}$"
    }
  },
  "required": [
    "specversion",
    "id",
    "source",
    "type",
    "time",
    "datacontenttype",
    "net",
    "expiresat",
    "data"
  ],
  "$defs": {
    "token": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
    },
    "member": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}\\.[a-z0-9][a-z0-9-]{0,62}\\.[a-z0-9][a-z0-9-]{0,62}$"
    },
    "ulid": {
      "type": "string",
      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
    },
    "timestamp": {
      "type": "string",
      "format": "date-time"
    },
    "message": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "to": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/member"
          },
          "uniqueItems": true
        },
        "topic": {
          "$ref": "#/$defs/token"
        },
        "summary": {
          "type": "string",
          "minLength": 1,
          "maxLength": 160
        },
        "body": {
          "type": "string",
          "description": "At most 65536 UTF-8 bytes; control-character restrictions are checked by ecc validate."
        },
        "replyto": {
          "$ref": "#/$defs/ulid"
        },
        "thread": {
          "$ref": "#/$defs/ulid"
        },
        "hops": {
          "type": "integer",
          "minimum": 0,
          "maximum": 64
        }
      },
      "required": [
        "summary"
      ],
      "oneOf": [
        {
          "required": [
            "to"
          ],
          "properties": {
            "to": {
              "minItems": 1
            }
          },
          "not": {
            "required": [
              "topic"
            ]
          }
        },
        {
          "required": [
            "topic"
          ],
          "not": {
            "required": [
              "to"
            ]
          }
        }
      ],
      "dependentRequired": {
        "replyto": [
          "thread"
        ]
      },
      "allOf": [
        {
          "if": {
            "required": [
              "hops"
            ],
            "properties": {
              "hops": {
                "minimum": 1
              }
            }
          },
          "then": {
            "required": [
              "thread"
            ]
          }
        }
      ]
    },
    "presence": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "member": {
          "$ref": "#/$defs/member"
        },
        "version": {
          "$ref": "#/$defs/version"
        },
        "pid": {
          "type": "integer"
        },
        "cwd": {
          "$ref": "#/$defs/absolutePath"
        },
        "repo": {
          "$ref": "#/$defs/repoName"
        },
        "branch": {
          "$ref": "#/$defs/branch"
        },
        "state": {
          "enum": [
            "starting",
            "working",
            "idle",
            "needs-input",
            "ended"
          ]
        },
        "title": {
          "type": "string",
          "maxLength": 160
        },
        "tier": {
          "type": "integer",
          "minimum": 0,
          "maximum": 3
        },
        "subscribe": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/token"
          }
        },
        "caps": {
          "type": "array",
          "maxItems": 32,
          "items": {
            "$ref": "#/$defs/token"
          },
          "description": "At most 32 tokens. A receiver keeps the tokens and drops the rest."
        },
        "wakes": {
          "type": "integer"
        },
        "started": {
          "$ref": "#/$defs/timestamp"
        }
      },
      "required": [
        "member",
        "state",
        "tier",
        "started"
      ]
    },
    "hold": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/ulid"
        },
        "member": {
          "$ref": "#/$defs/member"
        },
        "paths": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/absolutePath"
          },
          "minItems": 1,
          "description": "Absolute, clean paths, as placed: a hold is on the resolved path of work inside a repository."
        },
        "mode": {
          "enum": [
            "exclusive",
            "shared"
          ]
        },
        "note": {
          "type": "string"
        },
        "created": {
          "$ref": "#/$defs/timestamp"
        },
        "expires": {
          "$ref": "#/$defs/timestamp"
        }
      },
      "required": [
        "id",
        "member",
        "paths",
        "mode",
        "created",
        "expires"
      ]
    },
    "ack": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "message": {
          "$ref": "#/$defs/ulid"
        }
      },
      "required": [
        "message"
      ]
    },
    "sealed": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "recipient": {
          "$ref": "#/$defs/member"
        },
        "certid": {
          "$ref": "#/$defs/ulid"
        },
        "ciphertext": {
          "type": "string",
          "minLength": 64,
          "maxLength": 262144
        }
      },
      "required": [
        "recipient",
        "certid",
        "ciphertext"
      ]
    },
    "absolutePath": {
      "description": "A clean absolute path from any platform: a Unix path, a drive path or a UNC path, with no empty, . or .. segment and no trailing separator. Checked lexically, since it is usually a path on another host.",
      "type": "string",
      "maxLength": 4096,
      "pattern": "^(?:[A-Za-z]:[\\\\/]|[\\\\/]{1,2})(?:(?:[^/\\\\.][^/\\\\]*|\\.[^/\\\\.][^/\\\\]*|\\.\\.[^/\\\\]+)(?:[\\\\/](?:[^/\\\\.][^/\\\\]*|\\.[^/\\\\.][^/\\\\]*|\\.\\.[^/\\\\]+))*)?$"
    },
    "version": {
      "description": "A release version (vMAJOR.MINOR.PATCH) or a development build: dev, or a git revision, optionally marked -dirty. A receiver clears a bad presence value and keeps the member. A receiver clears a bad presence value and keeps the member. A receiver clears a bad presence value and keeps the member.",
      "type": "string",
      "pattern": "^(?:v(?:0|[1-9][0-9]{0,9})\\.(?:0|[1-9][0-9]{0,9})\\.(?:0|[1-9][0-9]{0,9})|(?:dev|[0-9a-f]{7,40})(?:-dirty)?)$"
    },
    "repoName": {
      "description": "A repository's directory name: one path component, a leading dot allowed, not . or .., with no control, escape or invisible format character. A receiver clears a bad value and keeps the member.",
      "type": "string",
      "minLength": 1,
      "maxLength": 255,
      "pattern": "^[^/\\\\\\x00-\\x1f\\x7f-\\x9f]+$"
    },
    "branch": {
      "description": "A branch name by the rules of git check-ref-format --branch: no space, ~ ^ : ? * [ \\ or control character; no component starting with . or ending in .lock; no .., @{ or //; not @ alone; no leading -, no leading or trailing /, no trailing dot. Other Unicode is allowed. A receiver clears a bad value and keeps the member.",
      "type": "string",
      "minLength": 1,
      "maxLength": 255,
      "pattern": "^[^-/\\x00-\\x20\\x7f~^:?*\\[\\\\][^\\x00-\\x20\\x7f~^:?*\\[\\\\]*$"
    }
  },
  "dependentRequired": {
    "tracestate": [
      "traceparent"
    ]
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "type": {
            "const": "cc.echo.message.v1"
          }
        }
      },
      "then": {
        "properties": {
          "data": {
            "$ref": "#/$defs/message"
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "cc.echo.presence.v1"
          }
        }
      },
      "then": {
        "properties": {
          "data": {
            "$ref": "#/$defs/presence"
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "cc.echo.ack.v1"
          }
        }
      },
      "then": {
        "properties": {
          "data": {
            "$ref": "#/$defs/ack"
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "cc.echo.hold.v1"
          }
        }
      },
      "then": {
        "properties": {
          "data": {
            "$ref": "#/$defs/hold"
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "cc.echo.sealed.v1"
          }
        }
      },
      "then": {
        "properties": {
          "data": {
            "$ref": "#/$defs/sealed"
          }
        }
      }
    }
  ]
}
