{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://gitreal.dev/spec/completion-claim-v0.1.schema.json",
  "title": "Completion Claim v0.1",
  "description": "An agent's assertion that a task is done, plus evidence collected from the environment after the fact, plus a verifier's verdict. Prose spec: https://gitreal.dev/spec",
  "type": "object",
  "required": ["spec_version", "task", "claimed_outcome", "mutations", "evidence", "verdict"],
  "additionalProperties": false,
  "properties": {
    "spec_version": { "const": "0.1" },
    "task": {
      "type": "object",
      "required": ["id", "description"],
      "properties": {
        "id": { "type": "string", "maxLength": 200 },
        "description": { "type": "string", "maxLength": 2000 },
        "requested_by": { "type": "string", "maxLength": 200 }
      }
    },
    "claimed_outcome": {
      "type": "string",
      "maxLength": 8000,
      "description": "What the agent says it did. Recorded, never trusted, never scored."
    },
    "mutations": {
      "type": "array",
      "description": "Every change the agent believes it made to the world.",
      "items": {
        "type": "object",
        "required": ["id", "kind", "target", "at"],
        "additionalProperties": false,
        "properties": {
          "id": { "type": "string", "maxLength": 64 },
          "kind": {
            "enum": ["state_write", "file_change", "deployment", "message_sent", "other"]
          },
          "target": { "type": "string", "maxLength": 500 },
          "at": { "type": "string", "format": "date-time" }
        }
      }
    },
    "evidence": {
      "type": "array",
      "description": "Observations of the world. Must be collected strictly after every mutation they cover.",
      "items": {
        "type": "object",
        "required": ["check_type", "target", "observed_value", "at", "covers", "verifier"],
        "additionalProperties": false,
        "properties": {
          "check_type": {
            "enum": ["test_run", "endpoint_health", "state_readback"],
            "description": "v0.1 defines exactly three. A fourth for browser/DOM assertions is an open question, not yet specified."
          },
          "target": { "type": "string", "maxLength": 500 },
          "expected": {
            "description": "What would count as the world having changed. Omit only for checks with no assertion, which are weak evidence."
          },
          "observed_value": { "description": "What was actually seen." },
          "at": { "type": "string", "format": "date-time" },
          "covers": {
            "type": "array",
            "items": { "type": "string", "maxLength": 64 },
            "description": "mutation ids this evidence attests to"
          },
          "verifier": {
            "type": "object",
            "required": ["id", "kind"],
            "additionalProperties": false,
            "properties": {
              "id": { "type": "string", "maxLength": 200 },
              "kind": {
                "enum": ["independent", "self_attested", "absent"],
                "description": "self_attested NEVER satisfies a policy requirement. An agent reporting that it checked its own work is a second claim, not evidence."
              }
            }
          }
        }
      }
    },
    "policy": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string", "maxLength": 200 },
        "requires": {
          "type": "array",
          "items": { "enum": ["test_run", "endpoint_health", "state_readback"] }
        },
        "max_age_s": { "type": "integer", "minimum": 0 },
        "on_unverified": { "enum": ["block", "warn", "record"], "default": "record" }
      }
    },
    "verdict": {
      "type": "object",
      "required": ["state"],
      "additionalProperties": false,
      "description": "Computed by the verifier. Never written by the agent being verified.",
      "properties": {
        "state": {
          "enum": ["verified", "unverified", "refuted"],
          "description": "unverified means unproven, not passed. Absence of evidence never yields verified."
        },
        "unmet": { "type": "array", "items": { "type": "string" } },
        "reason": { "type": ["string", "null"], "maxLength": 2000 },
        "decided_at": { "type": "string", "format": "date-time" }
      }
    }
  }
}
