{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://verifex.dev/standards/screening-decision-record/v0.1/schema.json",
  "title": "Screening Decision Record",
  "version": "0.1.0",
  "description": "An OPEN REFERENCE by Verifex (not an industry standard) for the fields a sanctions/PEP/entity screening result must carry so a reviewer can reconstruct why the result was produced and whether a no-hit was eligible to be treated as clear. A match score is not a decision record: this schema distinguishes the raw result, the coverage actually executed, the candidate set considered, the human disposition, and the retained evidence state. It is a technical reference, not legal advice, a regulatory template, or a claim that any result is compliant.",
  "type": "object",
  "required": [
    "record_version",
    "request_id",
    "input",
    "screening_mode",
    "coverage",
    "candidate_set",
    "served_decision",
    "clearance_eligible",
    "evidence",
    "timestamps"
  ],
  "additionalProperties": true,
  "properties": {
    "record_version": {
      "type": "string",
      "const": "screening-decision-record/0.1",
      "description": "The reference version this record conforms to."
    },
    "request_id": {
      "type": "string",
      "description": "Stable identifier for the screening request, used to retrieve the evidence record later."
    },
    "input": {
      "type": "object",
      "description": "The subject as submitted, plus the normalized query the engine actually screened.",
      "required": ["raw", "normalized_query", "subject_type"],
      "properties": {
        "raw": { "type": "object", "description": "The caller's original input (name, type, and any context such as country or date of birth)." },
        "normalized_query": { "type": "string", "description": "The query after normalization/transliteration that was actually matched. Distinguishing raw input from normalized query is what lets a reviewer see WHAT was screened." },
        "subject_type": { "type": "string", "enum": ["person", "entity", "vessel", "unknown"] }
      }
    },
    "screening_mode": {
      "type": "string",
      "enum": ["standard", "exact_only"],
      "description": "Standard is recall-oriented and can support a clearance path. Exact-only is a narrow lookup and NEVER earns a compliance clearance on its own."
    },
    "sources": {
      "type": "array",
      "description": "Every source consulted, with the exact list version and the state it was in at screening time. This is the heart of coverage evidence.",
      "items": {
        "type": "object",
        "required": ["source_id", "publisher", "list_version", "state"],
        "properties": {
          "source_id": { "type": "string", "description": "Stable source identifier (e.g. OFAC_SDN, UK_SANCTIONS_LIST, UN_CONSOLIDATED, EU_CONSOLIDATED)." },
          "publisher": { "type": "string", "description": "The publishing authority (e.g. U.S. Treasury, FCDO, United Nations, European Union)." },
          "list_version": { "type": "string", "description": "The exact version/edition of the list that was screened (e.g. a publisher version string or the ingest date)." },
          "state": {
            "type": "string",
            "enum": ["current", "stale", "unavailable", "disabled", "plan_excluded"],
            "description": "Freshness and screenability state at screening time. 'stale'/'unavailable' must never be silently treated as a pass. Freshness is not the same as publishing cadence."
          },
          "last_successful_observation": { "type": ["string", "null"], "format": "date-time", "description": "When Verifex last successfully ingested/observed this source, or null if never." }
        }
      }
    },
    "coverage": {
      "type": "object",
      "required": ["status"],
      "description": "Whether the execution actually screened everything in scope for the caller's plan/policy.",
      "properties": {
        "status": {
          "type": "string",
          "enum": ["complete", "partial", "unavailable"],
          "description": "complete = every in-scope source answered; partial = at least one in-scope source was stale/unavailable; unavailable = coverage could not be established. Partial or unavailable coverage cannot support a clearance."
        },
        "unavailable_sources": { "type": "array", "items": { "type": "string" }, "description": "Source IDs that were in scope but did not answer." }
      }
    },
    "candidate_set": {
      "type": "object",
      "required": ["complete"],
      "description": "Whether the retrieved candidate set was exhaustive for the query, or capped/truncated.",
      "properties": {
        "complete": { "type": "boolean", "description": "true only if the candidate set was not capped. A truncated/capped candidate set cannot clear." },
        "truncated": { "type": "boolean" },
        "considered_count": { "type": "integer", "minimum": 0 }
      }
    },
    "reasoning": {
      "type": "array",
      "description": "The signals that moved the result: which alias/attribute matched or conflicted, and why. Retrieval is candidate retrieval, not an analyst-confirmed identity.",
      "items": {
        "type": "object",
        "required": ["signal", "polarity"],
        "properties": {
          "signal": { "type": "string", "description": "e.g. alias_exact_after_normalization, jurisdiction_aligned, dob_conflict, registration_date_absent." },
          "polarity": { "type": "string", "enum": ["supporting", "conflicting", "neutral"] },
          "detail": { "type": "string" }
        }
      }
    },
    "served_decision": {
      "type": "string",
      "enum": ["clearance_eligible", "review_required", "monitor", "block", "unavailable"],
      "description": "The operational outcome served to the caller. PEP exposure or weak resemblance must NOT be rendered as a sanctions-grade block; an incomplete execution must NOT be rendered as clear."
    },
    "clearance_eligible": {
      "type": "boolean",
      "description": "Whether the result is eligible for the CALLER'S clearance policy. Eligible only if screening_mode is standard, coverage.status is complete, candidate_set.complete is true, no actionable match remains, and evidence.state is durable. The legal/policy decision itself remains the customer's; this flag reports eligibility, not a legal conclusion."
    },
    "human_disposition": {
      "type": ["object", "null"],
      "description": "The analyst action, if any. A machine result and a human disposition are different facts and are recorded separately.",
      "properties": {
        "status": { "type": "string", "enum": ["none", "cleared", "escalated", "confirmed_match", "false_positive"] },
        "actor": { "type": "string" },
        "at": { "type": "string", "format": "date-time" },
        "note": { "type": "string" }
      }
    },
    "evidence": {
      "type": "object",
      "required": ["state"],
      "description": "Whether the record needed to reconstruct this decision later is retained.",
      "properties": {
        "state": { "type": "string", "enum": ["available", "pending", "unavailable"], "description": "available = the decision can be reconstructed later; unavailable = it cannot." },
        "capsule_id": { "type": ["string", "null"], "description": "Identifier for the retained evidence capsule, if available." }
      }
    },
    "policy_context": {
      "type": "object",
      "description": "The policy/version context under which the decision was made, so a later reviewer knows which rules applied.",
      "properties": {
        "policy_version": { "type": "string" },
        "plan": { "type": "string", "description": "The plan whose source entitlement defined 'in scope'." }
      }
    },
    "timestamps": {
      "type": "object",
      "required": ["screened_at"],
      "properties": {
        "screened_at": { "type": "string", "format": "date-time" },
        "record_created_at": { "type": "string", "format": "date-time" }
      }
    }
  }
}
