{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://civitas-cerebrum.github.io/element-interactions/contribution-handover.schema.json",
  "title": "Contribution Handover",
  "description": "Structured sign-off captured per-PR against @civitas-cerebrum/element-interactions. Every boolean maps to a hard rule or design rule in skills/contributing-to-element-interactions/SKILL.md. Every false / n/a value must be paired with a specific *Reason field. Methodology rule — the handover file is validated manually against this schema before git push origin / gh pr create; the harness hook that previously enforced this (contribution-handover-gate.sh) was retired in 0.3.6; the rule still applies.",
  "type": "object",
  "additionalProperties": false,
  "required": ["schemaVersion", "pr", "preflight", "design", "tests", "build", "coverage", "docs", "version"],
  "properties": {
    "schemaVersion": {
      "type": "integer",
      "const": 1,
      "description": "Schema major version. Bump when fields are added/removed in incompatible ways."
    },
    "pr": {
      "type": "object",
      "additionalProperties": false,
      "required": ["title", "branch", "summary"],
      "properties": {
        "title": { "type": "string", "minLength": 5 },
        "branch": { "type": "string", "minLength": 1 },
        "summary": { "type": "string", "minLength": 20, "description": "1–3 sentences describing what shipped and why." }
      }
    },
    "preflight": {
      "type": "object",
      "additionalProperties": false,
      "description": "Hard Rule: Before filing an issue or opening a PR — duplicate check + sync status.",
      "required": ["branchSyncedWithMain", "duplicateIssuesSearched", "duplicatePRsSearched", "depVersionsChecked"],
      "properties": {
        "branchSyncedWithMain": { "$ref": "#/$defs/checkOrReason" },
        "duplicateIssuesSearched": { "$ref": "#/$defs/checkOrReason" },
        "duplicatePRsSearched": { "$ref": "#/$defs/checkOrReason" },
        "depVersionsChecked": { "$ref": "#/$defs/checkOrReason" },
        "branchSyncedWithMainReason": { "type": "string" },
        "duplicateIssuesSearchedReason": { "type": "string" },
        "duplicatePRsSearchedReason": { "type": "string" },
        "depVersionsCheckedReason": { "type": "string" }
      }
    },
    "design": {
      "type": "object",
      "additionalProperties": false,
      "description": "Design rules (1–18) the change must respect. n/a is allowed when the rule does not apply (e.g. presenceDetect on a verification-only PR) but requires a reason.",
      "required": [
        "argumentOrderConvention",
        "asyncEverywhere",
        "stepsKeptLightweight",
        "namingConvention",
        "noRawLocatorInSrc",
        "presenceDetectInActions",
        "webOnlyCastAtSite",
        "errorMessageFormatFollowed",
        "loggingPresent",
        "typescriptDiscipline"
      ],
      "properties": {
        "argumentOrderConvention": { "$ref": "#/$defs/checkOrReason" },
        "asyncEverywhere": { "$ref": "#/$defs/checkOrReason" },
        "stepsKeptLightweight": { "$ref": "#/$defs/checkOrReason" },
        "namingConvention": { "$ref": "#/$defs/checkOrReason" },
        "noRawLocatorInSrc": { "$ref": "#/$defs/checkOrReason" },
        "presenceDetectInActions": { "$ref": "#/$defs/checkOrReason" },
        "webOnlyCastAtSite": { "$ref": "#/$defs/checkOrReason" },
        "errorMessageFormatFollowed": { "$ref": "#/$defs/checkOrReason" },
        "loggingPresent": { "$ref": "#/$defs/checkOrReason" },
        "typescriptDiscipline": { "$ref": "#/$defs/checkOrReason" },
        "argumentOrderConventionReason": { "type": "string" },
        "asyncEverywhereReason": { "type": "string" },
        "stepsKeptLightweightReason": { "type": "string" },
        "namingConventionReason": { "type": "string" },
        "noRawLocatorInSrcReason": { "type": "string" },
        "presenceDetectInActionsReason": { "type": "string" },
        "webOnlyCastAtSiteReason": { "type": "string" },
        "errorMessageFormatFollowedReason": { "type": "string" },
        "loggingPresentReason": { "type": "string" },
        "typescriptDisciplineReason": { "type": "string" }
      }
    },
    "tests": {
      "type": "object",
      "additionalProperties": false,
      "description": "Hard Rules: tests hit real Vue test app + assertions are non-tautological.",
      "required": ["implemented", "exerciseRealVueApp", "nonTautologicalAssertions", "passing"],
      "properties": {
        "implemented": { "$ref": "#/$defs/checkOrReason" },
        "exerciseRealVueApp": { "$ref": "#/$defs/checkOrReason" },
        "nonTautologicalAssertions": { "$ref": "#/$defs/checkOrReason" },
        "passing": { "$ref": "#/$defs/checkOrReason" },
        "specFiles": { "type": "array", "items": { "type": "string" }, "description": "New or modified spec files in this PR." },
        "implementedReason": { "type": "string" },
        "exerciseRealVueAppReason": { "type": "string" },
        "nonTautologicalAssertionsReason": { "type": "string" },
        "passingReason": { "type": "string" }
      }
    },
    "build": {
      "type": "object",
      "additionalProperties": false,
      "required": ["buildPasses", "fullSuitePassing"],
      "properties": {
        "buildPasses": { "$ref": "#/$defs/checkOrReason" },
        "fullSuitePassing": { "$ref": "#/$defs/checkOrReason" },
        "knownFailures": { "type": "string", "description": "Any pre-existing failures observed locally that are unrelated to this PR (e.g. docker-compose-backed tests not running)." },
        "buildPassesReason": { "type": "string" },
        "fullSuitePassingReason": { "type": "string" }
      }
    },
    "coverage": {
      "type": "object",
      "additionalProperties": false,
      "description": "Hard Rule: 100% API coverage gate.",
      "required": ["apiCoverageGate100"],
      "properties": {
        "apiCoverageGate100": { "$ref": "#/$defs/checkOrReason" },
        "apiCoverageGate100Reason": { "type": "string" }
      }
    },
    "docs": {
      "type": "object",
      "additionalProperties": false,
      "description": "Rule 19 — README + api-reference are mandatory for any new public API. skill files are required only when workflow/rules change.",
      "required": ["readmeUpdated", "apiReferenceUpdated", "skillFilesUpdated"],
      "properties": {
        "readmeUpdated": { "$ref": "#/$defs/checkOrReason" },
        "apiReferenceUpdated": { "$ref": "#/$defs/checkOrReason" },
        "skillFilesUpdated": { "$ref": "#/$defs/checkOrReason" },
        "readmeUpdatedReason": { "type": "string" },
        "apiReferenceUpdatedReason": { "type": "string" },
        "skillFilesUpdatedReason": { "type": "string" }
      }
    },
    "version": {
      "type": "object",
      "additionalProperties": false,
      "description": "Rule 15 — single patch bump per PR.",
      "required": ["patchBumpedOnce", "from", "to"],
      "properties": {
        "patchBumpedOnce": { "$ref": "#/$defs/checkOrReason" },
        "from": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
        "to":   { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
        "patchBumpedOnceReason": { "type": "string" }
      }
    }
  },
  "$defs": {
    "checkOrReason": {
      "description": "true = signed off as compliant; false = explicitly violated and a *Reason field must justify; \"n/a\" = rule does not apply and a *Reason field must explain why.",
      "oneOf": [
        { "type": "boolean" },
        { "type": "string", "const": "n/a" }
      ]
    }
  }
}
