{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/gunspec-io/json-schema/gunspec-io-api-contract-run-schema.json",
  "title": "ApiContractRun",
  "description": "One run of the contract suite: every documented operation called against this API and held against this document. Produced by `scripts/smoke/` in CI. Uptime is a separate question and a separate system - this says whether what the API answers is still what the reference promises.",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/gunspec-io-openapi.json#/components/schemas/ApiContractRun",
  "type": "object",
  "properties": {
    "surface": {
      "type": "string",
      "enum": [
        "api",
        "mcp",
        "fitment",
        "catalogue",
        "integrity",
        "plausibility",
        "anomalies",
        "cartridges",
        "materials",
        "corroboration",
        "sources",
        "manufacturers",
        "links"
      ],
      "description": "What the run measured: `api` for this REST API, `mcp` for the hosted MCP server, `fitment` for the attachment compatibility suite, `catalogue` for the catalogue record checks: every file against its schema, no company under two ids, and no firearm whose fields cannot all be true, `integrity` for the integrity checks: figures, prose and feature lists copied between unrelated guns, citations that are not evidence, timelines, weight against size, and confidence against evidence, `plausibility` for the plausibility checks: a loaded weight against the empty one and its magazine, a gun older than its cartridge, figures in the wrong unit, energy against velocity, variants that disagree with their family, and the same gun filed twice, `anomalies` for the statistical anomaly checks: a figure unusually far from its family's or its cohort's median, and a barrel an unusual share of its gun's length; each a figure to verify, not one shown wrong, `cartridges` for the cartridge checks: energy against bullet and velocity, pressure in MPa against psi, dimensions no case can have, a bullet against the calibre its name states, a cartridge older than its parent, and each load against its cartridge, `materials` for the materials registry: every material's density quoted from a page that still states it, within the range it declares, and every material value the catalogue uses covered, so the mass checks read sourced densities, `corroboration` for the corroboration checks: firearm figures compared with a second, independent source (Wikidata), `sources` for the source checks: how many records cite their own maker or another authority, and which heavily cited sites the source hierarchy cannot yet name, `manufacturers` for the manufacturer checks: every manufacturer record held against Wikidata for its website, founding year, country and whether it still trades, `links` for the link checks: every manufacturer website and every cited source asked whether it still answers and is still the page cited."
    },
    "env": {
      "type": "string",
      "enum": [
        "production",
        "staging",
        "local"
      ],
      "description": "Which environment was measured: `production`, `staging` or `local`. A staging run is never mixed into production, because a report read as evidence about the wrong environment is worse than no report."
    },
    "specVersion": {
      "type": [
        "string",
        "null"
      ],
      "description": "The version of this document the run was measured against, so a result names the contract it was true of."
    },
    "commitSha": {
      "type": [
        "string",
        "null"
      ],
      "description": "The commit of our repository the run started from, or null where CI did not report one."
    },
    "runUrl": {
      "type": [
        "string",
        "null"
      ],
      "description": "The CI run, where it is reachable."
    },
    "ranAt": {
      "type": "string",
      "description": "When the run finished, ISO 8601."
    },
    "durationMs": {
      "type": "integer",
      "description": "Wall-clock time the whole sweep took, in milliseconds."
    },
    "operations": {
      "type": "integer",
      "description": "How many documented operations the run covered."
    },
    "passed": {
      "type": "integer",
      "description": "Operations where every check that ran passed."
    },
    "failed": {
      "type": "integer",
      "description": "Operations where at least one check failed. One endpoint failing three ways is one unhealthy endpoint, not three."
    },
    "skipped": {
      "type": "integer",
      "description": "Operations nothing could be asked about: no key for that gate, a write the suite will not send against a live host, or a fixture it could not resolve. Never folded into `passed`."
    },
    "tiers": {
      "type": "array",
      "description": "The plans the run held a key for. The tier checks can only cover a gate the run could call, so this is what separates \"every gate was proven\" from \"we had one key\".",
      "items": {
        "type": "string"
      }
    },
    "results": {
      "type": "array",
      "description": "One entry per documented operation.",
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The operation, as method and path: `get /v1/firearms/{id}`."
          },
          "method": {
            "type": "string",
            "description": "HTTP method, lower case, as the operation key spells it."
          },
          "path": {
            "type": "string",
            "description": "The documented path, with its parameters still in braces."
          },
          "tag": {
            "type": [
              "string",
              "null"
            ],
            "description": "The OpenAPI tag the operation belongs to, which is how the report groups."
          },
          "tier": {
            "type": [
              "string",
              "null"
            ],
            "description": "The plan the document declares for it, which is what the access verdicts are judged against."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Production status, one of `in_production`, `discontinued`, `out_of_production`, `in_service`, `limited_production`, `prototype`. Discontinued records are kept deliberately: a rifle built in 2004 wears parts nobody sells today."
          },
          "ms": {
            "type": "integer",
            "description": "How long that operation took, in milliseconds."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "skip"
            ],
            "description": "`pass`, `fail` or `skip` for the operation as a whole."
          },
          "checks": {
            "type": "object",
            "properties": {
              "passed": {
                "type": "integer",
                "description": "Operations where every check that ran passed."
              },
              "failed": {
                "type": "integer",
                "description": "Operations where at least one check failed. One endpoint failing three ways is one unhealthy endpoint, not three."
              },
              "skipped": {
                "type": "integer",
                "description": "Operations nothing could be asked about: no key for that gate, a write the suite will not send against a live host, or a fixture it could not resolve. Never folded into `passed`."
              }
            },
            "description": "How many checks passed, failed and were skipped for this operation."
          },
          "describes": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the operation asks, when its key does not say. A fitment scenario states its question and the checks behind it; an API operation carries none, its path being its name."
          },
          "failures": {
            "type": "array",
            "description": "Only the checks that failed. A run is about 1,500 checks, and the passing ones are the counts above.",
            "items": {
              "type": "object",
              "properties": {
                "check": {
                  "type": "string",
                  "description": "Which check this entry is about, e.g. `schema`, `tier-refuses`."
                },
                "detail": {
                  "type": "string",
                  "description": "What the check found, in the words a person needs to act on it."
                }
              }
            }
          },
          "access": {
            "type": "array",
            "description": "What happened when the plan gates were exercised: a keyless call, the documented plan, and the plan immediately below it.",
            "items": {
              "type": "object",
              "properties": {
                "check": {
                  "type": "string",
                  "enum": [
                    "gate",
                    "tier-admits",
                    "tier-refuses",
                    "tier-shape"
                  ],
                  "description": "Which check this entry is about, e.g. `schema`, `tier-refuses`."
                },
                "tier": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The plan the document declares for it, which is what the access verdicts are judged against."
                },
                "verdict": {
                  "type": "string",
                  "enum": [
                    "pass",
                    "fail",
                    "skip"
                  ],
                  "description": "`pass`, `fail` or `skip` for the operation as a whole."
                },
                "detail": {
                  "type": "string",
                  "description": "What the check found, in the words a person needs to act on it."
                }
              }
            }
          }
        },
        "required": [
          "key",
          "method",
          "path",
          "verdict",
          "checks",
          "failures",
          "access"
        ]
      }
    },
    "systemChecks": {
      "type": "array",
      "description": "The same run reduced to questions about the API as a whole rather than about one endpoint: whether any response carries JSON inside a string, whether every gate still refuses a keyless call, whether every ETag revalidates. A rollup of the results above, so the two cannot disagree; `found` counts the operations that failed the check and `sample` names a few of them.",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The system check, as a stable kebab-case id: `json-not-stringified`, `allowance-matches-the-plan`. Never renamed, because the docs name it in prose and a reader may have linked to it."
          },
          "describes": {
            "type": "string",
            "description": "What passing means, in a sentence."
          },
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "skip"
            ],
            "description": "`skip` where the run never gathered the evidence, which is not the same as passing."
          },
          "found": {
            "type": "integer",
            "description": "How many operations failed the check."
          },
          "checked": {
            "type": "integer",
            "description": "How many the run could ask it of. A pass over two operations and a pass over 127 both report zero failures, so this is what separates them."
          },
          "sample": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "A few of the operations that failed it."
          }
        },
        "required": [
          "id",
          "describes",
          "status",
          "found",
          "checked",
          "sample"
        ]
      }
    }
  },
  "required": [
    "env",
    "ranAt",
    "operations",
    "passed",
    "failed",
    "skipped",
    "tiers",
    "results",
    "systemChecks"
  ]
}