{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zovos.ai/schemas/evidence/control-test-result/v1.json",
  "title": "ControlTestResult",
  "description": "Zovos Evidence Protocol (ZEP) control-test result. A bring-your-own-assistant submission: the customer's own AI assistant performs a control test against the customer's own system with the customer's own tools/credentials, then submits this result plus evidence artifacts to Zovos, where it lands pending human review. Zovos NEVER receives customer system credentials. This schema DELIBERATELY has NO `confidence` field: a model-stated confidence is itself fabricable and gives false assurance. Assurance comes from raw tool-output artifacts + server-side sha256 hashing + an append-only audit + mandatory reviewer acceptance + random re-test sampling, not a number the model invents. `additionalProperties` is false at every level so a `confidence` (or any other unknown) field is rejected rather than silently recorded.",
  "$comment": "Authoritative source of truth for the ZEP control-test-result contract. zovos-web (W-F5) serves a byte-identical published copy; regenerate it with `python -m app.schemas.zep.export`. The backend validates submissions against the pydantic mirror in app.schemas.zep.control_test_result (kept in agreement with this file by test_zep_schema.py) rather than adding a jsonschema runtime dependency.",
  "type": "object",
  "required": [
    "schema_version",
    "idempotency_key",
    "control_id",
    "procedure_version",
    "outcome",
    "method",
    "tester",
    "target_system",
    "method_narrative",
    "timestamps",
    "artifacts"
  ],
  "additionalProperties": false,
  "not": { "required": ["confidence"] },
  "properties": {
    "schema_version": {
      "const": "zep/1.0",
      "description": "Pinned protocol version string."
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "description": "Caller-generated; safe to retry on the stateless MCP transport. Zovos dedupes per tenant: a replay returns the original receipt, a conflicting payload under the same key is a 409."
    },
    "control_id": {
      "type": "string",
      "description": "Zovos control catalogue display id (e.g. C-2026-0001). Resolved INSIDE the caller's tenant only; there is no tenant argument."
    },
    "procedure_id": {
      "type": "string",
      "description": "Optional echo of the procedure package identifier the assistant fetched. Metadata only; the control_id + procedure_version are authoritative."
    },
    "procedure_version": {
      "type": "integer",
      "minimum": 1,
      "maximum": 1000000,
      "description": "The control's effective test_procedure_version this test ran against. Rejected (409) if it does not equal the control's current version — the procedure moved under the assistant."
    },
    "framework_refs": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Optional framework anchors (e.g. FFIEC-ISB, NIST-800-53:IA-2(1)). Metadata only."
    },
    "outcome": {
      "type": "string",
      "enum": ["pass", "fail", "pass_with_exceptions", "not_tested", "not_applicable"],
      "description": "The raw examiner outcome. Mapped to operating effectiveness only on reviewer acceptance: pass->Effective, pass_with_exceptions->Partial, fail->Ineffective; not_tested/not_applicable never change a rating."
    },
    "method": {
      "type": "string",
      "enum": ["EXAMINE", "INTERVIEW", "TEST", "AUTOMATED"],
      "description": "OSCAL-AR assessment method. TEST and AUTOMATED outcomes REQUIRE at least one artifact with role 'raw_tool_output' (anti-fabrication)."
    },
    "method_narrative": {
      "type": "string",
      "minLength": 20,
      "maxLength": 4000,
      "description": "Re-performable description of the steps taken (examiner requirement). Untrusted free text — never interpreted as instructions server-side."
    },
    "tester": {
      "type": "object",
      "required": ["assistant", "execution_mode"],
      "additionalProperties": false,
      "description": "The tester of record is ALWAYS the verified WorkOS human principal behind the OAuth token, derived server-side. human_principal here is optional and any client-supplied value is IGNORED (echoed back only). The assistant block is SELF-DECLARED, untrusted, recorded verbatim, rendered UNVERIFIED, and never used for authorization.",
      "properties": {
        "human_principal": {
          "type": "object",
          "description": "SERVER-DERIVED from the OAuth token; client-supplied values are ignored. Not required in the client payload.",
          "additionalProperties": false,
          "properties": {
            "subject": { "type": "string" },
            "display_name": { "type": "string" },
            "email": { "type": "string", "format": "email" }
          }
        },
        "assistant": {
          "type": "object",
          "required": ["name"],
          "additionalProperties": false,
          "description": "SELF-DECLARED by the client; untrusted, recorded verbatim, never an authorization signal. name required; model/version optional.",
          "properties": {
            "name": { "type": "string", "examples": ["Claude Code", "ChatGPT (Developer mode)"] },
            "model": { "type": "string", "examples": ["claude-opus-5-5"] },
            "version": { "type": "string" }
          }
        },
        "execution_mode": {
          "type": "string",
          "enum": ["assistant_executed", "human_ran_assistant_formatted", "human_ran_human_submitted"],
          "description": "How the test was run. human_ran_assistant_formatted flags a pasted-transcript submission a reviewer should weight accordingly."
        }
      }
    },
    "target_system": {
      "type": "object",
      "required": ["type"],
      "additionalProperties": false,
      "description": "Describes the customer's system under test. NEVER contains credentials, tokens, cookies or any secret — only a non-secret type/identifier/environment. Self-declared, UNVERIFIED.",
      "properties": {
        "type": { "type": "string", "examples": ["EntraID", "ActiveDirectory", "AWS", "M365", "Other"] },
        "identifier": { "type": "string", "description": "Tenant id / account id / hostname — non-secret." },
        "environment": { "type": "string", "enum": ["production", "non_production"] }
      }
    },
    "population": {
      "type": "object",
      "additionalProperties": false,
      "description": "The population under test (examiner requirement). Null only when method != TEST or truly N/A.",
      "properties": {
        "definition": { "type": "string" },
        "size": { "type": "integer", "minimum": 0, "maximum": 100000000 },
        "source": { "type": "string", "description": "Where the population came from (e.g. a Graph query)." },
        "as_of": { "type": "string", "format": "date-time" }
      }
    },
    "sample": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "method": {
          "type": "string",
          "enum": ["full_population", "random", "judgmental", "haphazard", "systematic"]
        },
        "size": { "type": "integer", "minimum": 0, "maximum": 100000000 },
        "rationale": { "type": "string" },
        "period_start": { "type": "string", "format": "date" },
        "period_end": { "type": "string", "format": "date" }
      }
    },
    "exceptions": {
      "type": "array",
      "description": "May be empty. MUST be non-empty when outcome is 'pass_with_exceptions' or 'fail' (enforced by the submission service). On acceptance each exception is promoted to a draft Finding.",
      "items": {
        "type": "object",
        "required": ["description"],
        "additionalProperties": false,
        "properties": {
          "description": { "type": "string" },
          "affected_item": { "type": "string" },
          "severity": { "type": "string", "enum": ["low", "medium", "high"] },
          "evidence_ref": { "type": "string", "description": "sha256 of the artifact that shows the exception." }
        }
      }
    },
    "artifacts": {
      "type": "array",
      "description": "Evidence manifest. Bytes are uploaded out-of-band via begin_artifact_upload (presigned PUT) and referenced here by artifact_id + sha256; Zovos re-computes each object's sha256 server-side and rejects the submission on any mismatch. A text-only client may instead pass inline_text (<=32 KB). Every item needs exactly one of artifact_id or inline_text.",
      "items": {
        "type": "object",
        "required": ["sha256", "size", "content_type", "role"],
        "additionalProperties": false,
        "oneOf": [{ "required": ["artifact_id"] }, { "required": ["inline_text"] }],
        "properties": {
          "artifact_id": { "type": "string", "description": "From begin_artifact_upload; the bytes are already in S3." },
          "inline_text": { "type": "string", "maxLength": 32768, "description": "Text-only fallback (pasted transcript) for clients with no local files." },
          "filename": { "type": "string" },
          "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
          "size": { "type": "integer", "minimum": 0, "maximum": 52428800 },
          "content_type": { "type": "string", "examples": ["text/plain", "application/json", "image/png", "application/pdf"] },
          "role": { "type": "string", "enum": ["raw_tool_output", "screenshot", "export", "transcript", "supporting"] }
        }
      }
    },
    "timestamps": {
      "type": "object",
      "required": ["test_started", "test_completed"],
      "additionalProperties": false,
      "properties": {
        "test_started": { "type": "string", "format": "date-time" },
        "test_completed": { "type": "string", "format": "date-time" }
      }
    }
  }
}
