{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://ailfish.org/wall/response.schema.json",
  "title": "ailfish wall: one visit (scratch) response",
  "description": "What the wall returns for one scratch through one door. Produced today by the page and by `node cli.mjs scratch ... --json` (offline demo classifier); the planned POST /wall/api/scratch returns the same object plus `jev_mode` and `store`. Example: https://ailfish.org/wall/example-response.json. The scratch itself never appears in a response (leak guard).",
  "type": "object",
  "required": ["door", "door_sha256", "engine", "n", "rows", "chain", "delivery", "columns", "unresolved", "unmarked", "insideWobble", "baseline", "matured", "calls", "stored", "text"],
  "properties": {
    "wall": { "type": "string", "description": "wall id, e.g. wall-ailfish" },
    "door": { "enum": ["read", "blind", "reversed"], "description": "read: classified as written; blind: letters to x, digits to 9; reversed: characters in reverse order" },
    "door_sha256": { "$ref": "#/$defs/sha256", "description": "sha256 of the canonical door (preamble, every criterion, mask, baseline inputs, engine version). Results are comparable only under the same door hash." },
    "engine": { "type": "string", "description": "engine version, e.g. ailfish-wall/0.1.0" },
    "n": { "type": "integer", "minimum": 1, "description": "visit number in this room for this door" },
    "rows": { "$ref": "#/$defs/rows", "description": "composite cell id per column (leader, or the superposed top cell if no leader)" },
    "chain": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/chain" }], "description": "the pose-frame delta-chain, present only when delivery = now" },
    "delivery": { "enum": ["now", "held", "folded", "silence"], "description": "now: chain delivered; held: delivered to a later visit (see matured); folded: only a fold hash in the receipt; silence: delay chose none, the answer is []" },
    "columns": {
      "type": "object",
      "description": "per column (anchor, sign, amp, trajectory, rhythm, seam, delay, receipt): the full cloud and its honesty numbers",
      "additionalProperties": { "$ref": "#/$defs/column" }
    },
    "unresolved": { "type": "array", "items": { "type": "string" }, "description": "columns with no leader" },
    "unmarked": { "type": "array", "items": { "type": "string" }, "description": "columns whose composite is the none cell" },
    "insideWobble": { "type": "array", "items": { "type": "string" }, "description": "columns where every shift is smaller than the junk wobble: not distinguishable from nothing" },
    "baseline": {
      "type": "object",
      "required": ["inputs", "runs", "t", "spread", "fresh"],
      "properties": {
        "inputs": { "type": "integer", "description": "number of empty/junk inputs (6)" },
        "runs": { "type": "integer", "description": "runs per input (3)" },
        "t": { "type": "integer", "description": "when the baseline was measured, ms since epoch (UTC)" },
        "spread": { "type": "object", "additionalProperties": { "type": "number" }, "description": "junk wobble per column" },
        "fresh": { "type": "boolean", "description": "true if measured on this visit (costs 18 extra classifier calls)" }
      }
    },
    "matured": {
      "type": "array",
      "description": "chains held by earlier visits and delivered on this one",
      "items": { "type": "object", "required": ["from", "chain"], "properties": { "from": { "type": "integer" }, "chain": { "$ref": "#/$defs/chain" } } }
    },
    "calls": {
      "type": "array",
      "description": "one entry per classifier run of this scratch (3)",
      "items": {
        "type": "object",
        "required": ["run", "questions", "model"],
        "properties": {
          "run": { "type": "integer" },
          "questions": { "type": "integer" },
          "ms": { "type": "number" },
          "model": { "type": "string", "description": "e.g. fake-jev+jitter(run 0) in demo; jev-1.13.0 when live" },
          "usage": { "type": "object", "properties": { "input_tokens": { "type": "integer" }, "output_tokens": { "type": "integer" } } }
        }
      }
    },
    "stored": {
      "type": "object",
      "required": ["record", "sha256"],
      "description": "exactly what was stored (cell ids and numbers, never the scratch) and the sha256 of its JSON",
      "properties": { "record": { "$ref": "#/$defs/record" }, "sha256": { "$ref": "#/$defs/sha256" } }
    },
    "text": { "type": "string", "description": "the plain-text receipt, identical to the command-line output" },
    "jev_mode": { "enum": ["fake", "live"], "description": "API only: which classifier answered" },
    "store": { "type": "string", "description": "API only: which state store holds the room" }
  },
  "$defs": {
    "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
    "rows": { "type": "object", "additionalProperties": { "type": "string" } },
    "pose": {
      "type": "object",
      "required": ["id", "anchor", "amp", "sign"],
      "properties": {
        "id": { "type": "string" },
        "anchor": { "type": "array", "items": { "type": "number" }, "minItems": 3, "maxItems": 3, "description": "[x left/right, y down/up, z along the wire]" },
        "amp": { "type": "number", "minimum": 0, "maximum": 1 },
        "sign": { "enum": [1, -1] }
      }
    },
    "chain": {
      "type": "object",
      "description": "valid against https://ailfish.org/verify/schema/chain.schema.json",
      "required": ["silence_before", "silence_after", "frames"],
      "properties": {
        "silence_before": { "type": "boolean" },
        "silence_after": { "type": "boolean" },
        "frames": { "type": "array", "minItems": 2, "maxItems": 4, "items": { "type": "array", "maxItems": 5, "items": { "$ref": "#/$defs/pose" } } },
        "meta": { "type": "object" }
      }
    },
    "cell": {
      "type": "object",
      "required": ["id", "base", "raw", "shift", "temper", "history", "final"],
      "properties": {
        "id": { "type": "string" },
        "base": { "type": "number", "description": "zero-baseline weight" },
        "raw": { "type": "number", "description": "mean raw weight for this scratch" },
        "shift": { "type": "number", "description": "raw - base" },
        "temper": { "type": "number", "description": "softening step" },
        "history": { "type": "number", "description": "push from room history (applied after the classifier)" },
        "final": { "type": "number", "description": "final weight; final - base = shift + temper + history" }
      }
    },
    "column": {
      "type": "object",
      "required": ["composite", "leader", "agree", "of", "gap", "tie", "stable", "runnerUp", "cells", "history"],
      "properties": {
        "composite": { "type": "string" },
        "leader": { "type": ["string", "null"], "description": "named only if all runs agree and gap >= 0.05" },
        "agree": { "type": "integer" },
        "of": { "type": "integer" },
        "gap": { "type": "number" },
        "tie": { "type": "boolean", "description": "gap < 0.05" },
        "stable": { "type": "boolean", "description": "all runs agree on the top cell" },
        "runnerUp": { "type": "string" },
        "pull": { "type": "string", "description": "cell with the strongest pull vs nothing" },
        "runTops": { "type": "array", "items": { "type": "string" } },
        "cells": { "type": "array", "items": { "$ref": "#/$defs/cell" } },
        "entropy": { "type": "number" },
        "history": {
          "type": "object",
          "properties": { "tv": { "type": "number" }, "burned": { "type": "array", "items": { "type": "string" } }, "interfered": { "type": "boolean" }, "records": { "type": "integer" } }
        }
      }
    },
    "record": {
      "type": "object",
      "required": ["t", "n", "door", "door_sha12", "rows", "top", "unresolved", "collapsed"],
      "properties": {
        "t": { "type": "integer" },
        "n": { "type": "integer" },
        "door": { "type": "string" },
        "door_sha12": { "type": "string", "pattern": "^[0-9a-f]{12}$" },
        "rows": { "$ref": "#/$defs/rows" },
        "top": { "type": "object", "additionalProperties": { "type": "number" } },
        "unresolved": { "type": "array", "items": { "type": "string" } },
        "collapsed": { "type": "boolean" }
      }
    },
    "aborted": {
      "description": "returned instead of a visit when nothing was stored (e.g. leak guard, scratch too long, nothing to collapse); HTTP 422 from the API",
      "type": "object", "required": ["aborted", "reason"],
      "properties": { "aborted": { "const": true }, "reason": { "type": "string" } }
    },
    "error": {
      "description": "API errors (400, 404, 413, 429, 503); 429 carries Retry-After",
      "type": "object", "required": ["error"],
      "properties": { "error": { "type": "string" }, "retry_after_s": { "type": "integer" } }
    }
  }
}
