The Verdict object

A Verdict is a detector's decision about a GuardEvent. A runtime collects one verdict per detector and composes them into the single effective verdict that POST /v1/evaluate returns and the enforcement point enforces.

Normative schema: schema/verdict.schema.json.

Fields

FieldTypeRequiredDescription
ogr_versionstringrequiredThe literal "0.4"
event_idstringrequiredThe GuardEvent being judged
guard_idstringrequiredCopied from the event
providerstringrequiredDetector identity — what makes attribution, metering, and the benchmark leaderboard possible
decisionenumrequiredSee Decisions
confidencenumber 0–1optionalDetector self-reported confidence
latency_msnumber ≥ 0optionalDetector self-reported latency
reasonsarray of stringrecommendedHuman-readable justification
categoriesarrayrecommendedMatched risk categories with scores — see categories
modificationsobjectconditionalRequired when decision is modify or redact — see modifications
evidencearray of objectoptionalStructured pointers: spans, matched rule ids, fetched-artifact hashes
findingsarrayrecommended for span detectorsWhat was found, normalized — see findings

Decisions

decisionMeaningTypical domain
allowNo actionboth
blockDeny the action entirelyboth
require_approvalSuspend; a human must approve before proceeding — poll GET /v1/approvalssecurity
modifyProceed with a transformed payload (e.g. constrained argv)both
redactProceed with sensitive spans removedsafety / privacy

Severity order (used by deny-wins composition and most-severe conflict resolution — most severe first):

block  >  require_approval  >  redact  >  modify  >  allow

Both SDKs export this ordering as severity(decision).

A detector that does not handle an event's kind, or finds nothing, returns allow — an explicit abstention, never silence.

categories

{ "id": "security.prompt_injection", "domain": "security", "score": 0.93 }
FieldTypeRequiredDescription
idstringrequiredA taxonomy id matching ^(safety|security|privacy|x)\.[a-z0-9_.]+$
domainenumrequiredsafety | security | privacy
scorenumber 0–1optionalDetector-reported; not comparable across vendors except through the benchmark

id is drawn from the risk taxonomy. The namespaces:

NamespaceJudged onExamples
safety.*content, at the I/O boundarysafety.toxicity, safety.self_harm, safety.unsafe_advice
security.*actions and data flowsecurity.prompt_injection, security.malicious_command, security.secret_leak.api_key
privacy.*personal data crossing a boundaryprivacy.pii.email, privacy.pii.national_id.cn
x.<vendor>.*vendor/experimental classes with no neutral homex.ogr.politics.sensitive

Ids refine hierarchically (privacy.pii.national_id.cn); a consumer that does not know a refined id treats it as its longest known prefix — the rollup rule that lets policy be written once per bucket.

categories is the rollup (with max scores) of findings, and the field composition operates on.

modifications

Required when decision is modify or redact. An enforcement point that drops this field cannot carry out the decision — only degrade it to allow (a leak) or to block.

FieldTypeRequiredDescription
kindenumrequiredredact | rewrite | constrain
spansarrayfor span editsSpans to transform — fields below
payloadobjectfor whole-payload rewritesThe replacement payload

Each span:

FieldTypeRequiredDescription
pathstringrequiredPayload path, e.g. payload.text
start, endintegeroptionalOffsets over the payload as transported
operatorenumoptionalreplace (default) | mask | hash | encrypt
replacementstringoptionalA placeholder (${OGR_EMAIL_1}) — never the original value
refstringoptionalHandle, unique within the verdict; the same ref later refers to the same original value
{
  "kind": "redact",
  "spans": [ { "path": "payload.text", "start": 40, "end": 76,
               "operator": "replace", "ref": "OGR_EMAIL_1",
               "replacement": "${OGR_EMAIL_1}" } ]
}

hash supports stable pseudonyms; replace and encrypt support restoration — encrypt from the ciphertext, replace from a map the enforcement point holds. When composition merges several redact verdicts, the effective spans are the union, with overlaps merged to the covering range.

findings

A finding is what a detector found; decision and modifications remain what to do about it. Span detectors (PII, secrets) should emit them.

FieldTypeRequiredDescription
categorystringrequiredTaxonomy id, same pattern as categories[].id
pathstringoptionalPayload path the span sits in; event-level findings (e.g. a malicious command) omit path/start/end
start, endinteger ≥ 0optionalOffsets over the payload as transported
scorenumber 0–1optionalPer-finding score
detectorstringoptionalWhich detector produced it, e.g. ogr.patterns
{ "category": "privacy.pii.national_id.cn", "path": "payload.text",
  "start": 7, "end": 25, "score": 0.95, "detector": "ogr.patterns" }

The no-matched-text rule. Findings must not echo the matched text — offsets only. Otherwise every verdict store becomes a copy of the sensitive data it was meant to guard. All offsets refer to the payload as transported (after any local redaction), never to a form the receiver has not seen.

When decision is redact, modifications.spans should be derivable from the span-bearing findings (same path/offsets).

Runtimes may annotate findings with extension keys — the reference runtime adds a severity label (as in the evaluate example), a check_id naming the specific check that fired, and a human-readable title. Like all extension keys, pass through what you don't consume.

x.ogr.* extension keys

Verdicts carry runtime extensions as top-level x.ogr.* keys (vendors use x.<vendor>.*). Clients must pass through keys they do not understand. The ones the reference runtime emits on evaluate responses:

KeyMeaning
x.ogr.session_idSession the runtime attributed the event to
x.ogr.redaction_mapRedactions the PEP must apply
x.ogr.output_modebuffer | stream lane selection for streamed output
x.ogr.unjudgedPayload paths this verdict could not judge — non-empty means "could not look", not "found nothing"
x.ogr.whitelistedThe event matched a whitelist entry

In the Python SDK these land in verdict.extensions; in JS they stay verbatim keys on the verdict object.

Example — annotated

An LLM detector blocks an injected install command:

{
  "ogr_version": "0.4",
  "event_id": "evt-9f2",                     // the event judged
  "guard_id": "ga-1a2b",                     // copied from it
  "provider": "ogr.poc.llm_judge",           // who judged — attribution & metering
  "decision": "block",
  "categories": [
    { "id": "security.malicious_command", "domain": "security", "score": 0.91 },
    { "id": "security.prompt_injection",  "domain": "security", "score": 0.88 }
  ],
  "reasons": [
    "argv pipes a remotely fetched script directly into a shell",
    "command originated from untrusted web content (provenance: web/untrusted)",
    "env exposes AWS_SECRET_ACCESS_KEY to the spawned process"
  ],
  "evidence": [ { "type": "provenance_ref", "event_id": "evt-7c1" } ],
  "findings": [
    { "category": "security.malicious_command", "detector": "ogr.poc.llm_judge" }
  ],                                          // event-level: no path/offsets
  "confidence": 0.9,
  "latency_ms": 120
}