{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://barglabs.ai/dunstan/spec/schema/record-0.1.schema.json",
  "title": "Dunstan record, v0.1",
  "description": "An in-toto Statement v1 whose predicate records one check of a handback block against the record. See spec/claim-format.md.",
  "type": "object",
  "required": ["_type", "subject", "predicateType", "predicate"],
  "additionalProperties": false,
  "properties": {
    "_type": { "const": "https://in-toto.io/Statement/v1" },
    "subject": {
      "type": "array",
      "minItems": 1,
      "maxItems": 2,
      "prefixItems": [{ "$ref": "#/$defs/commitSubject" }, { "$ref": "#/$defs/blockSubject" }],
      "items": false
    },
    "predicateType": { "const": "https://barglabs.ai/dunstan/record/v0.1" },
    "predicate": { "$ref": "#/$defs/predicate" }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "predicate": {
            "properties": { "block": { "properties": { "status": { "const": "found" } } } }
          }
        }
      },
      "then": { "properties": { "subject": { "minItems": 2 } } },
      "else": { "properties": { "subject": { "maxItems": 1 } } }
    }
  ],
  "$defs": {
    "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
    "commitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
    "repository": {
      "description": "owner/repo",
      "type": "string",
      "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}$"
    },
    "issueRef": {
      "description": "owner/repo#N, always fully qualified in evidence.",
      "type": "string",
      "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,9}$"
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]+)?Z$"
    },
    "sourceKind": {
      "enum": [
        "pull_request",
        "pull_request_files",
        "closing_references",
        "repository",
        "issue",
        "commit",
        "compare",
        "check_runs",
        "workflow_runs",
        "artifact",
        "deployments"
      ]
    },
    "commitSubject": {
      "type": "object",
      "required": ["name", "digest"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "pattern": "^git\\+https://github\\.com/[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})/[A-Za-z0-9._-]{1,100}@[0-9a-f]{40}$"
        },
        "digest": {
          "type": "object",
          "required": ["gitCommit"],
          "additionalProperties": false,
          "properties": { "gitCommit": { "$ref": "#/$defs/commitSha" } }
        }
      }
    },
    "blockSubject": {
      "type": "object",
      "required": ["name", "digest"],
      "additionalProperties": false,
      "properties": {
        "name": { "const": "handback-block" },
        "digest": {
          "type": "object",
          "required": ["sha256"],
          "additionalProperties": false,
          "properties": { "sha256": { "$ref": "#/$defs/sha256" } }
        }
      }
    },
    "predicate": {
      "type": "object",
      "required": [
        "spec",
        "checker",
        "report",
        "block",
        "subject",
        "evidence",
        "claims",
        "verdict",
        "digests",
        "rerun",
        "assurance"
      ],
      "additionalProperties": false,
      "properties": {
        "spec": {
          "description": "The spec version the checker implements, semver.",
          "type": "string",
          "pattern": "^0\\.1\\.(?:0|[1-9][0-9]*)$"
        },
        "checker": {
          "type": "object",
          "required": ["name", "version", "digest"],
          "additionalProperties": false,
          "properties": {
            "name": { "type": "string", "minLength": 1, "maxLength": 214 },
            "version": { "type": "string", "minLength": 1, "maxLength": 100 },
            "digest": {
              "type": "object",
              "required": ["sha256"],
              "additionalProperties": false,
              "properties": { "sha256": { "$ref": "#/$defs/sha256" } }
            }
          }
        },
        "report": {
          "description": "The report's digest and where it was read. Never its text.",
          "type": "object",
          "required": ["sha256", "source"],
          "additionalProperties": false,
          "properties": {
            "sha256": { "$ref": "#/$defs/sha256" },
            "source": {
              "type": "object",
              "required": ["kind", "locator"],
              "additionalProperties": false,
              "properties": {
                "kind": { "enum": ["pr-body", "pr-comment", "file", "stdin", "api"] },
                "locator": { "type": "string", "minLength": 1, "maxLength": 2000 }
              }
            }
          }
        },
        "block": { "$ref": "#/$defs/block" },
        "subject": {
          "type": "object",
          "required": ["repository", "pullRequest", "headSha", "mergeSha"],
          "additionalProperties": false,
          "properties": {
            "repository": { "$ref": "#/$defs/repository" },
            "pullRequest": { "type": "integer", "minimum": 1 },
            "headSha": { "$ref": "#/$defs/commitSha" },
            "mergeSha": {
              "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }]
            }
          }
        },
        "evidence": { "$ref": "#/$defs/evidence" },
        "claims": {
          "type": "array",
          "items": { "$ref": "#/$defs/claim" }
        },
        "verdict": { "$ref": "#/$defs/verdict" },
        "digests": {
          "type": "object",
          "required": ["claims", "evidence"],
          "additionalProperties": false,
          "properties": {
            "claims": { "$ref": "#/$defs/sha256" },
            "evidence": { "$ref": "#/$defs/sha256" }
          }
        },
        "rerun": {
          "type": "object",
          "required": ["offline", "online"],
          "additionalProperties": false,
          "properties": {
            "offline": { "type": "string", "minLength": 1, "maxLength": 2000 },
            "online": { "type": "string", "minLength": 1, "maxLength": 2000 }
          }
        },
        "assurance": {
          "oneOf": [
            {
              "type": "object",
              "required": ["status", "issuer"],
              "additionalProperties": false,
              "properties": {
                "status": { "const": "unsigned" },
                "issuer": { "const": "self-generated" }
              }
            },
            {
              "type": "object",
              "required": ["status", "issuer", "keyFingerprint"],
              "additionalProperties": false,
              "properties": {
                "status": { "const": "signed" },
                "issuer": { "type": "string", "minLength": 1, "maxLength": 320 },
                "keyFingerprint": { "type": "string", "pattern": "^SHA256:[A-Za-z0-9+/]{43}$" }
              }
            }
          ]
        }
      }
    },
    "verdict": { "enum": ["pass", "fail", "unverifiable"] },
    "blockError": {
      "type": "object",
      "required": ["code"],
      "additionalProperties": false,
      "properties": {
        "code": {
          "enum": [
            "unterminated_fence",
            "invalid_json",
            "duplicate_member",
            "unsupported_version",
            "schema_violation"
          ]
        },
        "pointer": { "type": "string", "pattern": "^(?:/.*)?$" },
        "keyword": { "type": "string", "minLength": 1 }
      }
    },
    "block": {
      "oneOf": [
        {
          "type": "object",
          "required": ["status", "sha256", "value"],
          "additionalProperties": false,
          "properties": {
            "status": { "const": "found" },
            "sha256": { "$ref": "#/$defs/sha256" },
            "value": { "$ref": "handback-block-0.1.schema.json" }
          }
        },
        {
          "type": "object",
          "required": ["status", "reason", "sha256", "value"],
          "additionalProperties": false,
          "properties": {
            "status": { "const": "missing" },
            "reason": { "const": "block_missing" },
            "sha256": { "type": "null" },
            "value": { "type": "null" }
          }
        },
        {
          "type": "object",
          "required": ["status", "reason", "count", "sha256", "value"],
          "additionalProperties": false,
          "properties": {
            "status": { "const": "ambiguous" },
            "reason": { "const": "block_ambiguous" },
            "count": { "type": "integer", "minimum": 2 },
            "sha256": { "type": "null" },
            "value": { "type": "null" }
          }
        },
        {
          "type": "object",
          "required": ["status", "reason", "errors", "sha256", "value"],
          "additionalProperties": false,
          "properties": {
            "status": { "const": "invalid" },
            "reason": { "const": "block_invalid" },
            "errors": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/blockError" } },
            "sha256": { "type": "null" },
            "value": { "type": "null" }
          }
        }
      ]
    },
    "claim": {
      "type": "object",
      "required": ["id", "check", "field", "declared", "observed", "verdict"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string", "minLength": 1 },
        "check": { "enum": ["head", "scope", "reference", "count", "time"] },
        "field": {
          "description": "A JSON Pointer (RFC 6901) into the block.",
          "type": "string",
          "pattern": "^/.+$"
        },
        "declared": true,
        "observed": true,
        "verdict": { "$ref": "#/$defs/verdict" },
        "reason": { "$ref": "#/$defs/claimReason" }
      },
      "if": { "properties": { "verdict": { "const": "pass" } } },
      "then": { "not": { "required": ["reason"] } },
      "else": { "required": ["reason"] }
    },
    "claimReason": {
      "type": "string",
      "pattern": "^(?:head_mismatch|declared_not_changed|undeclared_file|file_list_truncated|not_closing|closing_link_unsettled|not_found|not_reachable|count_mismatch|all_succeeded_mismatch|checks_incomplete|no_check_runs|no_comparable_record_field|record_not_found|record_ambiguous|not_merged|premature|no_deployment|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request|pull_request_files|closing_references|repository|issue|commit|compare|check_runs|workflow_runs|artifact|deployments))$"
    },
    "unread": {
      "description": "A section the checker could not use: its source errored, or the source answered without the field.",
      "oneOf": [
        {
          "type": "object",
          "required": ["status", "source"],
          "properties": {
            "status": { "const": "unreadable" },
            "source": { "$ref": "#/$defs/sourceKind" }
          }
        },
        {
          "type": "object",
          "required": ["status", "field"],
          "properties": {
            "status": { "const": "unpopulated" },
            "field": { "type": "string", "pattern": "^[A-Za-z0-9_.]+$" }
          }
        }
      ]
    },
    "evidence": {
      "type": "object",
      "required": ["pullRequest", "sources"],
      "additionalProperties": false,
      "properties": {
        "pullRequest": {
          "description": "Always read: a checker that cannot read the pull request writes no record.",
          "type": "object",
          "required": [
            "status",
            "number",
            "state",
            "merged",
            "mergedAt",
            "headSha",
            "mergeSha",
            "changedFiles"
          ],
          "additionalProperties": false,
          "properties": {
            "status": { "const": "ok" },
            "number": { "type": "integer", "minimum": 1 },
            "state": { "enum": ["open", "closed"] },
            "merged": { "type": "boolean" },
            "mergedAt": { "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }] },
            "headSha": { "$ref": "#/$defs/commitSha" },
            "mergeSha": { "oneOf": [{ "$ref": "#/$defs/commitSha" }, { "type": "null" }] },
            "changedFiles": { "type": "integer", "minimum": 0 }
          }
        },
        "files": {
          "oneOf": [
            {
              "type": "object",
              "required": ["status", "complete", "entries"],
              "additionalProperties": false,
              "properties": {
                "status": { "const": "ok" },
                "complete": { "type": "boolean" },
                "entries": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": ["path", "status"],
                    "additionalProperties": false,
                    "properties": {
                      "path": { "type": "string", "minLength": 1 },
                      "status": {
                        "enum": [
                          "added",
                          "removed",
                          "modified",
                          "renamed",
                          "copied",
                          "changed",
                          "unchanged"
                        ]
                      },
                      "previousPath": { "type": "string", "minLength": 1 }
                    }
                  }
                }
              }
            },
            { "$ref": "#/$defs/unreadOnly" }
          ]
        },
        "closingReferences": {
          "oneOf": [
            {
              "type": "object",
              "required": ["status", "issues"],
              "additionalProperties": false,
              "properties": {
                "status": { "const": "ok" },
                "issues": { "type": "array", "items": { "$ref": "#/$defs/issueRef" } }
              }
            },
            { "$ref": "#/$defs/unreadOnly" }
          ]
        },
        "references": {
          "type": "array",
          "items": {
            "oneOf": [
              {
                "type": "object",
                "required": ["kind", "ref", "status", "exists"],
                "additionalProperties": false,
                "properties": {
                  "kind": { "const": "issue" },
                  "ref": { "$ref": "#/$defs/issueRef" },
                  "status": { "const": "ok" },
                  "exists": { "type": "boolean" }
                }
              },
              {
                "type": "object",
                "required": ["kind", "ref", "status", "exists", "reachableFromHead"],
                "additionalProperties": false,
                "properties": {
                  "kind": { "const": "commit" },
                  "ref": { "$ref": "#/$defs/commitSha" },
                  "status": { "const": "ok" },
                  "exists": { "type": "boolean" },
                  "reachableFromHead": { "type": "boolean" }
                }
              },
              {
                "type": "object",
                "required": ["kind", "ref"],
                "properties": {
                  "kind": { "enum": ["issue", "commit"] },
                  "ref": { "type": "string", "minLength": 1 }
                },
                "allOf": [{ "$ref": "#/$defs/unread" }]
              }
            ]
          }
        },
        "checkRuns": {
          "oneOf": [
            {
              "type": "object",
              "required": ["status", "commit", "excludedIds", "runs"],
              "additionalProperties": false,
              "properties": {
                "status": { "const": "ok" },
                "commit": { "$ref": "#/$defs/commitSha" },
                "excludedIds": {
                  "description": "Check runs left out of the count: the run the checker itself executes in.",
                  "type": "array",
                  "items": { "type": "integer", "minimum": 1 }
                },
                "runs": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": ["id", "name", "status", "conclusion"],
                    "additionalProperties": false,
                    "properties": {
                      "id": { "type": "integer", "minimum": 1 },
                      "name": { "type": "string" },
                      "status": { "type": "string", "minLength": 1 },
                      "conclusion": { "type": ["string", "null"] }
                    }
                  }
                }
              }
            },
            { "$ref": "#/$defs/unreadOnly" }
          ]
        },
        "testRecords": {
          "type": "array",
          "items": {
            "oneOf": [
              {
                "type": "object",
                "required": ["record", "status", "runs"],
                "additionalProperties": false,
                "properties": {
                  "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" },
                  "status": { "const": "ok" },
                  "runs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["runId", "runAttempt", "executed", "failed"],
                      "additionalProperties": false,
                      "properties": {
                        "runId": { "type": "integer", "minimum": 1 },
                        "runAttempt": { "type": "integer", "minimum": 1 },
                        "executed": { "type": "integer", "minimum": 0 },
                        "failed": { "type": "integer", "minimum": 0 }
                      }
                    }
                  }
                }
              },
              {
                "type": "object",
                "required": ["record"],
                "properties": {
                  "record": { "$ref": "handback-block-0.1.schema.json#/$defs/junitRecord" }
                },
                "allOf": [{ "$ref": "#/$defs/unread" }]
              }
            ]
          }
        },
        "deployments": {
          "type": "array",
          "items": {
            "oneOf": [
              {
                "type": "object",
                "required": ["environment", "status", "deployments"],
                "additionalProperties": false,
                "properties": {
                  "environment": { "type": "string", "minLength": 1 },
                  "status": { "const": "ok" },
                  "deployments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["id", "sha", "relation", "successAt"],
                      "additionalProperties": false,
                      "properties": {
                        "id": { "type": "integer", "minimum": 1 },
                        "sha": { "$ref": "#/$defs/commitSha" },
                        "relation": {
                          "enum": ["head", "merge", "descendant", "unrelated", "unknown"]
                        },
                        "successAt": {
                          "oneOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }]
                        }
                      }
                    }
                  }
                }
              },
              {
                "type": "object",
                "required": ["environment"],
                "properties": { "environment": { "type": "string", "minLength": 1 } },
                "allOf": [{ "$ref": "#/$defs/unread" }]
              }
            ]
          }
        },
        "sources": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["kind", "locator", "sha256", "readAt"],
            "additionalProperties": false,
            "properties": {
              "kind": { "$ref": "#/$defs/sourceKind" },
              "locator": { "type": "string", "minLength": 1, "maxLength": 2000 },
              "sha256": { "$ref": "#/$defs/sha256" },
              "etag": { "type": "string", "minLength": 1 },
              "readAt": { "$ref": "#/$defs/timestamp" },
              "error": {
                "description": "Present when the read failed: http_<status>, network, or parse.",
                "type": "string",
                "pattern": "^(?:http_[1-5][0-9]{2}|network|parse)$"
              }
            }
          }
        }
      }
    },
    "unreadOnly": {
      "allOf": [{ "$ref": "#/$defs/unread" }],
      "unevaluatedProperties": false
    }
  }
}
