The JSON report, its schema, and SARIF
paranoid writes one result in four formats: text
for a terminal, md for a PR comment or job summary,
json for scripting, and sarif for code
scanning tools.
Formats
| Format | Use |
|---|---|
| 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. |
Select a format with --format on verify.
All four renderers read the same finding.Result, so a
run produces the same findings in every format.
JSON report, schema version 9
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. The schema allows no undeclared fields
(additionalProperties: false everywhere). The
current version is 9. Whenever a change adds a
field, a category, or a report section, this number increases. A
field does not change shape within a version.
| Top-level field | Type | Meaning |
|---|---|---|
| schema_version | integer | Always 9 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 |
| proofs | object, optional | Formal-verification summary (schema version 9). Present only when the formal-verification check ran; absent otherwise. Never changes the score. See the Proofs table below |
| 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" | "formal-verification" |
| 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) |
Each field of proofs (when present):
| Field | Type |
|---|---|
| files_changed | array of string, the changed proof files, sorted |
| holes | integer, proof holes at head in the changed proof files |
| escapes | integer, axioms and escapes at head in the changed proof files |
| locked | object {total, ok, drifted, missing}, the locked specs at head |
| recheck | object {ran, reason, build_ok, audited, disallowed_axioms, no_answer}; reason is left out when empty; the counts are 0 when the re-check did not run |
{
"schema_version": 9,
"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"}
]},
"proofs": {"files_changed": ["Proofs/Sort.lean"], "holes": 1, "escapes": 0,
"locked": {"total": 2, "ok": 1, "drifted": 1, "missing": 0},
"recheck": {"ran": false, "reason": "not requested", "build_ok": false, "audited": 0, "disallowed_axioms": 0, "no_answer": 0}},
"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, for upload to GitHub code
scanning or any other SARIF reader. 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 are all written to
runs[0].properties, because SARIF has no matching
concept for paranoid's report shape. The
proofs block is written there too, only when
the formal-verification check ran. Coverage is not included in
SARIF properties.
SARIF is versioned by the SARIF spec itself, not by
schema_version: a JSON report schema change does not
change the SARIF shape, and the reverse also holds. See
Exit codes & CI for the
upload-sarif workflow example.
fix-prompt JSON schema
paranoid fix-prompt --format json emits a
separate document 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": []
}
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, which also
has its own history_version. See
History for both shapes, an example
of each, and how the HTML trend view is built.
Both schema files are checked in and tested. A rendered report
that does not match its schema fails the build. If your own
tooling parses these formats, pin a schema version and treat an
unexpected schema_version as an error, as
fix-prompt --report does.