{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://matproof.com/spec/sealed-evidence-bundle/v1/manifest.schema.json",
  "title": "Sealed Evidence Bundle manifest, version 1",
  "description": "Schema for manifest.json inside a sealed-evidence-bundle/v1 ZIP archive. Published under CC BY 4.0. See https://matproof.com/spec/sealed-evidence-bundle",
  "type": "object",
  "required": [
    "format",
    "formatVersion",
    "bundleId",
    "createdAt",
    "generator",
    "organization",
    "scope",
    "files",
    "bundleDigest"
  ],
  "additionalProperties": true,
  "properties": {
    "format": {
      "const": "sealed-evidence-bundle"
    },
    "formatVersion": {
      "type": "integer",
      "const": 1,
      "description": "A verifier that does not recognise this value should refuse the bundle rather than guess."
    },
    "spec": {
      "type": "string",
      "format": "uri"
    },
    "bundleId": {
      "type": "string",
      "minLength": 1,
      "description": "Issuer-assigned identifier. Not a security property: the bundleDigest is the identity that matters."
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    },
    "generator": {
      "type": "object",
      "required": ["product", "version"],
      "properties": {
        "product": { "type": "string", "minLength": 1 },
        "version": { "type": "string", "minLength": 1 }
      }
    },
    "organization": {
      "type": "object",
      "required": ["id", "name"],
      "description": "The subject of the evidence, not the issuer.",
      "properties": {
        "id": { "type": "string", "minLength": 1 },
        "name": { "type": "string", "minLength": 1 }
      }
    },
    "scope": { "$ref": "#/$defs/scope" },
    "files": {
      "type": "array",
      "items": { "$ref": "#/$defs/fileEntry" },
      "description": "Sorted by path, comparing raw UTF-8 bytes. Never contains manifest.json, SHA256SUMS or signature.json."
    },
    "bundleDigest": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$",
      "description": "SHA-256 over the concatenated lines '<sha256><SP><SP><path><LF>' of the sorted file list. Equals `sha256sum SHA256SUMS`."
    }
  },
  "$defs": {
    "scope": {
      "type": "object",
      "required": ["kind", "fileCount", "totalBytes", "redacted"],
      "additionalProperties": true,
      "properties": {
        "kind": {
          "type": "string",
          "description": "What the bundle covers. Issuer-defined; 'organization' and 'task' are the values Matproof emits."
        },
        "fileCount": { "type": "integer", "minimum": 0 },
        "totalBytes": { "type": "integer", "minimum": 0 },
        "redacted": {
          "type": "boolean",
          "description": "True when the issuer removed secrets from the payloads before hashing. The hashes then cover the redacted form."
        },
        "heldRuns": {
          "type": "integer",
          "minimum": 0,
          "description": "Automated checks that returned no usable result and were excluded. A gap is not a pass, so it is reported rather than dropped."
        },
        "integrityWarnings": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Paths whose stored hash did not match the exported bytes. Such files stay in the bundle so the reader can see the problem."
        },
        "taskCount": { "type": "integer", "minimum": 0 },
        "automationCount": { "type": "integer", "minimum": 0 },
        "taskId": { "type": "string" },
        "taskTitle": { "type": "string" },
        "frameworks": {
          "type": "array",
          "items": { "type": "string" }
        }
      }
    },
    "fileEntry": {
      "type": "object",
      "required": ["path", "sha256", "bytes", "kind"],
      "additionalProperties": true,
      "properties": {
        "path": {
          "type": "string",
          "minLength": 1,
          "pattern": "^(?!/)(?![A-Za-z]:)(?!.*(^|/)\\.\\.?(/|$))[^\\u0000\\\\]+$",
          "description": "Relative POSIX path inside the ZIP. No leading slash, no drive letter, no '.' or '..' segment, no backslash, no NUL."
        },
        "sha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$",
          "description": "Hash of the bytes stored in the archive. Detects any change made after the export."
        },
        "bytes": { "type": "integer", "minimum": 0 },
        "kind": {
          "type": "string",
          "enum": ["artifact", "report", "data", "meta"],
          "description": "artifact = evidence as uploaded; report = generated document; data = machine-readable records; meta = README and verifier."
        },
        "contentSha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$",
          "description": "Hash of the underlying evidence, stable across re-exports. Compare this, not sha256, when diffing two bundles."
        },
        "integrity": {
          "type": "string",
          "enum": ["verified", "unhashed", "mismatch"],
          "description": "Result of checking an artifact against the hash recorded when it was first stored."
        },
        "source": {
          "type": "object",
          "required": ["type", "id"],
          "properties": {
            "type": { "type": "string" },
            "id": { "type": "string" }
          }
        }
      }
    }
  }
}
