{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://ruchitsuthar.com/architecture/spine.schema.json",
  "title": "The Spine — architecture spec, machine layer",
  "description": "Schema for arch/spine.yaml: the machine-readable layer of a decision-first architecture spec. Human prose lives in the sibling Markdown files; this file holds only what a build, a check or a coding agent needs to act on.",
  "type": "object",
  "required": ["spine", "system", "quality_goals", "decisions", "review"],
  "additionalProperties": false,
  "properties": {
    "spine": {
      "description": "Schema version this file targets. 0.1 files remain valid: 0.2 only added the optional `context` array.",
      "enum": ["0.1", "0.2"]
    },
    "system": {
      "type": "object",
      "required": ["name", "purpose"],
      "additionalProperties": false,
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "purpose": {
          "type": "string",
          "description": "One sentence. What this system is for, in the language of whoever pays for it.",
          "minLength": 1
        },
        "owners": {
          "type": "array",
          "description": "Teams or handles accountable for the architecture, not the codebase.",
          "items": { "type": "string" }
        },
        "lifecycle": {
          "enum": ["greenfield", "growing", "steady", "sunsetting"],
          "description": "Changes how much of this file is worth filling in. A sunsetting system needs risks and decisions; it does not need a strategy."
        }
      }
    },
    "quality_goals": {
      "type": "array",
      "description": "Three to five. A system that is good at nine things is good at nothing; the ranking is the useful part.",
      "minItems": 1,
      "maxItems": 7,
      "items": {
        "type": "object",
        "required": ["id", "attribute", "scenario"],
        "additionalProperties": false,
        "properties": {
          "id": { "$ref": "#/$defs/qgId" },
          "attribute": {
            "type": "string",
            "description": "The quality attribute — availability, latency, changeability, operability, cost, answer quality."
          },
          "scenario": {
            "type": "string",
            "description": "Stimulus, environment, measurable response. 'Fast' is not a scenario; 'p99 read under 200ms at 3x peak' is."
          },
          "priority": {
            "type": "integer",
            "minimum": 1,
            "description": "1 is highest. Ties are not allowed — a tie means the trade-off has not been made yet."
          },
          "guardrails": {
            "type": "array",
            "description": "Guardrail ids that make this goal fail loudly when violated. Empty means the goal is a wish.",
            "items": { "$ref": "#/$defs/gId" }
          }
        }
      }
    },
    "constraints": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "constraint", "type"],
        "additionalProperties": false,
        "properties": {
          "id": { "$ref": "#/$defs/cId" },
          "constraint": { "type": "string", "minLength": 1 },
          "type": { "enum": ["technical", "organisational", "regulatory", "commercial"] },
          "rationale": { "type": "string" },
          "expires": {
            "type": "string",
            "format": "date",
            "description": "The date this stops being true, if it is knowable. Constraints without expiry dates outlive their reasons."
          }
        }
      }
    },
    "context": {
      "type": "array",
      "description": "The systems on the other side of the boundary. Only the fields a check or a coding agent acts on — the diagram and the prose live in 05-context.md.",
      "items": {
        "type": "object",
        "required": ["id", "system", "direction"],
        "additionalProperties": false,
        "properties": {
          "id": { "$ref": "#/$defs/extId" },
          "system": { "type": "string", "minLength": 1 },
          "direction": {
            "enum": ["inbound", "outbound", "bidirectional"],
            "description": "Who initiates. 'inbound' means they call you."
          },
          "protocol": {
            "type": "string",
            "description": "How it is reached: https/rest, grpc, kafka, sftp, jdbc."
          },
          "owner": {
            "type": "string",
            "description": "Team or vendor to ask when it changes. Not the codebase owner."
          },
          "trust": {
            "enum": ["trusted", "semi-trusted", "untrusted"],
            "description": "Decides how hard the boundary validates. Anything outside your deploy unit is at best semi-trusted."
          },
          "on_failure": {
            "enum": ["fail", "degrade", "queue", "ignore"],
            "description": "What this system does when that one is unavailable or wrong. The field that stops an agent inventing a retry policy."
          },
          "interface": {
            "type": "string",
            "description": "Where the boundary is implemented in this repo. Calls to this system belong nowhere else, which makes it guardrail-able."
          },
          "data": {
            "type": "string",
            "description": "What crosses, in the terms a residency or retention constraint would use."
          },
          "constrained_by": {
            "type": "array",
            "description": "Constraint ids that apply to this exchange — residency, retention, contractual response times.",
            "items": { "$ref": "#/$defs/cId" }
          }
        }
      }
    },
    "decisions": {
      "type": "array",
      "description": "The spine. Views, diagrams and structure are derived from these.",
      "items": {
        "type": "object",
        "required": ["id", "title", "status", "date"],
        "additionalProperties": false,
        "properties": {
          "id": { "$ref": "#/$defs/adrId" },
          "title": { "type": "string", "minLength": 1 },
          "status": { "enum": ["proposed", "accepted", "superseded", "deprecated"] },
          "date": { "type": "string", "format": "date" },
          "serves": {
            "type": "array",
            "description": "Quality goal ids this decision buys. A decision that serves nothing is a preference.",
            "items": { "$ref": "#/$defs/qgId" }
          },
          "supersedes": { "type": "array", "items": { "$ref": "#/$defs/adrId" } },
          "superseded_by": { "$ref": "#/$defs/adrId" },
          "file": {
            "type": "string",
            "description": "Path to the full ADR, relative to arch/.",
            "pattern": "^decisions/"
          }
        }
      }
    },
    "guardrails": {
      "type": "array",
      "description": "The enforcement layer. Each entry is a check that can fail a build, a deploy or an eval run.",
      "items": {
        "type": "object",
        "required": ["id", "checks", "kind", "rule"],
        "additionalProperties": false,
        "properties": {
          "id": { "$ref": "#/$defs/gId" },
          "checks": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/$defs/qgId" }
          },
          "kind": {
            "enum": ["build", "test", "runtime", "eval", "manual"],
            "description": "'manual' is honest and allowed — but it decays, so it should be a stated exception rather than the norm."
          },
          "rule": { "type": "string", "minLength": 1 },
          "threshold": { "type": "string" },
          "enforced_by": {
            "type": "string",
            "description": "The concrete thing that runs it: a CI job, a lint rule, an alert, an eval suite."
          },
          "on_breach": { "enum": ["fail", "warn"], "default": "fail" }
        }
      }
    },
    "agent_rules": {
      "type": "object",
      "description": "What a coding agent must and must not do in this repo. Read as guardrails, not as prose.",
      "additionalProperties": false,
      "properties": {
        "must": { "type": "array", "items": { "type": "string" } },
        "must_not": { "type": "array", "items": { "type": "string" } },
        "escalate": {
          "type": "array",
          "description": "Situations where the correct agent behaviour is to stop and open an ADR instead of choosing.",
          "items": { "type": "string" }
        }
      }
    },
    "vocabulary": {
      "type": "array",
      "description": "Terms this system uses, and terms it refuses. The highest-leverage few lines in the file when agents are generating names.",
      "items": {
        "type": "object",
        "required": ["term", "means"],
        "additionalProperties": false,
        "properties": {
          "term": { "type": "string" },
          "means": { "type": "string" },
          "not": { "type": "string", "description": "What people wrongly assume it means here." }
        }
      }
    },
    "risks": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "risk"],
        "additionalProperties": false,
        "properties": {
          "id": { "type": "string", "pattern": "^R-[0-9]+$" },
          "risk": { "type": "string", "minLength": 1 },
          "impact": { "enum": ["low", "medium", "high", "existential"] },
          "owner": { "type": "string" },
          "mitigation": { "type": "string" },
          "accepted_until": { "type": "string", "format": "date" }
        }
      }
    },
    "review": {
      "type": "object",
      "required": ["last", "cadence"],
      "additionalProperties": false,
      "properties": {
        "last": { "type": "string", "format": "date" },
        "cadence": { "enum": ["monthly", "quarterly", "half-yearly", "yearly"] },
        "next": { "type": "string", "format": "date" }
      }
    }
  },
  "$defs": {
    "qgId": { "type": "string", "pattern": "^QG-[0-9]+$" },
    "cId": { "type": "string", "pattern": "^C-[0-9]+$" },
    "extId": { "type": "string", "pattern": "^EXT-[0-9]+$" },
    "gId": { "type": "string", "pattern": "^G-[0-9]+$" },
    "adrId": { "type": "string", "pattern": "^ADR-[0-9]{4}$" }
  }
}
