Skip to content

RFC: Structured verification results (export-json) - #4727

Open
ivmat wants to merge 14 commits into
model-checking:mainfrom
ivmat:rfc-export-json
Open

RFC: Structured verification results (export-json)#4727
ivmat wants to merge 14 commits into
model-checking:mainfrom
ivmat:rfc-export-json

Conversation

@ivmat

@ivmat ivmat commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

RFC for --export-json <path> — an opt-in, -Z-gated flag that writes one machine-readable file describing a verification run: per-harness status, failed properties, cover and check outcomes, and the provenance needed to reproduce the run.

Why. Today the only way a program can read Kani's results is to grep rendered text for VERIFICATION:- , ** N of M cover properties satisfied, and Verification Time:. Those are printing details, not an interface — when they change, a consumer's grep silently matches nothing.

The specific gap: a proof can pass while proving nothing, and no machine-readable signal says so. A contradictory kani::assume makes every assertion unreachable; the harness reports VERIFICATION:- SUCCESSFUL and exits 0. Kani's text output says ** 0 of 2 failed (2 unreachable) — a program reading the exit code sees a clean proof. This RFC's schema makes that distinction, and the vacuity ones like it, machine-readable.

Why not --sarif. SARIF is findings-shaped: Kani's writer skips cover properties and emits nothing for successful ones, so a fully green run produces an empty results array. Proof, cover and vacuity semantics would have to live in properties bags — a private schema wearing a standard schema's clothes — and --sarif must stay valid SARIF while this artifact must be free to change shape under -Z. One file cannot be both.

Relation to #4472. @yimingyinqwqq proposed this capability there and did substantial work; the design discussion on that PR shaped this proposal, and I'd genuinely welcome their review. This RFC takes 0016 rather than 0015 to leave that PR's number with it. Written RFC-first because that is what reviewers asked for on #4472.

Status. Implemented as a proof of concept; the schema example in the RFC is real output. Happy to open the implementation PR alongside, or keep this standalone — whichever you prefer.

What is deliberately excluded, and why
  • CBMC statistics (symex time, VCC counts, solver time) — available only inside CBMC's free-text messages. Extracting them means pattern-matching human-readable output, which is the fragility this RFC exists to remove; doing it inside the fix would be self-defeating.
  • Per-harness peak memory — prototyped via getrusage(RUSAGE_CHILDREN) and rejected: ru_maxrss is a process-wide running maximum, so any figure is order-dependent (only a harness that out-peaks all its predecessors gets one) and meaningless under --jobs. Honest measurement needs per-child accounting (wait4()-based rusage or a per-child cgroup) — future work, sketched in the RFC. CI consumers infer OOM from exit code 137 today.
  • A stable schema — deferred. The shape is a one-way door, so it stays behind -Z with an explicit schema_version until real consumers have exercised it.
Open questions I'd like input on
  1. Should a JSON Schema document ship alongside? That likely means a schemars dependency, which is not currently in the workspace — a real dependency decision rather than something to slip in.
  2. Should this cover the autoharness subcommand, or is that a follow-up once the harness-level shape settles?
  3. Coverage results (--coverage) — include here, or leave in their existing artifact? (Today code_coverage properties are outside checks/covers and outside n_properties, so the partition invariant holds; a dedicated bucket is the alternative.)
  4. configuration now records assertion_reach_checks, ignore_global_asm and extra_pointer_checks — the soundness-relevant toggles we identified. What other options merit the same treatment, and should there be an explicit policy for when a flag must be recorded?

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@ivmat
ivmat requested a review from a team as a code owner August 7, 2026 18:38
@ivmat

ivmat commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

sorry for spamming, but this would help a lot with automatization and it seems previous PR stalled

@feliperodri feliperodri added the T-RFC Label RFC PRs and Issues label Aug 8, 2026

@feliperodri feliperodri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, thanks, thanks! Thanks for writing this up! This is a genuinely well-constructed RFC, and I appreciate that the motivation is grounded in things that actually exist in the tree rather than hypotheticals. I checked the claims and they hold up: the TODO at kani-driver/src/cbmc_property_renderer.rs:190, update_properties_with_reach_status at cbmc_property_renderer.rs:624, the --output-format=old mocking path at call_cbmc.rs:92, the SARIF writer skipping covers and successes (sarif.rs:143 and sarif_level() at sarif.rs:233), and the kebab-case keys in list/output.rs:113. The exhaustive property bucketing, the atomic-write contract, and the "null never means a guess" discipline are all more carefully specified than most RFCs in this directory.

I'm not asking you to change the design. What I'd like resolved before merge is interface
completeness and one piece of sequencing:

  1. The --sarif alternatives section needs to argue on different grounds — see my inline comment.
  2. The enum domains (outcome.kind, verdict, failure_kind) need to be specified, not just shown by example.
  3. schema_version needs a compatibility policy and stabilization criteria attached to it.
  4. Process: #4472 still carries its own rfc/src/rfcs/0015-json-handler.md. Let's make this one the official RFC.

The rest of my comments are smaller and can be resolved in the same pass. Once these are addressed I'm happy to see this merged as Under Review and move on to reviewing the implementation.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
Comment on lines +298 to +309
Kani already emits SARIF, and SARIF is a good fit for reporting defects to code-scanning tools. It is
a poor fit for reporting *proofs*, for reasons of shape rather than quality:

- Kani's SARIF writer skips cover properties and emits nothing for successful ones, so **a fully
green run produces an empty `results` array**. For a prover, the all-green run is the most common
and most important case.
- SARIF's model is a finding anchored to a location. Proof, cover and vacuity semantics have no
native home in it; they would live in `properties` bags — a private schema wearing a standard
schema's clothes — and every code-scanning consumer of that same file would then see results it
does not want.
- `--sarif` must remain valid SARIF. This artifact must be free to change shape while it is `-Z`
gated. One file cannot be both.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the section I'd most like reworked, because as written it invites exactly the objection you're trying to close off...

Two of the three bullets describe our writer, not the format. Skipping covers is a choice at
kani-driver/src/sarif.rs:143, and the empty-results-on-green behaviour comes from sarif_level() at
sarif.rs:233 returning None for everything except Failure/Undetermined/Unknown. SARIF 2.1.0 itself has result.kind ∈ {pass, fail, informational, notApplicable, review, open} and property bags, so a reviewer can reasonably answer "then fix the writer" to both points. Worth noting too that --sarif is already in main and is not -Z gated, so the bar for a second machine-readable artifact is higher than it would have been a release ago.

I think the argument you want is about consumer contracts rather than expressive power:

  • The same file is consumed by code-scanning tools that specifically do not want proof, cover and
    vacuity rows; adding them degrades that consumer to serve a different one.
  • --sarif is stable and must stay valid SARIF; this artifact must be free to break shape while it's
    -Z gated. That's the load-bearing point and it's currently the third bullet (I'd lead with it).
  • Vacuity has no native SARIF representation, only a property bag, which is a private schema wearing a
    standard schema's clothes (you already say this so it survives the rewrite).

Could you also add a sentence on how the two artifacts avoid drifting apart? If both are derived from
one internal result structure that's a strong answer and worth stating explicitly; if they're
independent renderers, say so and say why that's acceptable.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
"stubs": [],
"verified_stubs": []
},
"outcome": { "kind": "COMPLETED", "verdict": "SUCCESS" },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're admirably rigorous about the property buckets, exhaustive by CBMC status, with an explicit
other catch-all so a consumer never has to guess where an unrecognised result went (lines 219–225).
I'd like the same treatment for the enums introduced here, which currently appear only by example:

  • outcome.kind"COMPLETED" here, "OUT_OF_MEMORY" referenced at line 397. What's the full set?
    I'd expect it to track ExitStatus (Timeout, OutOfMemory, Other) plus the completed and
    never-started cases, but a consumer shouldn't have to reverse-engineer that from call_cbmc.rs.
  • outcome.verdict"SUCCESS" here; presumably a failure counterpart, and possibly an absent/null
    case when the harness didn't complete.
  • failure_kind"NONE" at line 175; this looks like it maps to FailedProperties, so please
    enumerate it and say whether a catch-all applies.

Same question as for the buckets: is each of these closed, or does an unknown value get a catch-all? A
consumer writing an exhaustive match needs to know which.

Minor and related: the keys are snake_case but these values are SCREAMING_SNAKE. Your snake_case
rationale (lines 313–320) rests on reusing kani_metadata and cbmc_output_parser types, which
doesn't extend to these, they look like new types. Either justify the mixed convention or make it
uniform.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated

```json
{
"schema_version": "0.1.0",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a version field but no rules attached to it, and for an RFC whose whole subject is an
interface, that's the gap I'd most want closed. Could you add a short "Compatibility policy"
subsection covering:

  • What is a minor change vs. a breaking one? (I'd expect: adding a field or a new enum variant into an
    existing catch-all is minor; renaming, removing, or changing the meaning of a field is major.)
  • What must a consumer do to be forward-compatible? Explicitly stating "consumers must ignore unknown
    fields" is worth a line, because it's the difference between us being able to extend the schema and
    not.
  • What should a consumer do on an unknown major? Refuse to parse, presumably... then say so.
  • Is warnings inside or outside these guarantees? (See my separate comment; I think outside.)

And separately, per RFC 0006's stabilization section and the template's note that open questions must
be closed before stabilization: what has to be true before this leaves -Z? Even a three-bullet
checklist ("open questions resolved, N releases of consumer feedback, schema doc shipped or explicitly
declined") gives the next person a decision procedure instead of a judgement call.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
substantial work; the design discussion there shaped this proposal, and I would welcome
@yimingyinqwqq's review. Reviewers on that PR asked for an RFC first, for real unstable gating, for
CBMC data to come from `--json-ui` rather than scraped log text, and for a standard schema approach.
This RFC exists to settle those questions before code merges. It takes RFC number `0016` rather than

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the courtesy here, but let's add here as 0015. I'll take care of #4472

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
Comment on lines +366 to +367
- **Which other flags belong in `configuration`?** `assertion_reach_checks`, `ignore_global_asm` and
`extra_pointer_checks` are recorded because each changes what a run's results mean without changing

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker but... I'd rather this were answered in the RFC than shipped as an open question, because the curated-list
approach has a failure mode the RFC doesn't name: a flag added later that changes result meaning is
silently absent from files written before someone notices, and there's nothing in the artifact to
indicate the omission. That's the same class of silent failure you're arguing against in the
motivation.

The alternative I don't see discussed: record the resolved effective argument set (you already carry
enabled_unstable_features and cbmc_args, so this is an extension rather than a new idea), and keep
the curated configuration.checks block as a convenience view over it. Consumers that want a specific
toggle read the named field; consumers that need to know two runs weren't comparable can diff the
whole set.

If you'd rather stay curated, that's defensible but then please state the policy in the RFC rather
than leaving it open: any flag that changes which properties are generated, or changes what a status
means, must be recorded here.
That gives future PRs a test to apply.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
Comment on lines +216 to +218
This is the vacuity case made machine-readable: `outcome.verdict` is `SUCCESS` and Kani's exit code is
`0`, exactly like a harness that proved something — but `checks.unreachable` names both properties
that could not actually be exercised, so a consumer no longer has to trust the exit code alone.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vacuity is the RFC's headline motivation, and the example does make it visible, but a consumer still has to infer the rule from the example. Since a harness-level triviality flag is deliberately future work (line 383, and RFC 0003 already frames the concern), I'd like the RFC to state the exact predicate a consumer should apply, e.g. along the lines of checks.total > 0 && checks.success == 0 && !checks.unreachable.is_empty() whatever you consider correct.

Two reasons this is worth the paragraph. First, if every consumer derives its own rule, they'll derive subtly different ones and we'll get bug reports about it. Second, whatever the rule is, it's invalid when configuration.checks.assertion_reach_checks is false; you explain exactly why at lines 250–256, so tying the two together closes the loop and makes the schema self-documenting on its own central claim.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
cannot be discharged by the default solver. [PR #4719](https://github.com/model-checking/kani/pull/4719),
opened independently by a CBMC maintainer, surfaces this same class of dropped-quantifier warning
prominently — corroboration that this is a real gap, not a hypothetical one. These strings are CBMC's
internal-IR pretty-printer output verbatim, can run to several kilobytes with no promised structure,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with carrying these, and the "opaque, do not pattern-match" framing is the right one. Two things to add:

  1. State that warnings is outside the schema_version compatibility guarantees. The field's presence and shape are contractual, its contents are not. Otherwise a consumer can reasonably read the version promise as covering it.
  2. Size. Your own example is ~11.6 KB for a single warning. Multiply by the standard-library-scale runs this RFC is explicitly targeting (line 40) and the artifact can plausibly become larger than everything else in it combined, which is a problem for the CI consumers that are the whole point. Worth specifying a per-warning cap or per-harness limit with an explicit truncation marker, so a consumer can tell "no more warnings" from "we stopped recording".

Comment thread rfc/src/SUMMARY.md Outdated
- [0011-source-coverage](rfcs/0011-source-coverage.md)
- [0012-loop-contracts](rfcs/0012-loop-contracts.md)
- [0013-list](rfcs/0013-list.md)
- [0016-export-json](rfcs/0016-export-json.md)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not your doing, but visible in your diff: 0014-harness-partition never got a SUMMARY.md entry when it landed (#4228), so with your line added the book navigation reads 0013 → 0016. Could you fix
it as a drive-by here?

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
## User Experience

```
cargo kani -Z export-json --export-json results.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small things on the surface syntax:

  • Every other artifact-producing flag we have is a noun: --sarif <PATH>, --output-into-files. --export-json is a verb and doesn't say what's being exported — next to --sarif on a command line, something like --results-json <PATH> reads better and ages better if we ever emit a second JSON artifact. Not a hill I'll die on, but worth one paragraph in the rationale either way.
  • Worth also supporting - for stdout, or explicitly declining to; piping into a consumer without a temp file is a natural CI want, and it interacts with your atomic-write contract, so it's better settled in the RFC than discovered later.
  • Could you justify a dedicated -Z export-json over -Z unstable-options? Both patterns exist in the tree (--coverage gates on SourceCoverage; most plain options gate on UnstableOptions), and the template's footnote implies a per-RFC ident, so I think you're fine and I'd just like the choice stated rather than implied.

Comment thread rfc/src/rfcs/0016-export-json.md Outdated
the other failures above, but never changes the run's verdict.

`null` always means *not measured or not applicable*, never a guess, and is always distinguishable
from `0`, `false`, and `[]`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is the best part of the RFC! 🤩 The rename-based "exists implies complete" contract, the explanation of why the up-front delete is additionally needed, and the enumeration of what absence can and cannot distinguish. I hope we can keep this level of precision when the implementation PR lands; it's exactly the reasoning that's usually missing from output-format work.

feliperodri added a commit to yimingyinqwqq/kani-output that referenced this pull request Aug 12, 2026
RFC 0016 (model-checking#4727) is the spec under review for this feature and covers the
same ground in more detail. Keeping both would leave two competing
specifications for one flag, and would make the merge order between the
two PRs significant. The design discussion belongs in model-checking#4727; this branch
is the implementation.

Note this leaves RFC number 0015 unused. model-checking#4727 numbered itself 0016
precisely to reserve 0015 for this PR, so that choice may be worth
revisiting now that the file is gone.
@ivmat

ivmat commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@feliperodri thank you for detailed review of the PR. i admit i did not take all things into account, i wanted to start discussion in order to get faster feedback - which i got so thats great. i will fix all the comments, but just need one decision: should we aim for compelete version, i.e intial format to be stable but flexible (but strict per version)? meaning it comes with a schema and revision -- not a problem, just wondering. most of the added details are due to issues i experienced in rs-verified-der so for sure i may have missed some big things simply not having the need for them. so in short, how large coverage do you want from v1, model the full result or just a minimum as first revision. i dont mind which one but this raises another question - if we go for "full" format, could we do implementation in few PRs, simply due to possible size of work (if we go for all items included)?

@feliperodri

Copy link
Copy Markdown
Member

@ivmat we don’t need a full follow-up version here. Since PR #4472 has already been merged and this change is the first version, we can capture the remaining work as open questions or follow-up issues tied to this work/RFC rather than expanding the scope of this PR.

@ivmat

ivmat commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

@feliperodri ok i will then split enhancemnts into issues with each PR (or you want one issue delivery covering them all with one PR?)

ivmat added 14 commits August 24, 2026 20:57
Proposes an opt-in, -Z-gated --export-json <path> flag writing one
machine-readable file per verification run: per-harness status, failed
properties, check and cover outcomes bucketed exhaustively by status, the
warnings CBMC tags as such, and the provenance needed to reproduce the
run. Anchored on issue model-checking#942, which has requested exactly this document
since 2022. The schema example is genuine output of the proof-of-concept
implementation; the vacuity motivating case (a contradictory assume
reporting SUCCESSFUL) is shown end to end.
Rename 0016-export-json.md to 0015-export-json.md and list it (plus the
pre-existing 0014-harness-partition) in SUMMARY, so this supersedes the
0015 slot claimed by model-checking#4472's json-handler draft.

Review fixes (overnight lens + model review, local only):
- Remove the two <!-- Q2/Q3 --> review-scaffolding comments (belong in the
  PR thread, not the committed RFC; one re-asserted the [S] soundness label
  the maintainer ruled wrong).
- Value domains: distinguish the covers buckets (satisfied/unsatisfiable)
  from the checks buckets (success/failure) in the exhaustive-partition
  prose, matching the example and the enum table.
- Stabilization: attribute the open-questions-before-stabilization
  requirement to the RFC process/template, not RFC 0006 (0006's API
  Stabilization section does not state it; template.md does).
The "Fields that are not always strings" section called resolved_solver and
resolved_unwind "always-string counterparts", but resolved_unwind is a number
when present and null otherwise (as the worked example shows), and the global
null rule permits null. State them as plain scalars (string/number/null, never
objects) so a consumer following the prose does not break on the example's own
resolved_unwind: null.
model-checking#4472 merged on 2026-08-12 and ships --export-json behind
-Z unstable-options. The RFC no longer proposes a future flag. It now
specifies the contract for a shipped one.

- Summary and prior-work section state the as-built reality.
- The -Z gate section states the shipped gate and proposes the
  dedicated ident as a migration step.
- New first open question: does this schema supersede the shipped v1
  shape, or is the RFC redrawn around it.
- Drop the reference to closed PR model-checking#4732; run_state is proposed here,
  not implemented anywhere.
- 'Do nothing' section names the second cost: the shipped shape
  becomes a de-facto unversioned contract.
Three corrections and a style pass, no change to the proposal itself.

The directory-path bullet claimed argument-parse-time rejection as fact
while the write-behaviour section described it as a follow-up. It now
states the proposal and points at the note, so the two agree.

Two loose uses of "sound" and "soundness" described reporting and
consumer behaviour rather than a verdict, status or exit code. Reworded
to say what they meant. Also dropped a reviewer request from the body,
which belongs in the PR thread rather than the RFC.

The prose carried 81 em-dashes across 670 lines where peer RFCs in the
book use none, and leaned on "honest" as a stock adjective. Rewritten
with sentence splits, commas, colons and parentheses. Code spans and
fenced examples are byte-identical; the book still builds clean.
Two places described a path that is already a directory. The bullet under
flag interactions said this RFC proposes rejecting it at argument-parse
time; the write-behaviour note called that rejection a small follow-up,
which put the same rule inside and outside the proposal at once. An
earlier pass fixed the bullet and left the note, so the contradiction
survived. The note now says adopting the RFC includes moving the failure
to parse time, and that the shipped behaviour is an accident of
implementation order.

The completeness section pointed at "Q2". Open questions is an unnumbered
list and never mentioned the completeness mechanism, so the reference
resolved to nothing and the question the reference implied was open was
not actually posed. Added it: marker versus delete-up-front, with why
each fails in a different direction for a consumer that only checks
whether the file exists, and pointed the sentence at it by name.
Address two gaps found by building the writer and by the downstream-consumer
analysis:

- `tools`: a single object with kani/rustc/cbmc/goto-cc/goto-instrument versions
  and a solvers[] list, restoring the machine-readable tool provenance the
  shipped model-checking#4472 shape carried (kani issue model-checking#2572) that this schema had dropped.
- `selector`: the exact `--harness` string per harness (the module-qualified
  path), so an out-of-tree consumer has one stable, re-runnable key rather than
  reconstructing it from the file path.

Also record, as open questions, the three shipped fields still dropped
(workspace provenance, autoharness is_bounded/is_ctor_based, coverage.enabled)
so their removal is a decision rather than a silent regression.
Addresses blockers and majors from the review at rev 2e6cf78:

- Relabel the example as the proposed schema shown against a real PoC
  vacuity run, not a verbatim PoC dump (tools/run_state/warnings_truncated
  are proposed additions the PoC does not emit today).
- Drop the redundant `selector` field: `name` (pretty_name) is already
  the module-qualified, --harness --exact-selectable string; document its
  crate-scoped uniqueness, the --exact requirement, is_automatically_generated
  and proof_for_contract edge cases, and why not mangled_name.
- Add a normative field-reference table covering every field the example
  shows, including the failed_properties/unsupported_constructs/other[]
  element shapes, the checks/covers bucket-arithmetic invariant, and
  n_properties == checks.total + covers.total.
- Fix the failure_kind soundness bug: it is the raw failure classification,
  not NONE iff verdict == SUCCESS (a passing should_panic harness is
  PANICS_ONLY).
- Generalize the tools.* null rule, define solvers[] cardinality/ordering
  and its name-spelling agreement with resolved_solver, and add the
  conditional goto_synthesizer key.
- Expand the dropped-fields open question into a full shipped-vs-proposed
  disposition (build_mode, mangled_name, end_line, goto_file, per-check
  detail, OS info, cbmc stats, is_ctor_based verified present in the
  shipped writer); flag is_bounded/effective object_bits as soundness-
  relevant and proposed mandatory; fix the coverage.enabled drop, which
  contradicted the RFC's own configuration policy.
- Medium fixes: define file's base directory and connect it to the
  dropped workspace_root; fix the completeness-contract stale-file
  window wording; narrow the summary's reproduce-the-run claim; state
  the property-id format and ordinal-instability caveat; note the
  Serialize-derive gap on reused cbmc_output_parser types; add exact-
  minor pinning guidance for 0.x consumers.

No design change: shape, vacuity predicates, and enum domains are untouched.
Source-verified against kani-driver's call_cbmc.rs, cbmc_output_parser.rs,
frontend/schema_utils.rs, and kani_metadata for every claim below.

Must-fix:
- failure_kind: replace the wrong "disagree in exactly one case" / "NONE
  strictly stronger than SUCCESS" prose with the full should_panic truth
  table (call_cbmc.rs's verification_outcome_from_properties); scope the
  field to outcome.kind == COMPLETED, omitted otherwise.
- Delete the stale top-level kani_version field-table row (absent from the
  example, superseded by tools.kani); reword tools.kani to stand alone and
  note it is the one tool version that is never null.
- Add a presence matrix (marker vs terminal document; per-harness fields by
  outcome.kind) so "never absent/null" claims are correctly scoped instead
  of contradicted by INCOMPLETE markers and TIMEOUT/OOM/CRASHED harnesses.
- Write the missing "Summary" section: define all 7 summary.* fields, the
  total == len(harnesses) and matched_count/total/run_state invariants, and
  how non-COMPLETED harnesses count (neither successful nor failed).
- Define the 4 previously-undefined configuration.checks.* bools
  (memory_safety, overflow, unwinding, undefined_function).
- Document the crate_name (rustc, underscored) vs cargo -p (Cargo package,
  often hyphenated) mismatch on the workspace re-run recipe.

Field-mandatory decisions applied as ruled:
- Promote harnesses[].is_bounded to a mandatory field (out of Open
  Questions), with a full definition of when it is true.
- Promote configuration.coverage_enabled to a mandatory field.
- Keep effective object_bits as an explicit open question, split cleanly
  from is_bounded, citing the model-checking#4731 triage.

Should-fix: failed_properties[] membership + the n_failed ==
len(checks.failure) invariant + the class=="cover" partition criterion;
a tools.solvers[].source ("builtin"|"probed") sibling to disambiguate an
overloaded null version, plus the goto_synthesizer "requested" vs "ran"
wording fix; the compatibility-policy minor-change clause scoped to
explicitly-open vocabularies (solver names) instead of an empty referent,
with attributes.kind/attributes.solver added to the value-domain table;
a run-scoped CRASHED example message, a run_state x outcome.kind
co-occurrence note, NO_HARNESSES_SELECTED vs a zero-harness crate, and a
recommended symmetric covers-vacuity check.

Nits: structural truncated/original_chars fields replacing the
free-text truncation marker, a run-level OUT_OF_MEMORY scope note, and an
under-listing fix in the example's framing paragraph.

Validated: the main JSON example and the warnings example both
json.loads() cleanly; mdbook build is error-free (pre-existing footnote
warnings in other RFCs only).
Removes run-level outcome.kind == CRASHED: the shipped writer's single
terminal write (verify_project in kani-driver/src/main.rs) happens once,
after every harness result is known, so any hard error before that point
(a kani-compiler crash, a driver panic, even a failed CBMC spawn for one
harness) unwinds past it and no terminal document is ever written. A Kani
self-crash therefore can only ever surface as a stale INCOMPLETE marker
(or an untouched pre-existing file, if the crash predates the marker),
never as a CRASHED value in a document that doesn't exist. Adds a
run_state/outcome co-occurrence table covering every combination the
schema can actually produce, and removes the now-dead run-level
outcome.code/outcome.message fields (per-harness CRASHED, code, and
message are unaffected and remain fully producible).

Adds a forward-looking paragraph stating the schema is deliberately open
to growing richer per-harness provenance/evidence over versions via the
additive minor-version rule, rather than presenting v1 as a ceiling.

Folds in several smaller consistency fixes: scopes the field-table "-"
legend and the summary "always present" wording to terminal documents
(both are absent in the INCOMPLETE marker); fixes the one remaining
goto_synthesizer "ran" wording to say "requested", matching
schema_utils.rs; fixes tools.solvers[].source to derive builtin/external
from the actual resolution path (effective_solver's binary vs no-binary
case) instead of the solver name, resolving the --sat-solver cadical vs
--external-sat-solver cadical collision; states the schema_version-then-
run_state consumer read order explicitly in both places it's implied;
and merges the two NO_HARNESSES_SELECTED definitions into one.
Tighten the export-json RFC prose without touching normative content.
Collapse multi-sentence explanations to single statements, delete
restatements of field-table cells, cut over-hedging / meta-commentary,
and trim rationale to its load-bearing core.

Preserved verbatim: every table (field reference, value domains, presence
matrices, run_state x outcome, failure_kind truth table), both JSON
examples, and all code blocks. No field, type, nullability, predicate,
guard, enum domain, compatibility rule, mandatory decision (is_bounded /
coverage_enabled), open question, or out-of-scope item removed.

1302 -> 1035 lines; 100657 -> ~74KB; prose bytes 80554 -> 54909 (-32%).
mdbook build clean; both JSON examples still parse.
Reorganize the document so the human-read body is ~half length, with the
exhaustive machine-contract detail relocated (not deleted) to a new
"Normative schema reference" appendix.

Body keeps the design narrative: Summary/User Impact, the vacuity gap, the
model-checking#4472 relationship, User Experience and flag interactions, the JSON example,
the two vacuity predicates, the key decisions (is_bounded/coverage_enabled
mandatory, name is the selector), the run_state completeness contract, and
the Rationale, open questions, and out-of-scope sections.

Appendix collects the field-reference tables, the presence matrices, the
value-domain/enum table, the run_state x outcome co-occurrence table, and
the failure_kind truth table, plus the per-field contract prose. All tables,
JSON, and code blocks are byte-identical; no content removed. Also a light
plain-English polish pass on relocated prose.
… + machine.*, defer to Future

Per model-checking#4731 (feliperodri: 'cut v1 to the core'). Removes from the v1
schema: tools.goto_cc/goto_instrument/goto_synthesizer, tools.solvers[] ({name,version,
source}), and the whole machine.* block (cpu_count/total_memory_bytes/memory_limit_bytes/
os/arch). Keeps tools.kani/rustc/cbmc and per-harness resolved_solver (the solver actually
used stays recoverable). Both cut groups added to Out-of-scope/Future with a 'file a tracking
issue on adoption' note. Updated: JSON example, field-reference table, Tool-provenance prose,
presence matrix, value-domain table (solver spellings 3->2), compatibility-policy open-vocab
list. Example JSON re-validated; mdbook builds clean.
@ivmat
ivmat requested a review from a team as a code owner August 28, 2026 18:07
@ivmat

ivmat commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

@feliperodri misundestood, thoguht id have to discard this pr -- but now i understand this should stay PR for the RFC.

changes requested from this review:

  1. --sarif alternatives — reworked to argue on format grounds, conceding SARIF 2.1.0 could carry this and that skipping covers/successes is our writer's choice, so it no longer invites "then fix the writer."
  2. Enum domainsoutcome.kind, verdict, and failure_kind now have explicit value-domain tables (with a failure_kind truth table) instead of appearing only by example.
  3. schema_version — a new Compatibility policy section defines minor vs major changes and the "consumers must ignore unknown fields" rule.
  4. Official 0015 — renumbered 0016 → 0015 and fixed the SUMMARY.md nav gap; left Add --export-json for structured verification results #4472's own 0015 file to you.
  5. Sorted harnesses[] — now specified as sorted by (crate_name, file, line, name), so --jobs N output is stable and diffable.
  6. --only-codegen — now explicitly rejected, like --sarif.
  7. Vacuity predicate — stated exactly, with a note on the partial-vacuity case it misses.
  8. warnings — declared outside the schema_version guarantees and bounded with explicit truncation markers.
  9. Flag name / stdout — added a --export-json vs --results-json rationale (name left open) and explicitly declined --as-stdout.
  10. Curated flag-listenabled_unstable_features (sorted -Z) is now recorded; a full resolved argv is not, so flag it if you want that.

extra changes:

  1. Cut v1 to the core (applying your --export-json: a failed, empty, or partial run can serialize as a clean pass #4731 steer) — dropped cbmc_stats, the auxiliary tool/solver provenance (tools.goto_cc/goto_instrument/goto_synthesizer, tools.solvers[]), the machine block, and object_bits, keeping tools.kani/rustc/cbmc and per-harness resolved_solver, each deferred to Out-of-scope with a tracking-issue note.
  2. Honest caveat on that cut — it drops external-solver binary versions, so a solver upgrade that flips an UNDETERMINED is no longer captured (named as the restore trigger in Future work).
  3. Completeness is now a 4-value run_state with a pre-verification INCOMPLETE marker — a deliberate change from the rename-only contract, because rename alone leaves a stale prior COMPLETE file readable if a re-run dies during build (marker vs your up-front delete left as an open question).
  4. crate_name added, join key (crate_name, name) — fixes cross-workspace misattribution, but differs from --export-json: a failed, empty, or partial run can serialize as a clean pass #4731's mangled_name suggestion, which the RFC explicitly declines.
  5. is_bounded mandatory on every harness — a bounded result read as unrestricted is the over-claim that bit a downstream consumer, so it's non-optional.
  6. Semantic apparatus added (presence matrix, value-domain tables, counting identities) — this is the "no leaf-value validation" answer at the spec level, with a shippable JSON Schema still an open question.
  7. name is the selector — no separate selector field, since the re-run string is pretty_name.
  8. Structure — normative tables moved to an appendix to roughly halve the read body (editorial only).

if anything of extra things should be removed, no problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-RFC Label RFC PRs and Issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants