{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:bitunlock:schema:product-event:v1",
  "title": "BitUnlock signed product event v1",
  "description": "A signed Nostr kind 30078 event bound to seller-unlock-service-v1. Signature verification and exact service-binding checks are semantic validation performed in addition to this structural schema. Event content is owned by the storefront and opaque to BitUnlock.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "pubkey",
    "created_at",
    "kind",
    "tags",
    "content",
    "sig"
  ],
  "properties": {
    "id": {
      "$ref": "#/$defs/hex64"
    },
    "pubkey": {
      "$ref": "#/$defs/hex64"
    },
    "created_at": {
      "type": "integer",
      "minimum": 1
    },
    "kind": {
      "const": 30078
    },
    "content": {
      "type": "string",
      "description": "Storefront-owned public metadata. BitUnlock does not parse or assign semantics to this field. It must not contain delivery plaintext or secrets."
    },
    "sig": {
      "type": "string",
      "pattern": "^[0-9a-f]{128}$"
    },
    "tags": {
      "type": "array",
      "minItems": 9,
      "maxItems": 128,
      "items": {
        "$ref": "#/$defs/tag"
      },
      "allOf": [
        {
          "$ref": "#/$defs/dTag"
        },
        {
          "$ref": "#/$defs/priceTag"
        },
        {
          "$ref": "#/$defs/statusTag"
        },
        {
          "$ref": "#/$defs/deliveryTag"
        },
        {
          "$ref": "#/$defs/deliveryModeTag"
        },
        {
          "$ref": "#/$defs/serviceTag"
        },
        {
          "$ref": "#/$defs/servicePubkeyTag"
        },
        {
          "$ref": "#/$defs/policyTag"
        },
        {
          "$ref": "#/$defs/claimEpochTag"
        }
      ]
    }
  },
  "$defs": {
    "hex64": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$"
    },
    "tag": {
      "type": "array",
      "minItems": 2,
      "maxItems": 8,
      "items": {
        "type": "string",
        "maxLength": 2048
      }
    },
    "dTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "d"
          },
          {
            "type": "string",
            "pattern": "^[^:\\r\\n][^\\r\\n]{0,255}$"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "priceTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "price"
          },
          {
            "type": "string",
            "pattern": "^[1-9][0-9]*$"
          },
          {
            "const": "SAT"
          }
        ],
        "items": false,
        "minItems": 3,
        "maxItems": 3
      },
      "minContains": 1,
      "maxContains": 1
    },
    "statusTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "status"
          },
          {
            "const": "active"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "deliveryTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "br:delivery"
          },
          {
            "const": "seller-unlock-service-v1"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "deliveryModeTag": {
      "description": "'static' or 'execution' (BITUNLOCK_UPGRADE_PLAN.md Milestone 2). Additive alongside the unchanged br:delivery protocol-version tag -- lets a client tell from the public event alone whether a product is a fixed payload or a provider-backed compute product, without a second protocol version.",
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "br:delivery-mode"
          },
          {
            "enum": [
              "static",
              "execution"
            ]
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "serviceTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "br:unlock-service"
          },
          {
            "type": "string",
            "pattern": "^https://"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "servicePubkeyTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "br:unlock-service-pubkey"
          },
          {
            "$ref": "#/$defs/hex64"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "policyTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "br:unlock-policy"
          },
          {
            "type": "string",
            "pattern": "^30078:[0-9a-f]{64}:bitunlock:service-policy:v1$"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    },
    "claimEpochTag": {
      "type": "array",
      "contains": {
        "type": "array",
        "prefixItems": [
          {
            "const": "br:claim-epoch"
          },
          {
            "type": "string",
            "pattern": "^([1-9][0-9]{0,14}|[0-9]{4}-(0[1-9]|1[0-2]))$"
          }
        ],
        "items": false,
        "minItems": 2,
        "maxItems": 2
      },
      "minContains": 1,
      "maxContains": 1
    }
  }
}
