{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://penny-surface-contract.penny-mcamp.workers.dev/spec/surface-contract.schema.json",
  "title": "Surface Contract",
  "contractSpecVersion": "0.2",
  "description": "A machine-readable description of the operational behaviour and limitations of one platform surface: what an integrating agent can rely on, what breaks, what is unknown, and what the platform does not offer. Every claim cites evidence and declares whether an independent checker can reproduce it. The contract describes the surface, never the quality of the seller behind it.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "contractVersion",
    "id",
    "host",
    "title",
    "updatedAt",
    "provenance",
    "evidence",
    "claims"
  ],
  "properties": {
    "contractVersion": {
      "const": "0.1"
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9._-]*$",
      "description": "Stable slug used in URLs, e.g. moltbook"
    },
    "host": {
      "type": "string",
      "minLength": 3,
      "description": "The host whose surface this describes, e.g. www.moltbook.com"
    },
    "title": {
      "type": "string",
      "minLength": 3
    },
    "updatedAt": {
      "type": "string",
      "description": "When this revision of the contract was written (ISO 8601)"
    },
    "maintainer": {
      "type": "string"
    },
    "license": {
      "type": "string",
      "description": "Defaults to CC0-1.0: contracts are facts about behaviour, free to copy and embed"
    },
    "summary": {
      "type": "string"
    },
    "provenance": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "method",
        "limits"
      ],
      "properties": {
        "method": {
          "type": "string",
          "description": "How the claims below were established (e.g. own brokered HTTP exchanges, captured responses)"
        },
        "limits": {
          "type": "string",
          "description": "What this contract does NOT establish. Mandatory, because an over-claimed contract is worse than none"
        }
      }
    },
    "evidence": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/evidence"
      }
    },
    "claims": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/claim"
      }
    },
    "corrections": {
      "type": "array",
      "description": "Revision log. A published claim is never silently rewritten: when a claim's status, statement or scope changes, an entry here names the claim, what changed and the evidence that changed it. Added in contractSpecVersion 0.2; additive, so a 0.1 contract validates unchanged.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "at",
          "claimId",
          "change",
          "evidenceIds"
        ],
        "properties": {
          "at": {
            "type": "string",
            "description": "ISO 8601 time of the change"
          },
          "claimId": {
            "type": "string"
          },
          "change": {
            "type": "string",
            "description": "What changed, and from what to what"
          },
          "evidenceIds": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  },
  "$defs": {
    "evidence": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "kind",
        "observedAt",
        "authority",
        "ref"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "kind": {
          "enum": [
            "our-capture",
            "our-measurement",
            "partner-measurement",
            "thirdparty-statement",
            "platform-documentation"
          ],
          "description": "Authority class. thirdparty-statement is never treated as verified."
        },
        "observedAt": {
          "type": "string"
        },
        "authority": {
          "type": "string",
          "description": "Whose response or statement this is, in one sentence"
        },
        "ref": {
          "type": "string",
          "description": "Stable pointer: our source id, observation id, friction id, or a public URL"
        },
        "url": {
          "type": "string"
        },
        "quote": {
          "type": "string"
        }
      }
    },
    "claim": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "area",
        "statement",
        "status",
        "evidenceIds",
        "check"
      ],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$"
        },
        "area": {
          "enum": [
            "auth",
            "read",
            "write",
            "pending_state",
            "state_change",
            "pagination",
            "idempotency",
            "rate_limit",
            "limits",
            "portability",
            "discovery"
          ]
        },
        "statement": {
          "type": "string",
          "minLength": 10
        },
        "status": {
          "enum": [
            "guarantee",
            "failure",
            "unknown",
            "unsupported"
          ],
          "description": "guarantee: observed behaviour an integrator can rely on. failure: a documented way the surface refuses or breaks. unknown: not established. unsupported: the surface does not offer it."
        },
        "evidenceIds": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string"
          }
        },
        "check": {
          "$ref": "#/$defs/check"
        },
        "costObserved": {
          "type": "object",
          "description": "What this behaviour cost an integrator in our own measurements. Optional; null fields are unknown, never zero.",
          "additionalProperties": true,
          "properties": {
            "actionsPerSuccess": {
              "type": [
                "integer",
                "null"
              ]
            },
            "refusedAttempts": {
              "type": [
                "integer",
                "null"
              ]
            },
            "note": {
              "type": "string"
            }
          },
          "required": [
            "note"
          ]
        }
      }
    },
    "check": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "http",
            "manual",
            "none"
          ],
          "description": "http: an independent checker reproduces this claim with an unauthenticated request. manual: reproducible only with a credential or a human step, so the public checker reports it as manual rather than verified. none: not mechanically checkable."
        },
        "request": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "method": {
              "enum": [
                "GET",
                "HEAD"
              ]
            },
            "url": {
              "type": "string"
            },
            "headers": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          },
          "required": [
            "method",
            "url"
          ]
        },
        "expect": {
          "$ref": "#/$defs/expect"
        },
        "note": {
          "type": "string"
        },
        "procedure": {
          "type": "string",
          "description": "For manual checks: the exact steps and what a pass looks like"
        }
      }
    },
    "expect": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "anyOf": {
          "type": "array",
          "minItems": 2,
          "description": "Two or more acceptable observed shapes. The outer expectations must hold AND at least one branch must match; a claim that has more than one legitimate shape must not be reported as failed because the checker modelled only one.",
          "items": {
            "$ref": "#/$defs/expect"
          }
        },
        "status": {
          "type": "integer"
        },
        "statusClass": {
          "type": "string",
          "pattern": "^[1-5]xx$"
        },
        "headersPresent": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "json": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "path"
            ],
            "properties": {
              "path": {
                "type": "string",
                "description": "Dotted path with array indexes, e.g. comments.0.verification_status"
              },
              "type": {
                "enum": [
                  "string",
                  "number",
                  "boolean",
                  "array",
                  "object"
                ]
              },
              "equals": {},
              "oneOf": {
                "type": "array"
              }
            }
          }
        }
      }
    },
    "contribution": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "contractId",
        "kind",
        "statement",
        "evidence"
      ],
      "properties": {
        "contractId": {
          "type": "string"
        },
        "claimId": {
          "type": "string"
        },
        "kind": {
          "enum": [
            "discrepancy",
            "new-claim",
            "independent-confirmation"
          ]
        },
        "statement": {
          "type": "string",
          "minLength": 10
        },
        "reporter": {
          "type": "string",
          "description": "Who is reporting; an agent name or a URL. Optional, and never verified by us."
        },
        "observedAt": {
          "type": "string"
        },
        "evidence": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "enum": [
                  "http-exchange",
                  "url",
                  "quote",
                  "screenshot",
                  "other"
                ]
              },
              "url": {
                "type": "string"
              },
              "status": {
                "type": "integer"
              },
              "body": {
                "type": "string"
              },
              "quote": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}