Deterministic scan JSON output — schema decision-scan/v1
termivar scan <target> --format json prints a single JSON document to stdout;
the [ALPHA] warning and any error go to stderr. The deprecated, discoverable
decision-scan command alias runs the same implementation and produces identical
stdout and stderr. The document is
built from the same typed summary the text renderer uses — it is never produced by
parsing rendered text. --format json cannot be combined with --explain (the
JSON already carries the full diagnostics); the combination is rejected as an
argument conflict.
The schema_version field is the contract handle. Its historical name remains
decision-scan/v1 even though scan is now the canonical command. Renaming it
would be a breaking wire change. This page pins its exact shape.
Top-level document
| Field | Type | Notes |
|---|---|---|
schema_version |
string | Always "decision-scan/v1" for this version. |
engine |
string | Always "decision-preview". |
target_origin |
string | ASCII origin serialization of the authorized target. |
summary |
object | Aggregate counts (below). |
executor_routes |
object | Runtime executor-route authority (below). |
hypotheses |
array | Reasoning hypotheses, sorted (below). |
planning_turns |
array | One entry per planning turn, in order. |
dispatches |
array | Wire dispatches, in dispatch order. |
verification_outcomes |
array | Verification outcome turns, in order. |
terminal |
object | The bounded terminal state. |
usage |
object | Resource usage. |
No field is ever omitted; empty arrays are [] and absent scalars are null.
summary
| Field | Type |
|---|---|
bootstrap_evidence_writes |
integer ≥ 0 |
planning_turns |
integer ≥ 0 |
verification_outcomes |
integer ≥ 0 |
conclusive_outcomes |
integer ≥ 0 |
inconclusive_outcomes |
integer ≥ 0 |
experience_records |
integer ≥ 0 |
Invariants: summary.planning_turns == planning_turns.length;
summary.verification_outcomes == verification_outcomes.length;
conclusive_outcomes + inconclusive_outcomes == verification_outcomes.length.
executor_routes
| Field | Type | Notes |
|---|---|---|
unavailable |
array of string (action ids) | Semantic actions the planner knows but the current runtime composition has no executor route for. Sourced from the runtime's own authority, never inferred from planning exclusion reasons. |
There is no available list: it is not synthesized by subtracting sets. The
inventory is a fixed property of the runtime's executor registry and is identical
regardless of the fixture/evidence.
hypotheses[]
Sorted by (predicate, value).
| Field | Type | Notes |
|---|---|---|
predicate |
string | Dotted knowledge predicate, e.g. authentication.mechanism. |
value |
string | null | Scalar string form of the value; null unless value_disposition == "exposed". |
value_kind |
string enum | text | boolean | signed | unsigned | text_list | other. |
value_disposition |
string enum | exposed | redacted | non_scalar | other. The machine-output safety decision (below). |
strength |
string enum | weak | strong | other. |
posterior_basis_points |
integer 0..=10000 | Posterior probability in basis points. Basis points and the text-only percent are each rounded directly from the upstream probability (never one from the other). |
state |
string enum | proposed | supported | contradicted | confirmed | rejected | other. |
value_disposition is a fail-closed safety policy on the hypothesis value: a
scalar value is exposed only under an allowlisted safe predicate (the standard
web technology/authentication predicates); a scalar under any other predicate is
redacted with value == null (so a future rule cannot leak a token, cookie, or
other sensitive text); a list value is non_scalar; an unknown value kind is
other. value is non-null only when exposed.
planning_turns[]
| Field | Type | Notes |
|---|---|---|
turn |
integer ≥ 0 | Zero-based turn index, in order. |
planned |
array of string (action ids) | Dependency-safe plan steps, in plan order. |
excluded |
array of object | { "action_id": string, "reason": string enum }. |
excluded[].reason enum: policy_suppressed | defense_suppressed |
requirements_not_met | no_eligible_hypothesis | risk_limit_exceeded |
below_minimum_utility | dependency_unavailable | budget_exceeded |
other. Route availability (executor_routes) and per-turn eligibility
(planning_turns) are independent axes.
dispatches[]
In dispatch order.
| Field | Type | Notes |
|---|---|---|
sequence |
integer ≥ 0 | Strictly increasing dispatch order. |
action_id |
string | Semantic action charged for the dispatch. |
stage |
string enum | passive | active | other. |
origin |
string enum | null | bootstrap | planned | adaptive | retry | other, or null. |
stage and origin are separate facts. An active-verification probe is
stage == "active" with origin == null; a consumer infers "active verification"
from that pair. (The text renderer's active_verification label is a
presentation-only derivation and never appears in JSON.)
verification_outcomes[]
| Field | Type | Notes |
|---|---|---|
action_id |
string | Action associated with the verification outcome. |
status |
string enum | success | blocked | unknown | false_positive | needs_review | confirmed_negative | other. |
conclusive |
boolean | Whether the case authorizes a hypothesis transition and the outcome maps to a verifier-owned hypothesis state. A Success may therefore be false, and no outcome is automatically a vulnerability. |
terminal
| Field | Type | Notes |
|---|---|---|
command |
string enum | execute_action | collect_active_evidence | replan | complete | await_human_review | halt | other. |
stop_reason |
string enum | null | objective_complete | no_eligible_action | human_review | adaptation_limit | action_cycle_limit | runtime_budget_limit | cancelled_by_host | other, or null when the command is not a halt. |
runtime_limit |
object | null | Structured budget stop, or null. |
runtime_limit object: { "dimension": string enum, "limit": integer,
"observed": integer, "action_id": string | null }. dimension ∈
total_requests | wall_time | response_bytes | request_body_bytes |
active_verifications | same_action_attempts |
consecutive_no_progress_turns | other. Units of limit/observed depend on
dimension: bytes for response_bytes/request_body_bytes, a count for
the request/verification/attempt dimensions, milliseconds for wall_time.
usage
| Field | Type | Units |
|---|---|---|
total_requests |
integer | count |
active_verifications |
integer | count |
response_bytes |
integer | bytes |
elapsed_ms |
integer | milliseconds |
Determinism
The semantic records and their ordering are deterministic for the same target, the same runtime version/configuration, and equivalent non-boundary responses — i.e. runs that do not sit against a transport-byte or wall-time budget boundary.
usage.elapsed_msis inherently variable across runs.- Near a budget boundary (the cumulative response-byte threshold or the
wall-time limit), TCP chunking and scheduling can affect
usage.response_bytes,terminal.runtime_limit.observed, which request the bound is reached at (and thereforeusage.total_requests), and potentially the terminal timing/state (whether the budget is crossed on this run at all). Consumers must not assume byte-exact equality near a boundary.
Away from boundaries, everything except elapsed_ms is deterministic.
Ordering
- Array element order is contractual:
planning_turnsanddispatchesare in execution order,verification_outcomesin turn order,hypothesessorted by(predicate, value), andexecutor_routes.unavailablesorted. - JSON object member order is not a consumer-semantic contract. Consumers must
address fields by name, not by position. The exact byte serialization (member
order, whitespace) reflects the current canonical renderer and may change without
a
schema_versionbump; only the field set, types, nullability, and the array orderings above are contractual.
Data the schema does not expose
The decision-scan/v1 schema does not expose raw evidence records, response
bodies, response headers, credentials, cookies, tokens, or evidence identifiers.
Every enum value is a stable snake_case label; no value is ever emitted through
Rust Debug formatting.
Enum stability
All string enums are closed vocabularies with a other catch-all so an
unrecognized upstream variant degrades to a stable, non-Debug token rather than
leaking type internals or breaking parsing.
Consumer safety guidance
decision-scan/v1 is an operational-diagnostics contract. It reports what the
runtime observed, planned, dispatched, and verified — not security findings or
vulnerabilities. When building automation on it:
- Read
hypotheses[].valueonly whenvalue_disposition == "exposed". For any other disposition the value is withheld (null); do not treat a missing value as an error, and do not readvalueunconditionally. - A planned action is not necessarily dispatched. To learn whether an action
actually ran, compare
planning_turns[].plannedagainstdispatches[].action_id— an action can be planned but deferred (e.g. behind human review) and never dispatched. status == "success",state == "confirmed", andconclusive == trueare separate verification semantics, not finding or vulnerability semantics. ASuccessmeans the action objective was satisfied, but a knowledge-only case intentionally reportsconclusive == falseand leaves its motivating hypothesis unmodified. A transition-authorized conclusive verification means the runtime reached a definite answer about a detection (e.g. "this endpoint advertises HTTP Basic authentication", or "this page contains a Livewire marker") — it does not mean a reportable security issue. Detecting a technology or an authentication mechanism is not itself a vulnerability.confirmed_negativeisconclusive == trueonly when its case authorizes a hypothesis transition. A transition-authorized definitive negative is conclusive too, soconclusivealone cannot gate any finding.redacted,non_scalar, andothervalue dispositions, and theconfirmed_negative,blocked,needs_review, andfalse_positiveoutcome statuses, may appear even though the current standard fixture corpus does not emit all of them. Handle every documented enum value.
Turning these diagnostics into findings is the job of a separate, explicit projection layer — not of a consumer inferring findings from these fields.
Versioning policy
schema_version changes only on a breaking change. Within v1, changes are
additive only.
Breaking (requires decision-scan/v2):
- removing or renaming a field;
- changing a field's type;
- changing the meaning of an existing enum value;
- changing a field's nullability;
- changing an ordering or invariant guaranteed above.
Non-breaking (allowed within v1):
- adding a new, documented, optional field;
- adding a new enum value only where a
othercatch-all already absorbs unknown values (consumers must treat unknown enum values asother).
Consumers should ignore unknown fields and treat unknown enum values as other.