{
  "openapi": "3.1.0",
  "info": {
    "title": "ShockAI Model API: provider interface",
    "version": "0.1.8-preview",
    "summary": "The interface a blast-modelling provider implements so the ShockAI platform can send it cases and receive its answers: predictions of what a blast will do, and diagnoses of what a fired blast did.",
    "description": "Partner preview. The platform calls these endpoints on the provider, and every request is signed with the platform's Ed25519 key in `ShockAI-Signature`; every callback back is signed with the provider's own key, declared in its manifest. No secret is shared and no `Authorization` header is sent. A model is sent only the parts of a case its manifest declares, so it never receives a value it did not ask for. The platform refuses a response body over 16 MiB and follows no redirect. Fields may be added within v0; readers must ignore fields they do not know. Breaking changes will come with a new major version and a deprecation window.",
    "contact": {
      "name": "ShockAI partnerships",
      "url": "https://app.shockai.co/developers/partners"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://{providerHost}/shockai/v0",
      "description": "Your service. The base path is yours to choose; it is registered at onboarding.",
      "variables": {
        "providerHost": {
          "default": "models.example.com"
        }
      }
    }
  ],
  "security": [
    {
      "shockaiSignature": []
    }
  ],
  "tags": [
    {
      "name": "Discovery",
      "description": "What the model is and what it needs."
    },
    {
      "name": "Runs",
      "description": "Sending a case and getting a result."
    },
    {
      "name": "Pricing",
      "description": "Binding prices for models charged by quote."
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "getHealth",
        "summary": "Say the service is up",
        "description": "Optional and unsigned: the only endpoint that is. The platform may call it before a signed request, to wake a host that sleeps when idle, and ignores the body. Answer 200 without reading anything a case holds.",
        "tags": [
          "Discovery"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Up."
          }
        }
      }
    },
    "/manifest": {
      "get": {
        "operationId": "getManifest",
        "summary": "Describe the model",
        "description": "Returns the model manifest. The platform reads it at registration and whenever the provider announces a new version; a manifest change never alters a run already made, because every result is pinned to the version that produced it.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "The manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelManifest"
                },
                "examples": {
                  "default": {
                    "$ref": "#/components/examples/manifest"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The signature is missing, expired or wrong (`authentication`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "The model failed (`provider_error`). The platform retries with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/quote": {
      "post": {
        "operationId": "createQuote",
        "summary": "Price a case",
        "description": "For models priced by `quote`. Returns a binding price for the case; the platform shows it to the user and, on acceptance, passes the quote id with the run. Quoting must not start work.",
        "tags": [
          "Pricing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlastCase"
              },
              "examples": {
                "default": {
                  "$ref": "#/components/examples/blastCase"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed or the case fails validation (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The signature is missing, expired or wrong (`authentication`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "The case is valid but the model cannot run it (`unsupported_case`), or cannot honour its consent terms (`consent_refused`). Final: the platform will not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (`rate_limited`). Send `Retry-After`; the platform backs off accordingly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "The model failed (`provider_error`). The platform retries with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/runs": {
      "post": {
        "operationId": "createRun",
        "summary": "Run the model on a case",
        "description": "Sync models answer within 30 seconds with the run `succeeded` and its result. Async models answer `202` with the run `queued`, then report through the `run.*` webhooks. The case contains only the inputs the manifest declares. The platform sends an `Idempotency-Key`; a repeated key must return the original run, not start another.",
        "tags": [
          "Runs"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Stable across retries of the same run request."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sync: the finished run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                },
                "examples": {
                  "default": {
                    "$ref": "#/components/examples/run"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Async: the accepted run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed or the case fails validation (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "The signature is missing, expired or wrong (`authentication`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "The case is valid but the model cannot run it (`unsupported_case`), or cannot honour its consent terms (`consent_refused`). Final: the platform will not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (`rate_limited`). Send `Retry-After`; the platform backs off accordingly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "The model failed (`provider_error`). The platform retries with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "callbacks": {
          "runEvent": {
            "{$request.body#/callbackUrl}": {
              "post": {
                "summary": "Run status changed",
                "description": "Signed with the same scheme, with your key: one of the `signing.callbackKeys` your manifest declares, over the callback URL exactly as given. Sign each delivery afresh, so a retry carries a current timestamp. Deliver at least once: retry with backoff until a 2xx, stop on a 4xx other than 408 or 429, and after a restart send again what was not delivered, under the same event id. The platform de-duplicates on the event id, and polls a run whose callback does not come. The callback URL is the run's own, so keep it private.",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/WebhookEvent"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Received."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/runs/{runId}": {
      "get": {
        "operationId": "getRun",
        "summary": "Get a run",
        "description": "The platform polls this as a fallback when a webhook is late. Results must stay retrievable for at least 24 hours after the run finishes.",
        "tags": [
          "Runs"
        ],
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The id the provider assigned in `POST /runs`."
          }
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "401": {
            "description": "The signature is missing, expired or wrong (`authentication`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "The model failed (`provider_error`). The platform retries with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelRun",
        "summary": "Cancel a run",
        "description": "Best effort. A run that has already finished is returned unchanged. A cancelled run is not charged.",
        "tags": [
          "Runs"
        ],
        "parameters": [
          {
            "name": "runId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The id the provider assigned in `POST /runs`."
          }
        ],
        "responses": {
          "200": {
            "description": "The run, `cancelled` or already final.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "401": {
            "description": "The signature is missing, expired or wrong (`authentication`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No such run.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "The model failed (`provider_error`). The platform retries with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Airblast": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "airblast"
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "peakPa": {
            "description": "Peak overpressure, Pa. dB(L) = 20·log10(Pa / 20 µPa).",
            "$ref": "#/components/schemas/Estimate"
          },
          "peakDbL": {
            "description": "The same peak in dB(L), as the model computed it, so a limit in decibels is read without a round trip.",
            "$ref": "#/components/schemas/Estimate"
          },
          "scaledDistance": {
            "description": "The cube-root scaled distance the prediction was made at.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "receiverId",
          "peakPa",
          "validity"
        ],
        "description": "Predicted peak overpressure at a receiver."
      },
      "AirblastLaw": {
        "type": "object",
        "properties": {
          "scaling": {
            "type": "string",
            "const": "cube_root"
          },
          "kPa": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Site constant in P = K·SD^−β, pascals."
          },
          "beta": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Attenuation exponent β."
          },
          "sigmaLn": {
            "type": "number",
            "minimum": 0,
            "description": "Residual standard deviation in ln(Pa)."
          },
          "n": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Readings in the fit."
          },
          "fit": {
            "$ref": "#/components/schemas/LawFitStatistics"
          },
          "scaledDistance": {
            "description": "The scaled-distance range the fit was made over.",
            "$ref": "#/components/schemas/Range"
          },
          "source": {
            "type": "string",
            "enum": [
              "platform_fit",
              "user",
              "prior"
            ]
          }
        },
        "required": [
          "scaling",
          "kPa",
          "beta",
          "sigmaLn",
          "n",
          "fit",
          "scaledDistance",
          "source"
        ],
        "description": "A fitted airblast attenuation law on cube-root scaled distance."
      },
      "ApiError": {
        "$ref": "#/components/schemas/ApiError"
      },
      "AsBuiltHole": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "The design hole this record amends."
          },
          "collar": {
            "$ref": "#/components/schemas/Point3"
          },
          "toe": {
            "$ref": "#/components/schemas/Point3"
          },
          "waterDepthM": {
            "description": "Standing water measured at loading, m.",
            "type": "number",
            "minimum": 0
          },
          "decks": {
            "description": "The hole as loaded, when it differs from the design.",
            "minItems": 1,
            "maxItems": 32,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deck"
            }
          }
        },
        "required": [
          "id"
        ],
        "description": "A hole as drilled and loaded. Fields left out are as designed."
      },
      "BackbreakReading": {
        "type": "object",
        "properties": {
          "distanceM": {
            "type": "number",
            "minimum": 0,
            "description": "How far behind the holes the final wall broke, m. A model judging damage reads it as a radius about each hole’s charge, square to its axis: for vertical holes, the distance in plan from the line of their collars to the broken crest."
          },
          "holeIds": {
            "minItems": 1,
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
            },
            "description": "The holes it was measured behind: the back row for one reading of the blast, or one hole for a reading per hole."
          },
          "method": {
            "description": "How it was measured: taped, surveyed, read from a scan, or estimated by eye.",
            "type": "string",
            "enum": [
              "tape",
              "survey",
              "scan",
              "estimate"
            ]
          },
          "note": {
            "description": "Anything a reader should know.",
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "distanceM",
          "holeIds"
        ],
        "description": "How far behind some of its holes a fired blast broke the wall it left: the damage a model judging it can be fitted to."
      },
      "BlastCase": {
        "$ref": "#/components/schemas/BlastCase"
      },
      "BlobRef": {
        "type": "object",
        "properties": {
          "uri": {
            "type": "string",
            "format": "uri",
            "description": "A signed HTTPS URL. It expires; fetch it once when the run starts."
          },
          "sha256": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "mediaType": {
            "type": "string",
            "minLength": 3,
            "maxLength": 120,
            "description": "IANA media type, for example `image/tiff` or `application/vnd.las`."
          },
          "bytes": {
            "description": "Size in bytes.",
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "uri",
          "sha256",
          "mediaType"
        ],
        "description": "A large payload passed by reference rather than inline: terrain, point clouds, long waveforms."
      },
      "Calibration": {
        "type": "object",
        "properties": {
          "siteLaw": {
            "$ref": "#/components/schemas/SiteLaw"
          },
          "airblastLaw": {
            "$ref": "#/components/schemas/AirblastLaw"
          },
          "seedWaveforms": {
            "maxItems": 500,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SeedWaveform"
            }
          },
          "vod": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VodMeasurement"
            }
          },
          "outcomes": {
            "maxItems": 10000,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MeasuredOutcome"
            }
          },
          "strainAmplitude": {
            "$ref": "#/components/schemas/StrainAmplitude"
          }
        },
        "description": "What the site has measured. This is what lets a kinematic model be calibrated rather than assumed."
      },
      "CancelledRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "modelId": {
            "$ref": "#/components/schemas/ModelId"
          },
          "modelVersion": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
            "description": "A semantic version. A result is always pinned to the exact version that produced it."
          },
          "caseId": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "status": {
            "type": "string",
            "const": "cancelled"
          }
        },
        "required": [
          "id",
          "modelId",
          "modelVersion",
          "caseId",
          "createdAt",
          "updatedAt",
          "status"
        ],
        "description": "Stopped before it finished, by the platform or the provider."
      },
      "Capability": {
        "type": "object",
        "properties": {
          "output": {
            "$ref": "#/components/schemas/OutputKind"
          },
          "inputs": {
            "type": "object",
            "properties": {
              "required": {
                "minItems": 1,
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InputPath"
                }
              },
              "requiredAnyOf": {
                "default": [],
                "description": "Inputs that stand in for one another: the model needs at least one from each group, for example the charge, from the stated MIC or from the holes. A case carrying none of a group is declined with `missing_input`, as for a missing required input.",
                "type": "array",
                "items": {
                  "minItems": 2,
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/InputPath"
                  }
                }
              },
              "optional": {
                "default": [],
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InputPath"
                }
              }
            },
            "required": [
              "required"
            ]
          },
          "validity": {
            "description": "The domain the model is validated on. Outputs outside it must be marked `outside` or declined.",
            "type": "object",
            "properties": {
              "distanceM": {
                "$ref": "#/components/schemas/Range"
              },
              "scaledDistance": {
                "$ref": "#/components/schemas/Range"
              },
              "rockClasses": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RockClass"
                }
              },
              "notes": {
                "type": "string",
                "maxLength": 1000
              }
            }
          }
        },
        "required": [
          "output",
          "inputs"
        ],
        "description": "One kind of output a model produces, and what it needs to produce it."
      },
      "CaseUnavailable": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "ppv_field",
              "damage_envelope",
              "strain_field",
              "flyrock_zone",
              "fragmentation_curve",
              "movement_field",
              "timing_proposal",
              "hole_diagnosis",
              "strain_amplitude"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_supported",
              "missing_input",
              "outside_validity",
              "insufficient_calibration"
            ],
            "description": "Why the model did not answer."
          },
          "detail": {
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "kind",
          "reason"
        ],
        "description": "A kind answered once for the case that the model declined."
      },
      "Consent": {
        "type": "object",
        "properties": {
          "purpose": {
            "type": "string",
            "enum": [
              "prediction",
              "diagnosis"
            ],
            "description": "What the owner agreed the case be used for: `prediction` of what a blast will do, or `diagnosis` of what a fired blast did, which sends the blast’s own records. A case asks only for the kinds of its purpose."
          },
          "retention": {
            "type": "string",
            "enum": [
              "none",
              "30d",
              "contract"
            ],
            "description": "How long the provider may keep the case after the run."
          },
          "allowTraining": {
            "type": "boolean",
            "description": "Whether the provider may use the case to improve its model. False unless the owner opts in."
          }
        },
        "required": [
          "purpose",
          "retention",
          "allowTraining"
        ],
        "description": "The data owner’s terms for this case. A provider that cannot honour them must refuse the run."
      },
      "CoordinateSystem": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "const": "epsg"
              },
              "code": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991,
                "description": "EPSG code of a projected system, for example 28350 (GDA94 / MGA zone 50)."
              }
            },
            "required": [
              "kind",
              "code"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "const": "local"
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120,
                "description": "The mine grid name."
              },
              "gridBearingDeg": {
                "description": "Bearing of grid north from true north, degrees clockwise.",
                "type": "number",
                "minimum": -360,
                "maximum": 360
              },
              "origin": {
                "description": "WGS 84 position of the local origin, when known.",
                "type": "object",
                "properties": {
                  "latitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "longitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  }
                },
                "required": [
                  "latitude",
                  "longitude"
                ]
              }
            },
            "required": [
              "kind",
              "name"
            ]
          }
        ],
        "description": "The frame every position in the case is expressed in."
      },
      "DamageEnvelope": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "damage_envelope"
          },
          "thresholdMmS": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The PPV taken as the onset of damage for this envelope."
          },
          "criterion": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Name of the damage criterion, for example `holmberg-persson`, `charge-column-integration` or `hoek-brown-strain`."
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "holeId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "radiusM": {
                  "description": "Radius from the hole axis at which the threshold is reached.",
                  "$ref": "#/components/schemas/Estimate"
                }
              },
              "required": [
                "holeId",
                "radiusM"
              ]
            }
          },
          "surface": {
            "description": "The envelope as a closed mesh, when available.",
            "$ref": "#/components/schemas/BlobRef"
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "thresholdMmS",
          "criterion",
          "holes",
          "validity"
        ],
        "description": "The zone around the charges where rock is expected to be damaged: the basis for wall control."
      },
      "Deck": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ExplosiveDeck"
          },
          {
            "$ref": "#/components/schemas/UnchargedDeck"
          }
        ],
        "description": "One interval of a hole, collar downwards. Only an explosive deck carries a product and a mass."
      },
      "Design": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "pattern": {
            "description": "Layout family.",
            "type": "string",
            "enum": [
              "square",
              "rectangular",
              "staggered",
              "ring",
              "development",
              "other"
            ]
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DesignHole"
            },
            "description": "Every hole, collar to toe. Empty when the platform holds only a summary of the design, as for a what-if question about a charge at a distance."
          },
          "products": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          },
          "initiation": {
            "description": "Omitted when the platform holds only a summary of the design.",
            "$ref": "#/components/schemas/Initiation"
          },
          "maxInstantaneousCharge": {
            "$ref": "#/components/schemas/MaxInstantaneousCharge"
          },
          "drillingDeviationM": {
            "description": "One standard deviation of collar and drilling error, m (Cunningham’s W): what fragmentation uniformity loses to imperfect drilling.",
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "holes",
          "products"
        ],
        "description": "The blast as designed."
      },
      "DesignHole": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "row": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "column": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "collar": {
            "$ref": "#/components/schemas/Point3"
          },
          "toe": {
            "$ref": "#/components/schemas/Point3"
          },
          "diameterMm": {
            "description": "Hole diameter, mm. Left out when the plan does not state it.",
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 2000
          },
          "domainId": {
            "description": "The rock domain the hole sits in.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
          },
          "burdenM": {
            "description": "Burden this hole breaks, m.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "spacingM": {
            "description": "Spacing to its neighbour in the row, m.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "subdrillM": {
            "description": "Drilled length below grade, m.",
            "type": "number"
          },
          "decks": {
            "minItems": 1,
            "maxItems": 32,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deck"
            },
            "description": "The hole from collar to toe. Stemming is a deck like any other."
          },
          "delay": {
            "$ref": "#/components/schemas/HoleDelay"
          }
        },
        "required": [
          "id",
          "collar",
          "toe",
          "decks",
          "delay"
        ],
        "description": "A hole as designed."
      },
      "DiagnosedHole": {
        "type": "object",
        "properties": {
          "holeId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "The design hole."
          },
          "verdict": {
            "$ref": "#/components/schemas/HoleVerdict"
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "The model’s probability that the verdict is right, about what the verdict claims and nothing more: for `on_time`, `late` and `early`, that the hole fired and when; for `weak` and `strong`, its size; for `not_detected`, that it did not fire."
          },
          "alternatives": {
            "description": "The other verdicts the model weighed, likeliest first. Each is a claim of its own, so with the verdict’s they need not sum to one.",
            "maxItems": 8,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "verdict": {
                  "$ref": "#/components/schemas/HoleVerdict"
                },
                "confidence": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                }
              },
              "required": [
                "verdict",
                "confidence"
              ]
            }
          },
          "timing": {
            "description": "When the hole fired. Left out for a hole not detected.",
            "type": "object",
            "properties": {
              "offsetMs": {
                "type": "number",
                "description": "The firing time the model found less the design hole’s `delay.totalMs`, ms."
              },
              "sigmaMs": {
                "description": "One standard deviation of that time, ms.",
                "type": "number",
                "minimum": 0
              }
            },
            "required": [
              "offsetMs"
            ]
          },
          "size": {
            "description": "How big the hole was. Left out for a hole not detected.",
            "type": "object",
            "properties": {
              "relative": {
                "type": "number",
                "minimum": 0,
                "description": "Its ground motion over the blast’s typical hole’s: 1 is typical."
              },
              "sigmaLn": {
                "description": "One standard deviation of ln(relative).",
                "type": "number",
                "minimum": 0
              },
              "call": {
                "description": "The size as a claim of its own, where the records resolve sizes. A `weak` or `strong` verdict makes the same call.",
                "type": "object",
                "properties": {
                  "value": {
                    "type": "string",
                    "enum": [
                      "weak",
                      "typical",
                      "strong"
                    ]
                  },
                  "confidence": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "The model’s probability that the call is right."
                  }
                },
                "required": [
                  "value",
                  "confidence"
                ]
              }
            },
            "required": [
              "relative"
            ]
          },
          "confinement": {
            "description": "How much shear the hole carried, which is what confinement shows as. Only where a record’s heading is known.",
            "type": "object",
            "properties": {
              "relative": {
                "type": "number",
                "minimum": 0,
                "description": "Its shear over its compression (motion across the ray over motion along it) against the blast’s typical hole: 1 is typical."
              },
              "sigmaLn": {
                "type": "number",
                "minimum": 0
              }
            },
            "required": [
              "relative"
            ]
          },
          "clarityDb": {
            "description": "The hole’s motion over the noise at its clearest record, dB.",
            "type": "number"
          },
          "dominantFrequencyHz": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "resolvedWith": {
            "description": "The other design holes the records cannot tell this one from: they fired too close together for any record to separate.",
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
            }
          },
          "flags": {
            "description": "What a reader should know about this hole’s answer, as stable codes. Reserved: `heading_unknown`, no record that heard it has a known heading; `det_log_disagrees`, the firing record says it did not fire and the records say it did; `low_snr`, it is barely above the noise; `clipped_samples`, a record that heard it was clipped; `window_truncated`, the record ends before its motion does; `no_plan_time`, the design gives it no firing time.",
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 80,
              "pattern": "^[a-z][a-z0-9_.-]*$"
            }
          },
          "evidence": {
            "$ref": "#/components/schemas/HoleEvidence"
          }
        },
        "required": [
          "holeId",
          "verdict",
          "confidence"
        ],
        "description": "One hole’s diagnosis."
      },
      "Ed25519Jwk": {
        "type": "object",
        "properties": {
          "kty": {
            "type": "string",
            "const": "OKP"
          },
          "crv": {
            "type": "string",
            "const": "Ed25519"
          },
          "x": {
            "type": "string",
            "minLength": 43,
            "maxLength": 43,
            "pattern": "^[A-Za-z0-9_-]+$",
            "description": "The 32-byte public key, base64url without padding."
          },
          "kid": {
            "description": "Ignored: the key id is always the RFC 7638 thumbprint.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "use": {
            "type": "string",
            "const": "sig"
          },
          "alg": {
            "type": "string",
            "const": "EdDSA"
          }
        },
        "required": [
          "kty",
          "crv",
          "x"
        ],
        "description": "An Ed25519 public key as an RFC 8037 JWK. Publish only the public half: the platform refuses a manifest whose key carries its private part (`d`)."
      },
      "Estimate": {
        "type": "object",
        "properties": {
          "median": {
            "type": "number",
            "description": "The best estimate (P50)."
          },
          "percentiles": {
            "description": "Further points on the predictive distribution, usually those the request asked for.",
            "maxItems": 99,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "p": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Percentile, 0 to 100."
                },
                "value": {
                  "type": "number",
                  "description": "The value at that percentile, in the unit of the parent quantity."
                }
              },
              "required": [
                "p",
                "value"
              ]
            }
          },
          "ensemble": {
            "description": "Raw ensemble members, for providers that sample rather than summarise.",
            "maxItems": 10000,
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        },
        "required": [
          "median"
        ],
        "description": "A predicted quantity with its uncertainty. A point value with no spread is allowed but a spread is expected: a prediction without one cannot be scored for coverage."
      },
      "ExplosiveDeck": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "explosive"
          },
          "topDepthM": {
            "type": "number",
            "minimum": 0,
            "description": "Depth of the top of the deck along the hole, from the collar."
          },
          "bottomDepthM": {
            "type": "number",
            "minimum": 0,
            "description": "Depth of the bottom of the deck along the hole."
          },
          "productId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "The product loaded."
          },
          "massKg": {
            "type": "number",
            "minimum": 0,
            "description": "Charge mass."
          }
        },
        "required": [
          "kind",
          "topDepthM",
          "bottomDepthM",
          "productId",
          "massKg"
        ],
        "description": "An interval of a hole loaded with explosive."
      },
      "FailedRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "modelId": {
            "$ref": "#/components/schemas/ModelId"
          },
          "modelVersion": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
            "description": "A semantic version. A result is always pinned to the exact version that produced it."
          },
          "caseId": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "status": {
            "type": "string",
            "const": "failed"
          },
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unsupported_case",
                  "consent_refused",
                  "authentication",
                  "rate_limited",
                  "quote_expired",
                  "provider_error",
                  "timeout"
                ],
                "description": "The error family. `unsupported_case` and `consent_refused` are final; `rate_limited`, `provider_error` and `timeout` may be retried."
              },
              "code": {
                "type": "string",
                "maxLength": 80,
                "pattern": "^[a-z][a-z0-9_]*$",
                "description": "A stable machine-readable code within the family."
              },
              "message": {
                "type": "string",
                "minLength": 1,
                "maxLength": 1000
              },
              "param": {
                "description": "The case field at fault, as a JSON Pointer.",
                "type": "string",
                "maxLength": 200
              }
            },
            "required": [
              "type",
              "code",
              "message"
            ]
          }
        },
        "required": [
          "id",
          "modelId",
          "modelVersion",
          "caseId",
          "createdAt",
          "updatedAt",
          "status",
          "error"
        ],
        "description": "Finished without a result, and why."
      },
      "FiringRecord": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "det_log",
              "timing_import",
              "design"
            ],
            "description": "Where the firing times come from: a detonator log, an imported timing file, or the design itself."
          },
          "firedAt": {
            "description": "Wall-clock time of T0.",
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "firedMs": {
                  "description": "Measured firing time from T0, ms; null when the hole did not fire.",
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "fired",
                    "misfire",
                    "cutoff",
                    "unknown"
                  ]
                }
              },
              "required": [
                "id",
                "firedMs",
                "status"
              ]
            }
          }
        },
        "required": [
          "source",
          "holes"
        ],
        "description": "What actually fired, and when."
      },
      "FlyrockZone": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "flyrock_zone"
          },
          "maxThrowM": {
            "description": "Maximum expected throw, m.",
            "$ref": "#/components/schemas/Estimate"
          },
          "mechanism": {
            "type": "string",
            "enum": [
              "face_burst",
              "cratering",
              "rifling",
              "combined"
            ]
          },
          "clearance": {
            "description": "Recommended clearance distances.",
            "type": "object",
            "properties": {
              "personnelM": {
                "type": "number",
                "exclusiveMinimum": 0
              },
              "equipmentM": {
                "type": "number",
                "exclusiveMinimum": 0
              }
            },
            "required": [
              "personnelM",
              "equipmentM"
            ]
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "holeId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "maxThrowM": {
                  "type": "number",
                  "minimum": 0
                }
              },
              "required": [
                "holeId",
                "maxThrowM"
              ]
            }
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "maxThrowM",
          "mechanism",
          "validity"
        ],
        "description": "Predicted flyrock range and clearance."
      },
      "FragmentationCurve": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "fragmentation_curve"
          },
          "domainId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "curve": {
            "minItems": 2,
            "maxItems": 200,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "sizeMm": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Sieve size, mm."
                },
                "passingPercent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Percent of the rock passing that size."
                }
              },
              "required": [
                "sizeMm",
                "passingPercent"
              ]
            },
            "description": "The curve, by ascending sieve size."
          },
          "x50Mm": {
            "$ref": "#/components/schemas/Estimate"
          },
          "x80Mm": {
            "$ref": "#/components/schemas/Estimate"
          },
          "uniformityIndex": {
            "type": "number",
            "minimum": 0
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "curve",
          "x50Mm",
          "x80Mm",
          "validity"
        ],
        "description": "Predicted size distribution of the broken rock."
      },
      "FrequencyBin": {
        "type": "object",
        "properties": {
          "log10SdMin": {
            "type": "number"
          },
          "log10SdMax": {
            "type": "number"
          },
          "medianHz": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Median dominant frequency of the readings in the band, Hz."
          },
          "count": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "log10SdMin",
          "log10SdMax",
          "medianHz",
          "count"
        ],
        "description": "One band of log10(scaled distance) and the median dominant frequency the site recorded in it."
      },
      "Grid": {
        "type": "object",
        "properties": {
          "minX": {
            "type": "number"
          },
          "minY": {
            "type": "number"
          },
          "maxX": {
            "type": "number"
          },
          "maxY": {
            "type": "number"
          },
          "cellM": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Cell size, m."
          },
          "elevation": {
            "type": "string",
            "enum": [
              "terrain",
              "collar_plane"
            ],
            "description": "Where grid points sit vertically."
          }
        },
        "required": [
          "minX",
          "minY",
          "maxX",
          "maxY",
          "cellM",
          "elevation"
        ],
        "description": "A plan-view grid for field outputs. A field’s values sit at the nodes minX + i·cellM, minY + j·cellM for i below its `columns` and j below its `rows`, the last node within a cell of maxX and maxY; a reader takes `columns` and `rows` from the output and never recomputes them."
      },
      "HoleDelay": {
        "type": "object",
        "properties": {
          "totalMs": {
            "type": "number",
            "description": "Designed firing time from T0, milliseconds."
          },
          "surfaceMs": {
            "description": "Delay carried by the surface connector.",
            "type": "number",
            "minimum": 0
          },
          "downholeMs": {
            "description": "Delay of the in-hole unit.",
            "type": "number",
            "minimum": 0
          },
          "tieInFrom": {
            "description": "The hole this one receives its signal from on the surface tie-in.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
          }
        },
        "required": [
          "totalMs"
        ],
        "description": "When a hole is designed to fire."
      },
      "HoleDiagnosis": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "hole_diagnosis"
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiagnosedHole"
            },
            "description": "Every design hole with an explosive deck, once each."
          },
          "records": {
            "description": "What became of each record the model was given.",
            "maxItems": 64,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "recordId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "use": {
                  "type": "string",
                  "enum": [
                    "read",
                    "set_aside",
                    "skipped"
                  ],
                  "description": "What the model did with the record: read it; set it aside as unable to tell the holes apart; or skipped it unread, for example beyond the number of records it reads, or at a receiver with no position."
                },
                "residualFraction": {
                  "description": "For a record read: the energy left in it once every hole’s motion is taken out, over what was there.",
                  "type": "number",
                  "minimum": 0
                }
              },
              "required": [
                "recordId",
                "use"
              ]
            }
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "holes",
          "validity"
        ],
        "description": "What each hole of a fired blast did, read from the blast’s own ground-motion records and its firing record."
      },
      "HoleEvidence": {
        "type": "object",
        "properties": {
          "recordId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "The record of `measured.records` the motion is modelled on: the one that heard the hole most clearly."
          },
          "sampleRateHz": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The rate of the rows, which is the model’s own and need not be the record’s."
          },
          "startMs": {
            "type": "number",
            "description": "Time of the first row from T0, ms, as the model aligned the record."
          },
          "samples": {
            "maxItems": 8192,
            "type": "array",
            "items": {
              "minItems": 3,
              "maxItems": 3,
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "description": "The hole’s own motion at that record, one `[a, b, c]` row per sample on the record’s axes, mm/s. At most a second or so: enough to draw."
          }
        },
        "required": [
          "recordId",
          "sampleRateHz",
          "startMs",
          "samples"
        ],
        "description": "The working behind a verdict: the ground motion the model attributes to this hole alone, so a reader can see what it rests on."
      },
      "HoleVerdict": {
        "type": "string",
        "enum": [
          "on_time",
          "late",
          "early",
          "not_detected",
          "weak",
          "strong",
          "unrelieved",
          "unresolved"
        ],
        "description": "What became of a hole. `on_time`, `late` and `early`: it fired, within the model’s timing limit of its designed time, after it, or before it. `not_detected`: no motion of its own was found, so it is taken not to have fired. `weak` and `strong`: it fired, and its ground motion was well below or well above the blast’s typical hole. `unrelieved`: it fired confined, with no relief to break to; reserved for a model that can tell, and the platform’s own does not give it. `unresolved`: the records cannot tell it from the holes in `resolvedWith`. A verdict claims only what it names: a hole on time may also be weak, which `size` says."
      },
      "ImportDocument": {
        "$ref": "#/components/schemas/ImportDocument"
      },
      "ImportedOutput": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ReceiverPpv"
          },
          {
            "$ref": "#/components/schemas/MaxCharge"
          },
          {
            "$ref": "#/components/schemas/ReceiverWaveform"
          },
          {
            "$ref": "#/components/schemas/ReceiverSpectrum"
          },
          {
            "$ref": "#/components/schemas/PpvField"
          },
          {
            "$ref": "#/components/schemas/Airblast"
          },
          {
            "$ref": "#/components/schemas/DamageEnvelope"
          },
          {
            "$ref": "#/components/schemas/StrainField"
          },
          {
            "$ref": "#/components/schemas/FlyrockZone"
          },
          {
            "$ref": "#/components/schemas/FragmentationCurve"
          },
          {
            "$ref": "#/components/schemas/MovementField"
          },
          {
            "$ref": "#/components/schemas/TimingProposal"
          }
        ],
        "description": "One prediction made elsewhere, discriminated by `kind`: every kind a prediction case asks."
      },
      "Initiation": {
        "type": "object",
        "properties": {
          "system": {
            "$ref": "#/components/schemas/InitiationSystem"
          },
          "delayScatter": {
            "description": "One standard deviation of detonator timing error: a fixed part plus a percentage of the nominal delay.",
            "type": "object",
            "properties": {
              "fixedMs": {
                "type": "number",
                "minimum": 0
              },
              "percent": {
                "type": "number",
                "minimum": 0
              }
            }
          }
        },
        "required": [
          "system"
        ],
        "description": "How the blast is initiated."
      },
      "InitiationSystem": {
        "type": "string",
        "enum": [
          "electronic",
          "nonel",
          "electric",
          "detonating_cord"
        ],
        "description": "The detonator technology. It sets the delay scatter a model should assume when none is given."
      },
      "InputPath": {
        "type": "string",
        "enum": [
          "design.holes",
          "design.products",
          "design.initiation",
          "design.maxInstantaneousCharge",
          "design.drillingDeviation",
          "asBuilt.holes",
          "asBuilt.firing",
          "site.domains",
          "site.terrain",
          "receivers",
          "calibration.siteLaw",
          "calibration.airblastLaw",
          "calibration.seedWaveforms",
          "calibration.vod",
          "calibration.outcomes",
          "calibration.strainAmplitude",
          "measured.records",
          "measured.backbreak"
        ],
        "description": "A part of the BlastCase. The case a model receives is built from the parts it declares and nothing else. `design.holes` carries the holes and the layout family they form; `design.drillingDeviation` carries `design.drillingDeviationM`."
      },
      "LawFitStatistics": {
        "type": "object",
        "properties": {
          "dof": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Residual degrees of freedom of the fit."
          },
          "meanLnScaledDistance": {
            "type": "number",
            "description": "Mean of ln(scaled distance) over the readings."
          },
          "sxx": {
            "anyOf": [
              {
                "type": "number",
                "exclusiveMinimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "Σ(ln SD − mean)² over the readings. Null when only the law’s summary is known; intervals then leave out the leverage term."
          }
        },
        "required": [
          "dof",
          "meanLnScaledDistance",
          "sxx"
        ],
        "description": "The least-squares statistics of a log-log fit, for prediction intervals: ln y ± t(p, dof)·σ·√(1 + 1/n + (ln SD − mean)²/sxx)."
      },
      "MaxCharge": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "max_charge"
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "limitMmS": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The PPV limit the charge was held to: the receiver’s own limit."
          },
          "percentile": {
            "type": "number",
            "minimum": 50,
            "maximum": 99.9,
            "description": "The percentile the limit was read at."
          },
          "maxChargeKg": {
            "anyOf": [
              {
                "type": "number",
                "exclusiveMinimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "The largest maximum instantaneous charge whose prediction at that percentile stays at or under the limit. Null when even a token charge exceeds it."
          },
          "scaledDistance": {
            "description": "The scaled distance at that charge.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "receiverId",
          "limitMmS",
          "percentile",
          "maxChargeKg",
          "validity"
        ],
        "description": "The inverse question: how much charge per delay a receiver’s limit allows. The design value blasters plan with."
      },
      "MaxInstantaneousCharge": {
        "type": "object",
        "properties": {
          "massKg": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The largest charge detonating inside any one window, kg."
          },
          "windowMs": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The window, ms. Jurisdictions set it; 8 ms is common."
          }
        },
        "required": [
          "massKg",
          "windowMs"
        ],
        "description": "The maximum instantaneous charge (MIC) as the platform computed it for compliance. A model that works from the charge rather than from every hole should use it; one that models the holes may recompute it."
      },
      "Measured": {
        "type": "object",
        "properties": {
          "records": {
            "maxItems": 64,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MeasuredRecord"
            }
          },
          "backbreak": {
            "description": "How far behind its holes the blast broke the wall, once it was seen.",
            "maxItems": 5000,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BackbreakReading"
            }
          }
        },
        "description": "What was measured of this blast itself, once it has fired. `calibration` holds what earlier blasts taught the site; this holds the blast in question."
      },
      "MeasuredOutcome": {
        "type": "object",
        "properties": {
          "blastRef": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Which earlier blast this came from."
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "distanceM": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "micKg": {
            "description": "Maximum instantaneous charge of that blast, kg.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "ppvMmS": {
            "type": "number",
            "minimum": 0
          },
          "basis": {
            "$ref": "#/components/schemas/PpvBasis"
          },
          "dominantFrequencyHz": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "airblastDbL": {
            "type": "number",
            "minimum": 0
          }
        },
        "required": [
          "blastRef",
          "receiverId"
        ],
        "description": "A reading from an earlier blast at this site."
      },
      "MeasuredRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "The receiver it was recorded at. That receiver carries a `position`, with or without an elevation: a record with no place on the grid tells a model nothing about which hole it heard."
          },
          "sampleRateHz": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "axes": {
            "type": "string",
            "enum": [
              "recorded",
              "levelled",
              "oriented"
            ],
            "description": "How the rows’ three components lie. `recorded`: as the unit wrote them, with nothing known of how it lay. `levelled`: turned so the third component is vertical, up positive, and the first two lie in the level plane on an unknown bearing. `oriented`: levelled, with the first component along `headingDeg` and the second a quarter turn clockwise of it seen from above."
          },
          "headingDeg": {
            "description": "Bearing of the first component, degrees clockwise from grid north. Given for `oriented` axes only.",
            "type": "number",
            "minimum": 0,
            "maximum": 360
          },
          "quietUntilIndex": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "The rows before this index were recorded before the blast’s motion arrived: what a model measures the noise from."
          },
          "dominantFrequencyHz": {
            "description": "Dominant frequency of the record, Hz, as the platform’s analysis found it.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "clipMmS": {
            "description": "The recorder’s full scale, mm/s. A sample at it was clipped.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "recordedAt": {
            "description": "Wall-clock time of the first row, by the recorder’s own clock.",
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
          },
          "samples": {
            "$ref": "#/components/schemas/ParticleVelocity"
          }
        },
        "required": [
          "id",
          "receiverId",
          "sampleRateHz",
          "axes",
          "quietUntilIndex",
          "samples"
        ],
        "description": "A ground-motion record of this blast, as a monitor wrote it. Its time against T0 is not known: a recorder starts on the motion reaching it, and its clock is not the initiation system’s. A model that needs the two aligned finds the alignment itself."
      },
      "ModelId": {
        "type": "string",
        "minLength": 3,
        "maxLength": 128,
        "pattern": "^[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9.-]*$",
        "description": "A model identifier in the form `vendor/model`, lower case, for example `shockai/site-law`."
      },
      "ModelManifest": {
        "$ref": "#/components/schemas/ModelManifest"
      },
      "ModelMethod": {
        "type": "string",
        "enum": [
          "empirical",
          "kinematic",
          "numerical",
          "statistical",
          "hybrid"
        ],
        "description": "The family of method, so a user can tell a fitted law from a simulation."
      },
      "ModelResult": {
        "$ref": "#/components/schemas/ModelResult"
      },
      "MovementField": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "movement_field"
          },
          "points": {
            "maxItems": 50000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "Names the point within this field."
                },
                "from": {
                  "$ref": "#/components/schemas/Point3"
                },
                "to": {
                  "$ref": "#/components/schemas/Point3"
                },
                "flight": {
                  "description": "When the point moves. Without a `touchdown`, a viewer may draw it as an arc under gravity that joins `from` and `to` in that time; the model’s own path may differ, such as a block that rolls after it lands. Left out, only the displacement is stated.",
                  "type": "object",
                  "properties": {
                    "launchMs": {
                      "type": "number",
                      "description": "When the point leaves `from`, milliseconds from T0."
                    },
                    "durationMs": {
                      "type": "number",
                      "minimum": 0,
                      "description": "How long it then takes to come to rest at `to`, milliseconds."
                    },
                    "touchdown": {
                      "description": "Where the point first comes down on something, or the rock round it closes on it: the end of its flight through the air, before it rolls, slides, slumps or drops to `to`, or is carried up to it. The arc under gravity that joins `from` and `at` in `afterMs` is the model’s own flight; from there the point moves to `to`, arriving at `durationMs`. Left out, the flight is not told apart from what follows it.",
                      "type": "object",
                      "properties": {
                        "at": {
                          "$ref": "#/components/schemas/Point3"
                        },
                        "afterMs": {
                          "type": "number",
                          "minimum": 0,
                          "description": "When it gets there, milliseconds after it leaves `from`: never after `durationMs`."
                        }
                      },
                      "required": [
                        "at",
                        "afterMs"
                      ]
                    }
                  },
                  "required": [
                    "launchMs",
                    "durationMs"
                  ]
                },
                "source": {
                  "description": "The point as a source for a model of the broken rock’s surface, a flow over the ground the blast empties: how much rock, how heavy, the momentum it starts with and whether the mass carries it. A field that states sources states one for every point with a flight (conformance holds it to that). Left out, a viewer infers the flow from where the points rest.",
                  "type": "object",
                  "properties": {
                    "volumeM3": {
                      "type": "number",
                      "exclusiveMinimum": 0,
                      "description": "The rock the point stands for as it stood in the bench, m³: in situ, before any swell."
                    },
                    "massKg": {
                      "type": "number",
                      "exclusiveMinimum": 0,
                      "description": "That rock’s mass, kg."
                    },
                    "momentum": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "description": "Eastward, kg·m/s."
                        },
                        "y": {
                          "type": "number",
                          "description": "Northward, kg·m/s."
                        }
                      },
                      "required": [
                        "x",
                        "y"
                      ],
                      "description": "The level momentum the rock leaves `from` with at `flight.launchMs`, kg·m/s, once whatever confines or relieves it has taken its share: what a flow of the broken rock over the ground starts with. Its vertical part is not stated; the flight is."
                    },
                    "carried": {
                      "type": "boolean",
                      "description": "True where the mass carries the rock: it comes down and rests over the ground the blast breaks or the bench round it. False where it is thrown clear, out through the free face onto the floor in front, a piece of its own."
                    }
                  },
                  "required": [
                    "volumeM3",
                    "massKg",
                    "momentum",
                    "carried"
                  ]
                }
              },
              "required": [
                "id",
                "from",
                "to"
              ]
            }
          },
          "muckpileSurface": {
            "description": "The predicted muckpile surface.",
            "$ref": "#/components/schemas/BlobRef"
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "points",
          "validity"
        ],
        "description": "Predicted displacement of the burden, and the muckpile it forms."
      },
      "Output": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ReceiverPpv"
          },
          {
            "$ref": "#/components/schemas/MaxCharge"
          },
          {
            "$ref": "#/components/schemas/ReceiverWaveform"
          },
          {
            "$ref": "#/components/schemas/ReceiverSpectrum"
          },
          {
            "$ref": "#/components/schemas/PpvField"
          },
          {
            "$ref": "#/components/schemas/Airblast"
          },
          {
            "$ref": "#/components/schemas/DamageEnvelope"
          },
          {
            "$ref": "#/components/schemas/StrainField"
          },
          {
            "$ref": "#/components/schemas/FlyrockZone"
          },
          {
            "$ref": "#/components/schemas/FragmentationCurve"
          },
          {
            "$ref": "#/components/schemas/MovementField"
          },
          {
            "$ref": "#/components/schemas/TimingProposal"
          },
          {
            "$ref": "#/components/schemas/HoleDiagnosis"
          },
          {
            "$ref": "#/components/schemas/StrainAmplitudeFit"
          }
        ],
        "description": "One answer, discriminated by `kind`."
      },
      "OutputKind": {
        "type": "string",
        "enum": [
          "receiver_ppv",
          "max_charge",
          "receiver_waveform",
          "receiver_spectrum",
          "ppv_field",
          "airblast",
          "damage_envelope",
          "strain_field",
          "flyrock_zone",
          "fragmentation_curve",
          "movement_field",
          "timing_proposal",
          "hole_diagnosis",
          "strain_amplitude"
        ],
        "description": "A kind of answer: a prediction of what a blast will do, or, for `hole_diagnosis` and `strain_amplitude`, a reading of what a fired blast did."
      },
      "OutputRequest": {
        "type": "object",
        "properties": {
          "outputs": {
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OutputKind"
            },
            "description": "What the caller wants back."
          },
          "receiverIds": {
            "description": "Receivers to predict at; all receivers when omitted.",
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
              "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
            }
          },
          "grid": {
            "$ref": "#/components/schemas/Grid"
          },
          "percentiles": {
            "default": [
              50,
              95
            ],
            "description": "Points of the predictive distribution to report. 50 and 95 when omitted.",
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            }
          },
          "designPercentile": {
            "default": 95,
            "description": "The percentile design values are read at: a charge limit is the largest charge whose prediction at this percentile stays under the limit. 95 when omitted.",
            "type": "number",
            "minimum": 50,
            "maximum": 99.9
          },
          "seed": {
            "description": "Seed for any sampling, so a run can be reproduced exactly.",
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "outputs"
        ],
        "description": "The question the case asks."
      },
      "ParticleVelocity": {
        "anyOf": [
          {
            "maxItems": 200000,
            "type": "array",
            "items": {
              "minItems": 3,
              "maxItems": 3,
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "description": "One row per sample, `[a, b, c]` in frame order, mm/s."
          },
          {
            "$ref": "#/components/schemas/BlobRef"
          }
        ],
        "description": "Three-component particle velocity, mm/s: one `[a, b, c]` row per sample in frame order, so every component has the same length; or a reference to the same rows."
      },
      "Point3": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "description": "Easting, metres."
          },
          "y": {
            "type": "number",
            "description": "Northing, metres."
          },
          "z": {
            "type": "number",
            "description": "Elevation, metres. Up is positive."
          }
        },
        "required": [
          "x",
          "y",
          "z"
        ],
        "description": "A position in the case coordinate system, metres."
      },
      "PpvBasis": {
        "type": "string",
        "enum": [
          "max_axis",
          "pvs"
        ],
        "description": "How peak particle velocity is taken: the largest single component (`max_axis`) or the peak vector sum (`pvs`)."
      },
      "PpvField": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "ppv_field"
          },
          "grid": {
            "$ref": "#/components/schemas/Grid"
          },
          "percentile": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "basis": {
            "$ref": "#/components/schemas/PpvBasis"
          },
          "columns": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991,
            "description": "Nodes along x: ⌊(maxX − minX) / cellM⌋ + 1."
          },
          "rows": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991,
            "description": "Nodes along y: ⌊(maxY − minY) / cellM⌋ + 1."
          },
          "valuesMmS": {
            "anyOf": [
              {
                "maxItems": 4000000,
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              {
                "$ref": "#/components/schemas/BlobRef"
              }
            ],
            "description": "Row-major from the south-west corner; null where the model declines to predict."
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "grid",
          "percentile",
          "basis",
          "columns",
          "rows",
          "valuesMmS",
          "validity"
        ],
        "description": "Peak particle velocity over a plan-view grid, at one percentile."
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "class": {
            "type": "string",
            "enum": [
              "anfo",
              "heavy_anfo",
              "emulsion",
              "watergel",
              "packaged",
              "booster",
              "other"
            ],
            "description": "Product family."
          },
          "densityGcc": {
            "description": "In-hole density, g/cm³.",
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 3
          },
          "relativeWeightStrength": {
            "description": "Relative weight strength, ANFO = 100.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "energyMjKg": {
            "description": "Rated energy, MJ/kg.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "nominalVodMs": {
            "description": "Nominal velocity of detonation, m/s.",
            "type": "number",
            "exclusiveMinimum": 0
          }
        },
        "required": [
          "id",
          "name",
          "class"
        ],
        "description": "An explosive product as loaded."
      },
      "Provenance": {
        "type": "object",
        "properties": {
          "modelId": {
            "$ref": "#/components/schemas/ModelId"
          },
          "modelVersion": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
            "description": "A semantic version. A result is always pinned to the exact version that produced it."
          },
          "caseId": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "finishedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "inputsUsed": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InputPath"
            },
            "description": "Which parts of the case the model actually read, as input paths."
          },
          "parameters": {
            "description": "Any parameters the model fitted or chose, so the result can be explained.",
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            }
          },
          "buildHash": {
            "description": "The build that answered, as the manifest's `build.hash` names it.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._:+/@-]+$"
          }
        },
        "required": [
          "modelId",
          "modelVersion",
          "caseId",
          "startedAt",
          "finishedAt",
          "inputsUsed"
        ],
        "description": "Where a result came from. Reports cite it; the platform uses it to score the model later."
      },
      "QueuedRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "modelId": {
            "$ref": "#/components/schemas/ModelId"
          },
          "modelVersion": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
            "description": "A semantic version. A result is always pinned to the exact version that produced it."
          },
          "caseId": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "status": {
            "type": "string",
            "const": "queued"
          },
          "progress": {
            "description": "Fraction complete, for display only.",
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        },
        "required": [
          "id",
          "modelId",
          "modelVersion",
          "caseId",
          "createdAt",
          "updatedAt",
          "status"
        ],
        "description": "Accepted and waiting to start."
      },
      "Quote": {
        "$ref": "#/components/schemas/Quote"
      },
      "Range": {
        "type": "object",
        "properties": {
          "min": {
            "type": "number"
          },
          "max": {
            "type": "number"
          }
        },
        "description": "An inclusive interval. Either bound may be omitted. A range whose `min` exceeds its `max` is empty: a model declared valid over one is valid nowhere, and no prediction falls inside a fitted range that is empty."
      },
      "Receiver": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "kind": {
            "type": "string",
            "enum": [
              "monitor",
              "structure",
              "boundary",
              "point"
            ],
            "description": "A seismograph location, a structure to protect, a lease or property boundary point, or any point of interest."
          },
          "position": {
            "description": "Where the receiver is. Omitted when only its distance is known. A monitor placed on a plan often has no surveyed height: its `z` is then left out.",
            "$ref": "#/components/schemas/SurfacePosition"
          },
          "distanceM": {
            "description": "The source-to-receiver distance the platform resolved: a surveyed or entered distance, or the slant distance to the nearest charge. A model should use it unless it models the geometry itself.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "distanceSource": {
            "description": "Where `distanceM` came from, for example `manual`, `placement` or `georeference`.",
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "limits": {
            "description": "The limits that apply here: a model reports exceedance probability against them, and a charge limit is held to the PPV limit.",
            "type": "object",
            "properties": {
              "ppvMmS": {
                "type": "number",
                "exclusiveMinimum": 0
              },
              "airblastDbL": {
                "type": "number",
                "exclusiveMinimum": 0
              },
              "standard": {
                "description": "The standard the limit comes from, for example `AS 2187.2`.",
                "type": "string",
                "maxLength": 120
              }
            }
          }
        },
        "required": [
          "id",
          "kind"
        ],
        "description": "A place a prediction is wanted. It has a position, a resolved distance, or both.",
        "anyOf": [
          {
            "required": [
              "position"
            ]
          },
          {
            "required": [
              "distanceM"
            ]
          }
        ],
        "x-atLeastOneOf": [
          [
            "position",
            "distanceM"
          ]
        ]
      },
      "ReceiverPpv": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "receiver_ppv"
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "basis": {
            "$ref": "#/components/schemas/PpvBasis"
          },
          "ppvMmS": {
            "description": "Peak particle velocity, mm/s.",
            "$ref": "#/components/schemas/Estimate"
          },
          "dominantFrequencyHz": {
            "description": "Frequency at the peak, Hz.",
            "$ref": "#/components/schemas/Estimate"
          },
          "scaledDistance": {
            "description": "The scaled distance the prediction was made at.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "exceedanceProbability": {
            "description": "Probability of exceeding the receiver’s PPV limit, when it has one.",
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "receiverId",
          "basis",
          "ppvMmS",
          "validity"
        ],
        "description": "Predicted peak particle velocity at a receiver."
      },
      "ReceiverSpectrum": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "receiver_spectrum"
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "spectrum": {
            "maxItems": 10000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "frequencyHz": {
                  "type": "number",
                  "exclusiveMinimum": 0
                },
                "amplitude": {
                  "type": "number",
                  "minimum": 0,
                  "description": "Fourier amplitude, mm/s·s."
                }
              },
              "required": [
                "frequencyHz",
                "amplitude"
              ]
            },
            "description": "The spectrum, by ascending frequency."
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "receiverId",
          "spectrum",
          "validity"
        ],
        "description": "A predicted amplitude spectrum at a receiver."
      },
      "ReceiverUnavailable": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "receiver_ppv",
              "max_charge",
              "receiver_waveform",
              "receiver_spectrum",
              "airblast"
            ]
          },
          "receiverId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
              },
              {
                "type": "null"
              }
            ],
            "description": "The receiver the model declined at, or null when it declines the kind at every receiver (for example, the case names none)."
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_supported",
              "missing_input",
              "outside_validity",
              "insufficient_calibration"
            ],
            "description": "Why the model did not answer."
          },
          "detail": {
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "kind",
          "receiverId",
          "reason"
        ],
        "description": "A kind answered per receiver that the model declined, at one receiver or at all of them."
      },
      "ReceiverWaveform": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "receiver_waveform"
          },
          "receiverId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "percentile": {
            "description": "Which member of the distribution this trace represents, when the model samples.",
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "sampleRateHz": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "startMs": {
            "type": "number",
            "description": "Time of the first sample from T0, ms."
          },
          "frame": {
            "type": "string",
            "enum": [
              "xyz",
              "tvl"
            ]
          },
          "samples": {
            "$ref": "#/components/schemas/ParticleVelocity"
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "receiverId",
          "sampleRateHz",
          "startMs",
          "frame",
          "samples",
          "validity"
        ],
        "description": "A predicted ground-motion time history at a receiver."
      },
      "RockClass": {
        "type": "string",
        "enum": [
          "super_soft",
          "soft",
          "medium",
          "hard"
        ],
        "description": "Coarse rock class used when no measured properties are available."
      },
      "RockDomain": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "rockClass": {
            "description": "Coarse class when nothing better is known.",
            "$ref": "#/components/schemas/RockClass"
          },
          "densityKgM3": {
            "description": "In-situ density, kg/m³.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "ucsMPa": {
            "description": "Uniaxial compressive strength, MPa.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "youngsModulusGPa": {
            "description": "Young's modulus, GPa.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "hoekBrownMi": {
            "description": "Hoek–Brown constant m_i of the intact rock: the m in σ1 = σ3 + σci·(m·σ3/σci + 1)^0.5, which with the UCS gives its tensile strength σci·(√(m² + 4) − m)/2, about UCS/m_i.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "poissonsRatio": {
            "description": "Poisson's ratio of the intact rock, between 0 and 0.5.",
            "type": "number",
            "exclusiveMinimum": 0,
            "exclusiveMaximum": 0.5
          },
          "strengthStage": {
            "description": "Which stage of failure the strength figures describe (Martin and Chandler 1994): crack initiation, the onset of strain localisation, or peak. A model judging damage reads the UCS and m_i as that stage's; peak when unstated.",
            "$ref": "#/components/schemas/StrengthStage"
          },
          "strainCriterion": {
            "description": "A strain criterion for this rock, usually regressed from its measured failure strains, which a model judging damage reads in place of the strength it derives from the figures.",
            "$ref": "#/components/schemas/StrainCriterion"
          },
          "pWaveVelocityMs": {
            "description": "P-wave velocity, m/s.",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "rmr": {
            "description": "Rock mass rating (Bieniawski).",
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "gsi": {
            "description": "Geological strength index.",
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "kuzRamRockFactor": {
            "description": "Kuz-Ram rock factor A, when calibrated.",
            "type": "number",
            "exclusiveMinimum": 0
          }
        },
        "required": [
          "id"
        ],
        "description": "A zone of rock the site treats as one material. Holes name the domain they sit in."
      },
      "Run": {
        "$ref": "#/components/schemas/Run"
      },
      "RunningRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "modelId": {
            "$ref": "#/components/schemas/ModelId"
          },
          "modelVersion": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
            "description": "A semantic version. A result is always pinned to the exact version that produced it."
          },
          "caseId": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "status": {
            "type": "string",
            "const": "running"
          },
          "progress": {
            "description": "Fraction complete, for display only.",
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        },
        "required": [
          "id",
          "modelId",
          "modelVersion",
          "caseId",
          "createdAt",
          "updatedAt",
          "status"
        ],
        "description": "Started and not yet finished."
      },
      "RunRequest": {
        "$ref": "#/components/schemas/RunRequest"
      },
      "Scaling": {
        "type": "string",
        "enum": [
          "square_root",
          "cube_root"
        ],
        "description": "Charge scaling for scaled distance: D/√Q for ground vibration, D/∛Q for airblast and near-field work."
      },
      "SeedWaveform": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "holeId": {
            "description": "The single hole that produced it.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
          },
          "receiverId": {
            "description": "Where it was recorded.",
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
          },
          "distanceM": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Charge-to-sensor distance, m."
          },
          "chargeKg": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Charge mass of the seed hole, kg."
          },
          "sampleRateHz": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "frame": {
            "type": "string",
            "enum": [
              "xyz",
              "tvl"
            ],
            "description": "Component frame: instrument axes, or transverse / vertical / longitudinal to the source."
          },
          "samples": {
            "$ref": "#/components/schemas/ParticleVelocity"
          },
          "recordedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          }
        },
        "required": [
          "id",
          "distanceM",
          "chargeKg",
          "sampleRateHz",
          "frame",
          "samples"
        ],
        "description": "A single-hole ground-motion record: the kinematic input a superposition model is calibrated from."
      },
      "SiteLaw": {
        "type": "object",
        "properties": {
          "metric": {
            "type": "string",
            "const": "ppv"
          },
          "basis": {
            "$ref": "#/components/schemas/PpvBasis"
          },
          "scaling": {
            "$ref": "#/components/schemas/Scaling"
          },
          "k": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Site constant K in PPV = K·SD^−β, mm/s."
          },
          "beta": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Attenuation exponent β."
          },
          "sigmaLn": {
            "description": "Residual standard deviation in natural-log units.",
            "type": "number",
            "minimum": 0
          },
          "n": {
            "description": "Readings in the fit. Fractional when a regional prior lends the fit its weight in readings.",
            "type": "number",
            "minimum": 0
          },
          "scaledDistance": {
            "description": "The scaled-distance range the fit was made over.",
            "$ref": "#/components/schemas/Range"
          },
          "fit": {
            "description": "What a prediction interval needs beyond σ and n.",
            "$ref": "#/components/schemas/LawFitStatistics"
          },
          "frequencyBins": {
            "description": "Dominant frequency by scaled distance, from the site’s own readings.",
            "maxItems": 60,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FrequencyBin"
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "platform_fit",
              "user",
              "prior"
            ],
            "description": "A law fitted by the platform from this site’s readings, one entered by the user, or a regional prior."
          }
        },
        "required": [
          "metric",
          "basis",
          "scaling",
          "k",
          "beta",
          "source"
        ],
        "description": "A fitted scaled-distance attenuation law."
      },
      "StrainAmplitude": {
        "type": "object",
        "properties": {
          "factor": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The ratio of the P wave's amplitude near the charges to the site law's PPV extrapolated inward, as the model that fitted it reads both: above 1 the rock is strained more than the law alone says. A model judging damage scales its near-field amplitude by it."
          },
          "basis": {
            "type": "string",
            "enum": [
              "backbreak",
              "half_cast",
              "scan",
              "strain_array",
              "judgement"
            ],
            "description": "What the factor was fitted to: how far behind the back row the wall broke, half-cast counts on the final wall, a post-blast scan against the design, strain read from an array of synchronised monitors, or judgement."
          },
          "blastRef": {
            "description": "Which blast it was fitted on.",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "fittedWith": {
            "description": "The model and version whose amplitude the factor scales. A factor fitted against one model's assumptions is that model's: another model, another version, or any model when this is left out, reads it as judgement.",
            "type": "object",
            "properties": {
              "modelId": {
                "$ref": "#/components/schemas/ModelId"
              },
              "modelVersion": {
                "type": "string",
                "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
                "description": "A semantic version. A result is always pinned to the exact version that produced it."
              }
            },
            "required": [
              "modelId",
              "modelVersion"
            ]
          },
          "note": {
            "description": "How the fit was made, for a reader.",
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "factor",
          "basis"
        ],
        "description": "The site's fit of the strain a blast puts in its rock near the charges, which a model judging damage scales its amplitude by."
      },
      "StrainAmplitudeFit": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "strain_amplitude"
          },
          "factor": {
            "description": "The factor on the model's near-field amplitude that makes the damage radius it reports on the observed holes what was seen: the median over the holes, in log, with their spread as percentiles. What a later case of the site carries as `calibration.strainAmplitude`.",
            "$ref": "#/components/schemas/Estimate"
          },
          "basis": {
            "type": "string",
            "enum": [
              "backbreak"
            ],
            "description": "What it was fitted to: the backbreak measured behind the holes."
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "holeId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "observedM": {
                  "type": "number",
                  "minimum": 0,
                  "description": "The backbreak seen behind it, m."
                },
                "factor": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "The least factor at which the model reports its radius as the backbreak, stated rounded up."
                }
              },
              "required": [
                "holeId",
                "observedM",
                "factor"
              ]
            },
            "description": "Each hole the fit read, once."
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "factor",
          "basis",
          "holes",
          "validity"
        ],
        "description": "A damage model's amplitude fitted to the damage a fired blast did: how much the strain near its charges exceeded, or fell short of, what the model takes from the site law."
      },
      "StrainCriterion": {
        "type": "object",
        "properties": {
          "stage": {
            "description": "The stage of failure the criterion describes; peak when unstated.",
            "$ref": "#/components/schemas/StrengthStage"
          },
          "sigmaCiMPa": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The σci the constants were fitted or derived with, MPa."
          },
          "a": {
            "type": "number",
            "description": "Per MPa, for strain as a fraction (not percent)."
          },
          "b": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Per MPa², for strain as a fraction. Must exceed c², or the curve does not enclose the unstrained state: a model judges such a criterion by the form it derives from the figures instead, and warns."
          },
          "c": {
            "type": "number",
            "description": "Per MPa, for strain as a fraction."
          },
          "lesserStrain": {
            "description": "The least compressive principal strain ε3 over the failures the fit was made from: compression positive, so the lateral extension of a triaxial test is negative. A model warns where it reads the curve outside it; an empty range covers nothing.",
            "$ref": "#/components/schemas/Range"
          },
          "greaterStrain": {
            "description": "The greatest compressive principal strain ε1 over the same failures. The curve has two limbs, and at a lesser strain inside its range the other limb was not fitted: with this, a model can tell which it read.",
            "$ref": "#/components/schemas/Range"
          },
          "basis": {
            "type": "string",
            "enum": [
              "regression",
              "derived"
            ],
            "description": "Whether the constants were regressed from measured failure strains (their eq. 14) or derived from E, μ and m (their eqs 5–7)."
          },
          "note": {
            "description": "Where the failure strains or figures came from, for a reader.",
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "sigmaCiMPa",
          "a",
          "b",
          "c",
          "basis"
        ],
        "description": "A strain-based Hoek–Brown criterion of the intact rock as Yang and Green (2025) write it, eq. 4: ε1 = ε3 ± (a·σci·ε3 + b·σci²)^0.5 + c·σci, strain a dimensionless fraction, compression positive. A model judging damage reads it on a state's greatest and least compressive principal strains, the middle one ignored, with no tension cutoff beyond the curve's own, in place of the strength it would derive from the domain's figures: so a criterion derived from the same E, μ and m need not answer as the figures do."
      },
      "StrainField": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "strain_field"
          },
          "grid": {
            "$ref": "#/components/schemas/Grid"
          },
          "depthM": {
            "type": "number",
            "minimum": 0,
            "description": "How far below the grid's plane the field was read, m, the plane being the charged holes' mean collar height for `collar_plane`: the strain the rock sees at the charges' level, not at the surface."
          },
          "columns": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991,
            "description": "Nodes along x: ⌊(maxX − minX) / cellM⌋ + 1."
          },
          "rows": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991,
            "description": "Nodes along y: ⌊(maxY − minY) / cellM⌋ + 1."
          },
          "valuesMicrostrain": {
            "anyOf": [
              {
                "maxItems": 4000000,
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "number",
                      "minimum": 0
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              {
                "$ref": "#/components/schemas/BlobRef"
              }
            ],
            "description": "Peak principal extension of the rock at each cell over the whole blast, microstrain, row-major from the south-west corner; null where the model declines to predict."
          },
          "thresholdMicrostrain": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The extension at which a plane P wave reaches the model's criterion, for a viewer to contour: the strain of the PPV its `damage_envelope` reports as `thresholdMmS`."
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "grid",
          "depthM",
          "columns",
          "rows",
          "valuesMicrostrain",
          "thresholdMicrostrain",
          "validity"
        ],
        "description": "Peak extension of the rock over a plan-view grid at a depth: where a blast strains the rock past its strength, and by how much."
      },
      "StrengthStage": {
        "type": "string",
        "enum": [
          "crack_initiation",
          "strain_localisation",
          "peak"
        ],
        "description": "A stage of the progressive failure of intact rock, from the first cracks to the peak load."
      },
      "SucceededRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "modelId": {
            "$ref": "#/components/schemas/ModelId"
          },
          "modelVersion": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
            "description": "A semantic version. A result is always pinned to the exact version that produced it."
          },
          "caseId": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "A content hash: `sha256:` followed by 64 lower-case hex digits."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
            "description": "An ISO 8601 timestamp in UTC, for example `2026-09-26T04:12:00Z`."
          },
          "status": {
            "type": "string",
            "const": "succeeded"
          },
          "result": {
            "$ref": "#/components/schemas/ModelResult"
          }
        },
        "required": [
          "id",
          "modelId",
          "modelVersion",
          "caseId",
          "createdAt",
          "updatedAt",
          "status",
          "result"
        ],
        "description": "Finished with the model’s result."
      },
      "SurfacePosition": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "description": "Easting, metres."
          },
          "y": {
            "type": "number",
            "description": "Northing, metres."
          },
          "z": {
            "description": "Elevation, metres. Up is positive. Left out where the height was never surveyed: unknown, not zero.",
            "type": "number"
          }
        },
        "required": [
          "x",
          "y"
        ],
        "description": "A place on the ground in the case coordinate system, metres, whose elevation may not be known."
      },
      "TimingProposal": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "const": "timing_proposal"
          },
          "objective": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "What the proposal optimises, for example `minimise P95 PPV at receiver R3`."
          },
          "holes": {
            "maxItems": 5000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "holeId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "totalMs": {
                  "type": "number"
                }
              },
              "required": [
                "holeId",
                "totalMs"
              ]
            }
          },
          "expected": {
            "description": "What the model expects the proposed timing to achieve.",
            "maxItems": 1000,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "receiverId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
                  "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
                },
                "ppvMmS": {
                  "$ref": "#/components/schemas/Estimate"
                }
              },
              "required": [
                "receiverId",
                "ppvMmS"
              ]
            }
          },
          "validity": {
            "$ref": "#/components/schemas/Validity"
          }
        },
        "required": [
          "kind",
          "objective",
          "holes",
          "validity"
        ],
        "description": "A proposed set of firing times."
      },
      "Unavailable": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ReceiverUnavailable"
          },
          {
            "$ref": "#/components/schemas/CaseUnavailable"
          }
        ],
        "description": "A requested output the model declined to produce, and why."
      },
      "UnchargedDeck": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "stemming",
              "air",
              "water",
              "inert"
            ],
            "description": "What fills this interval of the hole."
          },
          "topDepthM": {
            "type": "number",
            "minimum": 0,
            "description": "Depth of the top of the deck along the hole, from the collar."
          },
          "bottomDepthM": {
            "type": "number",
            "minimum": 0,
            "description": "Depth of the bottom of the deck along the hole."
          }
        },
        "required": [
          "kind",
          "topDepthM",
          "bottomDepthM"
        ],
        "description": "An interval of a hole with no explosive in it."
      },
      "Validity": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "within",
              "extrapolated",
              "outside"
            ],
            "description": "Whether the inputs sit inside the range the model was validated on."
          },
          "code": {
            "description": "Which way it is outside, as a stable code: for example `below_fitted_range`, `above_fitted_range`, `near_field`.",
            "type": "string",
            "maxLength": 60,
            "pattern": "^[a-z][a-z0-9_]*$"
          },
          "note": {
            "type": "string",
            "maxLength": 500
          }
        },
        "required": [
          "status"
        ],
        "description": "How far to trust one output."
      },
      "VodMeasurement": {
        "type": "object",
        "properties": {
          "holeId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "productId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
            "description": "An opaque identifier: letters, digits, `.`, `_`, `:` and `-`, up to 128 characters."
          },
          "vodMs": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Measured velocity of detonation, m/s."
          },
          "method": {
            "type": "string",
            "enum": [
              "continuous_resistance",
              "point_to_point",
              "optical"
            ]
          }
        },
        "required": [
          "holeId",
          "vodMs",
          "method"
        ],
        "description": "A measured velocity of detonation."
      },
      "Warning": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "maxLength": 80,
            "pattern": "^[a-z][a-z0-9_.-]*$",
            "description": "A stable machine-readable code. Reserved: `interval.approximate`, the intervals on this result are approximate (for example, the fit came without its leverage statistics); `target` narrows it to a receiver. Reserved for a diagnosis: `diagnosis.not_converged`, the fit had not settled when it stopped; `diagnosis.single_record`, only one record was read, so nothing cross-checks it; `diagnosis.det_log_disagrees`, a hole the firing record says did not fire shows in the records; `diagnosis.heading_unknown`, no record’s heading is known, so shear is not read."
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000
          },
          "target": {
            "description": "What the warning is about: a hole id, a receiver id or a field path.",
            "type": "string",
            "maxLength": 200
          }
        },
        "required": [
          "code",
          "message"
        ],
        "description": "Something the model wants a person to see."
      },
      "WebhookEvent": {
        "$ref": "#/components/schemas/WebhookEvent"
      }
    },
    "securitySchemes": {
      "shockaiSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "ShockAI-Signature",
        "description": "`t=<unix seconds>,kid=<RFC 7638 thumbprint>,ed25519=<base64url signature>`. The signature is Ed25519 over six lines joined by `\\n`: `shockai-model-api-v0`, the timestamp, the method, the target URL (lowercase origin, then path and query), the `Idempotency-Key` or an empty line, and the hex SHA-256 of the raw body. Requests are signed with the platform's key, published as JWKs at `https://app.shockai.co/developers/keys/v0.json`; callbacks with yours. Pick the key by `kid`, and reject a timestamp more than 300 seconds from now."
      }
    },
    "examples": {
      "manifest": {
        "summary": "The platform’s site-law model",
        "externalValue": "../examples/v0/manifest.json"
      },
      "blastCase": {
        "summary": "A 32-hole bench blast",
        "externalValue": "../examples/v0/blast-case.json"
      },
      "run": {
        "summary": "A finished sync run",
        "externalValue": "../examples/v0/run.json"
      }
    }
  }
}
