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
| Field | Type | Required | Description |
|---|---|---|---|
ogr_version | string | required | The literal "0.4" |
event_id | string | required | The GuardEvent being judged |
guard_id | string | required | Copied from the event |
provider | string | required | Detector identity — what makes attribution, metering, and the benchmark leaderboard possible |
decision | enum | required | See Decisions |
confidence | number 0–1 | optional | Detector self-reported confidence |
latency_ms | number ≥ 0 | optional | Detector self-reported latency |
reasons | array of string | recommended | Human-readable justification |
categories | array | recommended | Matched risk categories with scores — see categories |
modifications | object | conditional | Required when decision is modify or redact — see modifications |
evidence | array of object | optional | Structured pointers: spans, matched rule ids, fetched-artifact hashes |
findings | array | recommended for span detectors | What was found, normalized — see findings |
Decisions
decision | Meaning | Typical domain |
|---|---|---|
allow | No action | both |
block | Deny the action entirely | both |
require_approval | Suspend; a human must approve before proceeding — poll GET /v1/approvals | security |
modify | Proceed with a transformed payload (e.g. constrained argv) | both |
redact | Proceed with sensitive spans removed | safety / 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 }
| Field | Type | Required | Description |
|---|---|---|---|
id | string | required | A taxonomy id matching ^(safety|security|privacy|x)\.[a-z0-9_.]+$ |
domain | enum | required | safety | security | privacy |
score | number 0–1 | optional | Detector-reported; not comparable across vendors except through the benchmark |
id is drawn from the
risk taxonomy.
The namespaces:
| Namespace | Judged on | Examples |
|---|---|---|
safety.* | content, at the I/O boundary | safety.toxicity, safety.self_harm, safety.unsafe_advice |
security.* | actions and data flow | security.prompt_injection, security.malicious_command, security.secret_leak.api_key |
privacy.* | personal data crossing a boundary | privacy.pii.email, privacy.pii.national_id.cn |
x.<vendor>.* | vendor/experimental classes with no neutral home | x.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.
| Field | Type | Required | Description |
|---|---|---|---|
kind | enum | required | redact | rewrite | constrain |
spans | array | for span edits | Spans to transform — fields below |
payload | object | for whole-payload rewrites | The replacement payload |
Each span:
| Field | Type | Required | Description |
|---|---|---|---|
path | string | required | Payload path, e.g. payload.text |
start, end | integer | optional | Offsets over the payload as transported |
operator | enum | optional | replace (default) | mask | hash | encrypt |
replacement | string | optional | A placeholder (${OGR_EMAIL_1}) — never the original value |
ref | string | optional | Handle, 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.
| Field | Type | Required | Description |
|---|---|---|---|
category | string | required | Taxonomy id, same pattern as categories[].id |
path | string | optional | Payload path the span sits in; event-level findings (e.g. a malicious command) omit path/start/end |
start, end | integer ≥ 0 | optional | Offsets over the payload as transported |
score | number 0–1 | optional | Per-finding score |
detector | string | optional | Which 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:
| Key | Meaning |
|---|---|
x.ogr.session_id | Session the runtime attributed the event to |
x.ogr.redaction_map | Redactions the PEP must apply |
x.ogr.output_mode | buffer | stream lane selection for streamed output |
x.ogr.unjudged | Payload paths this verdict could not judge — non-empty means "could not look", not "found nothing" |
x.ogr.whitelisted | The 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
}