{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/Nikodem01/printwright-x402/main/public/pwc-1.schema.json",
  "title": "PWC-1 Printwright License Proof Bundle",
  "description": "Version 1 of the Printwright license certificate contract. Hedera carries only an opaque commitment (see $defs/commitment_envelope); the certificate itself is held off-chain and disclosed by its holder as the proof bundle described here. A verifier recomputes SHA-256( DOMAIN || blinding_nonce || JCS(certificate) ) and requires it to equal the commitment anchored at hedera.mirror_url. DOMAIN is the fixed byte string \"printwright:license-certificate:v1\\u0000\"; JCS is RFC 8785 canonical JSON. The ledger proves the committed bytes existed no later than the consensus timestamp — not that the assertions inside them are true.",
  "type": "object",
  "additionalProperties": false,
  "required": [ "proof_version", "algorithm", "certificate", "blinding_nonce", "commitment", "terms", "hedera" ],
  "properties": {
    "proof_version": { "const": 1 },
    "algorithm": {
      "const": "sha256-jcs-v1",
      "description": "SHA-256 over the domain-separated, RFC 8785-canonicalized preimage."
    },
    "certificate": { "$ref": "#/$defs/certificate" },
    "blinding_nonce": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "32 random bytes, hex-encoded. Makes the commitment unguessable even for a certificate whose fields an outsider could otherwise guess."
    },
    "commitment": { "$ref": "#/$defs/commitment" },
    "terms": { "$ref": "#/$defs/terms" },
    "hedera": { "$ref": "#/$defs/hedera" }
  },
  "$defs": {
    "commitment": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "SHA-256( DOMAIN || blinding_nonce_bytes || JCS(certificate) ), hex-encoded."
    },
    "commitment_envelope": {
      "title": "PWC-1 on-chain message",
      "description": "The entire payload of one Hedera Consensus Service message: opaque by construction, well under the 1024-byte single-chunk limit, and carrying no model, designer, buyer, or count that a topic scraper could read.",
      "type": "object",
      "additionalProperties": false,
      "required": [ "type", "version", "algorithm", "commitment" ],
      "properties": {
        "type": { "const": "printwright-license-commitment" },
        "version": { "const": 1 },
        "algorithm": { "const": "sha256-jcs-v1" },
        "commitment": { "$ref": "#/$defs/commitment" }
      }
    },
    "certificate": {
      "title": "PWC-1 license certificate",
      "description": "The committed facts. Never published by Printwright — disclosed only by the holder, inside a proof bundle.",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "v", "cert_id", "model_id", "model_hash", "designer", "license_type",
        "unit_serial", "buyer_hint", "payment_tx", "issued_at", "terms_hash"
      ],
      "properties": {
        "v": { "const": 1 },
        "cert_id": {
          "type": "string",
          "pattern": "^(sandbox-)?pw-[0-9a-f]{16,}$",
          "description": "Random and unguessable. Sequential ids would let anyone walk the reveal endpoints and undo the commitment."
        },
        "model_id": { "type": "integer", "minimum": 1 },
        "model_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
        "designer": {
          "type": "integer",
          "minimum": 1,
          "description": "A stable studio id, never a payout wallet: the licensor identity must not change when a designer rotates the account they are paid to, and the payout account is private."
        },
        "license_type": { "type": "string", "enum": [ "personal", "commercial_unit" ] },
        "unit_serial": { "type": "integer", "minimum": 1 },
        "buyer_hint": {
          "type": "string",
          "pattern": "^(bearer|sandbox-buyer|[0-9]+\\.[0-9]+\\.[0-9]+)$",
          "description": "A non-secret payer hint, not proof of current ownership. \"bearer\" for account-less agent buyers."
        },
        "payment_tx": {
          "type": "string",
          "pattern": "^(sandbox-tx-[0-9a-f]+|[0-9]+\\.[0-9]+\\.[0-9]+@[0-9]+\\.[0-9]{1,9})$"
        },
        "issued_at": {
          "type": "string",
          "format": "date-time",
          "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?Z$"
        },
        "terms_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
      }
    },
    "terms": {
      "description": "The exact licensed text, so a holder can prove which terms they bought under without fetching them from Printwright.",
      "type": "object",
      "additionalProperties": false,
      "required": [ "version", "kind", "hash", "text" ],
      "properties": {
        "version": { "type": "string" },
        "kind": { "type": "string", "enum": [ "personal", "commercial_unit" ] },
        "hash": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$",
          "description": "SHA-256 of text, and equal to certificate.terms_hash."
        },
        "text": { "type": "string" }
      }
    },
    "hedera": {
      "description": "Where the commitment is anchored. Delivery can precede the anchor, in which case status is \"minting\" and the bundle is re-fetched later to pick up the coordinates.",
      "oneOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "required": [ "status" ],
          "properties": { "status": { "const": "minting" } }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [ "network", "topic_id", "sequence_number", "mirror_url" ],
          "properties": {
            "network": { "type": "string", "enum": [ "testnet", "mainnet", "sandbox" ] },
            "sandbox": {
              "const": true,
              "description": "Present only for sandbox rehearsals, which are anchored to a local topic and settle no money."
            },
            "topic_id": { "type": "string" },
            "sequence_number": { "type": "integer", "minimum": 1 },
            "mirror_url": {
              "type": "string",
              "description": "The single mirror-node message holding the commitment envelope."
            }
          }
        }
      ]
    }
  }
}
