The JSON report, its schema, and SARIF
Four output formats, one underlying result. Pick text
for a terminal, md for a PR comment or job summary,
json for scripting, or sarif for code
scanning tools.
Formats
| Format | Use it for |
|---|---|
| text (default) | Reading in a terminal. Uses color on a real terminal and respects NO_COLOR. |
| md | PR comments, job summaries, anywhere Markdown renders. |
| json | Scripting and storage. Byte-identical across reruns of the same input. |
| sarif | GitHub code scanning and other SARIF-reading tools. |
Pick with --format on verify. All four
renderers describe the same underlying finding.Result,
so the same run produces the same findings in every format; only
the shape differs.
The JSON report, schema version 7
The JSON report follows a versioned schema, checked in at
docs/schema/report-v1.schema.json.
A test in the repository checks every rendered report against
this file; it accepts nothing the schema does not describe
(additionalProperties: false everywhere). The
current version is 7. Whenever a change adds a
field, a category, or a report section, this number goes up; a
field never changes shape under the same number.
| Top-level field | Type | Meaning |
|---|---|---|
| schema_version | integer | Always 7 today |
| score | integer 0-100 | The trust score |
| verdict | "pass" | "warn" | "fail" | Derived from the score |
| findings | array of finding | Everything that fired, minus baseline and policy suppressions |
| checks_run | array of string | Names of checks that actually ran |
| checks_skipped | array of {name, reason} | Checks that could not run, and why |
| coverage | object | Total test coverage, best-effort; see Sandbox & coverage |
| suppressed | object {count, entries} | Findings a baseline entry or a policy [[disable]] entry matched, see Baseline & suppressions and Team policy file |
| meta | object | repo, base, head, tool version, generated_at |
Each item in findings:
| Field | Type |
|---|---|
| rule_id | string, e.g. "TI001" |
| severity | "info" | "low" | "medium" | "high" | "critical" |
| category | "test-integrity" | "dependencies" | "sandbox" | "claims" | "error-handling" | "api-surface" | "safety" |
| file | string |
| line | integer, 0 when unknown |
| evidence | string, truncated to about 200 characters |
| message | string |
| suggestion | string |
Findings are sorted by rule ID, then file, then line, so JSON
output is byte-identical across reruns. Nil slices render as
empty arrays, never null.
Each item in suppressed.entries:
| Field | Type |
|---|---|
| rule_id | string |
| severity | "info" | "low" | "medium" | "high" | "critical" |
| file | string |
| line | integer, 0 when unknown |
| message | string |
| reason | string, empty for a bulk-generated baseline entry |
| expires | string, empty when the entry never expires |
| expired | boolean |
| source | "baseline" | "policy" (schema version 7) |
{
"schema_version": 7,
"score": 40,
"verdict": "fail",
"findings": [
{
"rule_id": "DEP001",
"severity": "critical",
"category": "dependencies",
"file": "requirements.txt",
"line": 2,
"evidence": "requests-toolkit-pro",
"message": "The new dependency requests-toolkit-pro does not exist in PyPI.",
"suggestion": "Remove the dependency or fix the typo."
}
],
"checks_run": ["test-integrity", "dependencies", "sandbox"],
"checks_skipped": [{"name": "claims", "reason": "no --claims, --session, or commit messages given"}],
"coverage": {"available": false, "reason": "no coverage tool detected", "head_percent": 0, "has_base": false, "base_percent": 0, "delta_percent": 0},
"suppressed": {"count": 1, "entries": [
{"rule_id": "TI012", "severity": "medium", "file": "flaky_test.go", "line": 12,
"message": "A retry was added to an existing test.", "reason": "flaky under load, tracked in issue #142",
"expires": "2026-12-01", "expired": false, "source": "policy"}
]},
"meta": {"repo": "/home/you/project", "base": "7db5cfb", "head": "worktree", "version": "v0.1.0", "generated_at": "2026-08-26T12:00:00Z"}
}
SARIF (--format sarif)
SARIF 2.1.0, so findings can upload straight into GitHub code
scanning or any other SARIF-reading tool. One run, one tool
driver named paranoid, one reportingDescriptor
per registered rule (id, short and full description, a
helpUri pointing at that rule's anchor in
docs/rules.md on the main branch), and one result
per finding.
| paranoid severity | SARIF level |
|---|---|
| critical, high | error |
| medium | warning |
| low, info | note |
The original severity is preserved in
properties.severity on the result even after it is
mapped to a SARIF level. Score, verdict, checks run, checks
skipped, and the suppressed block all ride along in
runs[0].properties, since SARIF has no first-class
concept that matches paranoid's own report shape. Coverage is
not included in SARIF properties.
SARIF is versioned by the SARIF spec itself, not by
schema_version: a JSON report schema bump never
forces a SARIF shape change, and the reverse is true too. See
Exit codes & CI for the
upload-sarif workflow example.
fix-prompt's own JSON schema
paranoid fix-prompt --format json emits a
completely separate artifact with its own versioned schema at
docs/schema/fix-prompt-v1.schema.json,
currently version 1, independent of the verify report's schema
version. See fix-prompt.
{
"schema_version": 1,
"instructions": [
{
"rule_id": "TI001",
"severity": "critical",
"file": "internal/calc/calc_test.go",
"line": 0,
"instruction": "Restore the deleted test file at internal/calc/calc_test.go; do not delete a test to make the suite pass."
}
],
"notes": []
}
The history envelope, version 1
verify --save writes each report to
.paranoid/history/, wrapped in a small envelope with
its own history_version (currently 1), independent
of both the verify schema and the fix-prompt schema:
{history_version, report}, where report
is the exact same object --format json would print
for that run. paranoid history --format json emits a
third, derived shape, a per-run series for scripting, also
carrying its own history_version. See
History for both shapes in full,
an example of each, and how the HTML trend view is built from
them.
Both schema files are checked in and both are tested: a
rendered report that does not match its schema fails the build.
If your own tooling parses these formats, pin to a schema
version and treat an unexpected schema_version as
an error, the same way fix-prompt --report does.