Skip to content

Add --export-json for structured verification results - #4472

Merged
feliperodri merged 86 commits into
model-checking:mainfrom
yimingyinqwqq:main
Aug 12, 2026
Merged

Add --export-json for structured verification results#4472
feliperodri merged 86 commits into
model-checking:mainfrom
yimingyinqwqq:main

Conversation

@yimingyinqwqq

@yimingyinqwqq yimingyinqwqq commented Nov 13, 2025

Copy link
Copy Markdown
Contributor

Add opt-in JSON export (--export-json ) to emit structured verification results (metadata, per-harness outcomes, CBMC stats).
Improves Kani by enabling reliable machine-readable output for external tools and applications.

Context: Current output is human-readable only, which blocks robust automation and integrations.

Manual tests:
• Run cargo kani --export-json out.json
• With multiple harnesses: counts in summary match executed harnesses.

Resolves #2572

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

ConnorJKY and others added 30 commits September 10, 2025 20:09
…n-handler

# Conflicts:
#	kani-driver/src/main.rs
…schedule the schema for harness metadada util func.
…p tests

Three of the five comments were valid and are fixed here.

**The JSON pipeline ran unconditionally.** Metadata construction, the
per-harness JSON building and `process_cbmc_results` — which spawns
`cbmc --version` — all ran even without `--export-json`, with only the
final `export()` being a no-op. The handler is now built only when the
flag is present, so runs that don't use the feature do none of the work.

**`error_details` was overwritten per harness, and could report a failing
run as clean.** It used `add_item` inside the per-harness loop, so only
the last harness survived, with no harness id attached. On a three-harness
run where one harness genuinely failed, the export said
`{"has_errors": false}` — a false negative in machine-readable output,
which is the failure mode this feature exists to prevent. It now
accumulates one entry per harness keyed by `harness_id`, matching how the
`cbmc` array already works. Copilot suggested the smaller fix of not
letting a success overwrite a failure; that removes the false negative but
still reports only one harness per run and cannot say which, so this takes
the per-harness form instead.

**Exec tests only cleaned up on the happy path.** A failing validation
step exits early under `set -e` and left the export behind. Replaced the
trailing `rm -f` with an EXIT trap in all four tests.

While in `process_harness_results`, two related fixes:

- `property_details` entries now carry `harness_id`. The array is built in
  harness-metadata order while `verification_results.results` is in
  completion order, so correlating the two by position was silently wrong.
- `unreachable` was derived as `total - passed - failed`, which counted
  undetermined and error properties as unreachable. It now counts
  `Unreachable` directly, and reports `undetermined` separately.

Schema template and the failed-verification assertions updated for the new
`error_details` shape.

Also applied Copilot's `as_deref().unwrap_or("unknown")` suggestion in
`create_verification_result_json`. Not because the previous code was
broken — it compiled and behaved correctly, since the temporary outlives
the `to_value` call — but because it avoids three needless allocations per
property, and properties can run to thousands.

The remaining two comments claimed borrow/compile errors in
`create_harness_metadata_json`, `create_verification_result_json` and
`JsonHandler::export`. Those are incorrect: `json!` serializes through
`to_value(&expr)` rather than moving, and serde_json provides
`impl From<serde_json::Error> for std::io::Error`. The code builds clean
and clippy-clean with `-D warnings`.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (11)

kani-driver/src/args/mod.rs:248

  • --export-json needs conflict validation for modes that cannot produce a real export. With --only-codegen, verify_project is skipped and the command succeeds without creating the requested file; with --output-format=old, run_cbmc constructs a mock VerificationResult, so the JSON can report a fabricated success with no checks. Reject these combinations, as is already done for SARIF.
    /// Output the verification results to a JSON file at the specified path.
    /// This feature is unstable and it requires `-Z unstable-options` to be used
    #[arg(long)]
    pub export_json: Option<PathBuf>,

kani-driver/src/frontend/schema_utils.rs:46

  • This exports the artifact output directory as workspace_root. Project::outdir is explicitly the compiler output directory (project.rs:33-37), and for Cargo it points under target/.../debug/deps, not to the workspace. Use Cargo metadata's workspace_root for Cargo projects and define the standalone equivalent from the input source path.
    "workspace_root": project.outdir.clone(),

kani-driver/src/frontend/schema_utils.rs:255

  • These values do not reliably describe the CBMC invocation. cbmc_object_bits() returns None when --object-bits is supplied through --cbmc-args, and the solver ignores session.args.solver, even though that option takes precedence over harness attributes in call_cbmc.rs:425-431. Export the same effective configuration used to build the command so consumers do not receive null/Cadical for explicitly configured runs.
                "object_bits": session.args.cbmc_object_bits(),
                "solver": h.attributes.solver.as_ref().map(|s| format!("{:?}", s)).unwrap_or_else(|| "Cadical".to_string()),

kani-driver/src/main.rs:195

  • The PR marks coverage issue #2636 as resolved, but the export only records whether coverage was enabled; none of the per-harness coverage_results are serialized, and the existing user-facing coverage output is unchanged. Export the coverage outcomes or remove the issue-closing claim.
    if let Some(handler) = handler.as_mut() {
        handler.add_item("coverage", json!({"enabled": session.args.coverage}));
        handler.export()?;

kani-driver/src/frontend/schema_utils.rs:224

  • For timeout, OOM, or another CBMC execution error, this object omits passed, failed, unreachable, and undetermined, although the bundled schema marks all four as required. Such exports therefore fail the new validator. Keep the shape stable and represent unavailable counts explicitly (or mark them optional in the schema).
                    Err(_) => json!({
                        "total_properties": 0,
                        "error": "Could not extract property details due to verification failure"
                    })

scripts/validate_json_export.py:85

  • Only validating element zero lets malformed data in every later harness/result pass schema validation. Iterate over every array element against the item template so multi-harness exports are actually covered.
            # Validate first item against schema template
            sub_errors = validate_structure_recursive(data[0], schema[0], f"{path}[0]")[
                1
            ]
            errors.extend(sub_errors)

scripts/validate_json_export.py:87

  • The validator performs no scalar type validation, and a schema object paired with a non-object also falls through here successfully. Consequently fields such as metadata, numeric counts, and booleans can have invalid JSON types while the “schema validation” passes. Encode expected and nullable types (ideally with JSON Schema) and reject type mismatches.
    # Leaf values - no validation needed

tests/json-handler/basic-export/test.sh:7

  • Without pipefail, a nonzero validator exit is hidden by the successful tail, so this integration test passes even when schema validation fails. Enable pipeline failure propagation.
set -eu

tests/json-handler/multiple-harnesses/test.sh:7

  • Without pipefail, a nonzero validator exit is hidden by the successful tail, so this integration test passes even when schema validation fails. Enable pipeline failure propagation.
set -eu

kani-driver/src/main.rs:149

  • The PR description also declares unrelated issues resolved (for example #1219's compiletest --help panic and #3357's non-terse parallel output), but this change does not touch those paths and the existing non-terse parallel restriction remains. Remove the unrelated Resolves entries to avoid closing still-open problems.
    // Only build the JSON document when `--export-json` asks for one. Everything below it is
    // overhead for every other run, including a `cbmc --version` probe in `process_cbmc_results`.
    let mut handler =
        session.args.export_json.as_ref().map(|path| JsonHandler::new(Some(path.clone())));

tests/json-handler/multiple-harnesses/test.sh:37

  • This test only counts metadata entries, so it does not verify the aggregation behavior stated in the PR (summary counts matching executed harnesses). Assert the results length and all summary counters for the three successful harnesses.
HARNESS_COUNT=$(python3 -c "import json; data=json.load(open('$OUTPUT_FILE')); print(len(data['harness_metadata']))")

…failures

Three defects that all let this feature report something untrue.

**`--export-json` accepted two modes that cannot produce a real export.**
With `--only-codegen`, verification never runs, so the command exited 0
and silently wrote no file at all. With `--output-format=old`, `run_cbmc`
mocks a `VerificationResult` with no properties and treats a timeout as
success, so the export was produced from fabricated data: a summary with
zero checks that a consumer cannot distinguish from a clean run. Both are
now rejected up front, as `--sarif` already does for the same two modes.

**The tests could not fail on validation errors.** `basic-export` and
`multiple-harnesses` pipe the validator into `tail`, and without
`pipefail` the pipeline reports tail's exit status. The validator's own
result was discarded, so the validation step in two of the four tests was
inert:

    $ python3 validate_json_export.py bad.json 2>&1 | tail -1
    Validation failed for bad.json
    $ echo $?
    0

**Exports from a run with no CBMC results failed the bundled validator.**
On timeout, out of memory, or a CBMC error, `property_details` dropped to
`total_properties` plus an `error` string, while the schema requires
`passed`, `failed`, `unreachable` and `undetermined` — so precisely the
runs a consumer most needs to interpret produced a file that Kani's own
validator rejects. The counts are now always present, and reported as
null rather than 0: `0 failed` asserts that nothing failed, when the truth
is that nothing was measured. `error` is marked optional in the schema.

Note the third defect was made more visible by the earlier commit here
that added `undetermined` to the counts, taking it from three missing
fields to four.

Verified: both conflicts are now rejected with an explicit message before
verification starts, and a forced CBMC error produces an export that
passes the validator.
Three exported values described something other than the run that
happened.

**`solver` ignored `--solver`.** The export read `h.attributes.solver` and
defaulted to the string "Cadical", while `handle_solver_args` gives
`--solver` precedence over the harness attribute. A run verified with
`--solver minisat` therefore exported `"solver": "Cadical"` — not missing
data, but wrong data, about a setting that changes how a result should be
read. The precedence chain is now a single `resolved_solver` method that
`handle_solver_args` also uses, so the command line and the export cannot
drift apart.

**`object_bits` reported null for explicitly configured runs.**
`cbmc_object_bits()` deliberately returns `None` once `--object-bits` comes
through `--cbmc-args`, since Kani then stops passing its own default. That
is right for building the command and wrong for describing it, so
`effective_object_bits` falls back to the value in `--cbmc-args`.

**`workspace_root` was the compiler output directory.** `Project::outdir`
is documented as the directory outputs are written to; for Cargo it sits
under `target/<triple>/debug/deps`. It is now reported as `output_dir`,
and `workspace_root` comes from Cargo metadata, which carries the real
one — null for a standalone run, which has no workspace.

Verified on a Cargo project:

    "workspace_root": "/private/tmp/cargocheck",
    "output_dir": "/private/tmp/cargocheck/target/kani/aarch64-apple-darwin/debug/deps"

and `--solver minisat` now exports `"solver": "Minisat"`, while
`--cbmc-args --object-bits 20` exports `"object_bits": 20`.
Two tests asserted much less than they appeared to.

**The validator only checked the first element of every array.** Malformed
data in any later harness passed validation, which defeats the purpose on
exactly the multi-harness exports it exists to check. Confirmed against
the previous version: an export with `cbmc[1].configuration` removed
passed, and now reports

    Missing required field: cbmc[1].configuration

**`multiple-harnesses` only counted `harness_metadata` entries.** The PR
offers "counts in summary match executed harnesses" as its validation, and
nothing tested that. It now checks the summary counters, the results
length and their statuses, and that `error_details`, `property_details`
and `cbmc` each cover all three harnesses exactly once -- by `harness_id`,
since those arrays are built in a different order from `results`, so
identity cannot come from position.

Verified the new assertions actually fail, by mutating a real export four
ways: a wrong summary counter, a dropped `error_details` entry, a flipped
harness status, and a missing `harness_id`. Each is reported as a distinct
message rather than a traceback, including when a missing id leaves `None`
in a set that then gets sorted for the error text.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (5)

kani-driver/src/harness_runner.rs:115

  • With --fail-fast, this vector contains only the failing harness. Any successful harnesses executed before the failure are discarded by the collect::<Result<Vec<_>>>() above, so the exported executed count becomes 1 and those real executions disappear from results. Preserve completed results (including the failure) when building the fail-fast report so the machine-readable summary reflects what actually ran.
                    let result = vec![HarnessResult {
                        harness: sorted_harnesses[failed.index_to_failing_harness],
                        result: failed.result,
                    }];

kani-driver/src/frontend/schema_utils.rs:224

  • The exported property totals cannot be reconciled for valid CBMC statuses. CheckStatus also includes Unknown, Error, Covered, Uncovered, Satisfied, and Unsatisfiable; none are counted here (and Unknown, which Kani renders as undetermined, is omitted from undetermined). As a result, these category counts can sum to less than total_properties. Export every status or define an exhaustive grouping so consumers do not receive incomplete statistics.
                        json!({
                            "total_properties": properties.len(),
                            "passed": count(CheckStatus::Success),
                            "failed": count(CheckStatus::Failure),
                            // Counted directly rather than derived by subtraction, which silently
                            // reported undetermined and error properties as unreachable.
                            "unreachable": count(CheckStatus::Unreachable),
                            "undetermined": count(CheckStatus::Undetermined)
                        })

kani-driver/src/frontend/schema_utils.rs:290

  • This can report the wrong solver when users select one through the supported --cbmc-args escape hatch (for example, --cbmc-args --sat-solver minisat). Those arguments are appended after Kani's generated solver flags, but resolved_solver only considers --solver, the harness attribute, and the default. Resolve direct CBMC solver arguments too, or reject them with --export-json, so configuration.solver describes the command that actually ran.
            "configuration": {
                "object_bits": effective_object_bits(session),
                "solver": format!("{:?}", session.resolved_solver(&h.attributes.solver)),
            },

kani-driver/src/args/mod.rs:248

  • The PR description marks several unrelated issues as resolved, including #1219 (a compiletest --help panic), #2636 (human-friendly coverage output), and #3357 (parallel non-terse output), but this option and the accompanying changes do not implement those requests. Remove the unrelated Resolves entries or implement their acceptance criteria; otherwise merging this PR will incorrectly close open work.
    /// Output the verification results to a JSON file at the specified path.
    /// This feature is unstable and it requires `-Z unstable-options` to be used
    #[arg(long)]
    pub export_json: Option<PathBuf>,

scripts/validate_json_export.py:90

  • The validator never checks leaf types or values, so malformed exports such as string summary counts, numeric status values, or null metadata pass as long as the keys exist. That means the new integration tests do not actually validate the structured contract represented by the template. Validate primitive types (and fixed enum/version values where applicable), or use a real JSON Schema validator.
    # Leaf values - no validation needed

@feliperodri

Copy link
Copy Markdown
Member

Two comments I'm deliberately not acting on in this PR

Both are legitimate, but both are decisions for RFC #4727 rather than fixes:

Type validation / adopting a real JSON Schema. Correct that the current validator does no scalar
type checking, so a numeric count could be a string and validation would still pass. But "ideally
with JSON Schema" is precisely the open question in RFC 0016: shipping a schema document implies a
schemars dependency that isn't in the workspace today, which is a real dependency decision rather
than something to add quietly. Anything homegrown I write here gets thrown away when that's settled,
so I'd rather leave the gap documented than build a second half-validator. Happy to do it either way
if reviewers prefer type checks in the interim.

Exporting coverage results. Also correct that the export only records whether coverage was
enabled and serializes none of the per-harness coverage_results. RFC 0016 lists "coverage results —
include here, or leave with kani-cov?" as an open question and defers aggregate coverage to
kani-cov and RFC 0011. Settling it inside an implementation PR would pre-empt the RFC, so the fix
here is to stop claiming it: the Resolves #2636 line is going away with the PR description rewrite,
along with the other issue-closing claims that don't hold — #1219 (compiletest --help panic) and
#3357 (non-terse parallel output) aren't touched by this change either, and the non-terse parallel
restriction is still in place. The description will say Tracks #942 and nothing more.

Three comments that don't hold

Flagging these so they don't get re-raised:

  • JsonHandler::export using ? on serde_json::to_string_pretty compiles fine — serde_json
    provides impl From<serde_json::Error> for std::io::Error. The suggested
    io::Error::new(io::ErrorKind::Other, e) would also be a step back, since ErrorKind::Other is
    discouraged for this and io::Error::other exists.
  • create_harness_metadata_json and create_verification_result_json do not move owned Strings
    out of a borrow: json! serializes through to_value(&expr). Adding & is a no-op.
  • The basic-export cleanup trap was already added in 5d23989, which the suggestion notes as
    outdated.

The branch builds clean and passes cargo clippy --workspace --tests -- -D warnings, both clippy
passes, cargo test -p kani-driver, and the json-handler (4), ui (144) and coverage (20)
suites locally.

Partially addresses model-checking#2572, which asks for the versions of all the tools
Kani relies on rather than just CBMC's. This covers the machine-readable
half; the issue also asks for them to be printed after harness metadata
collection, which this does not do, so the issue stays open.

The new top-level `tools` object reports:

- `kani`, and `rustc` from `kani-compiler --version`. kani-compiler is a
  rustc driver, so it reports the toolchain it was built against, which is
  the version that decides how Rust is translated. Asking the binary beats
  reading a `rustc` from PATH, which need not be the same toolchain.
- The CBMC suite Kani actually invokes: `cbmc`, `goto_cc`,
  `goto_instrument`, and `goto_synthesizer` when loop-contract synthesis
  runs.
- `solvers`, one entry per distinct solver the run resolves to, which can
  differ per harness. A list rather than a map, since the set varies per
  run and consumers should not have to guess which keys might appear.

Three conventions worth stating, since this is interface:

- A key is present only when the run uses that tool, so an absent key means
  "not part of this run" while a present null means "used, but its version
  could not be determined". `goto_synthesizer` is marked optional in the
  schema for this reason.
- CaDiCaL and MiniSAT are built into CBMC and would report CBMC's version,
  so they are named with a null version rather than given a misleading one.
- Versions are verbatim first lines of `--version` output, so they are
  display strings and not to be parsed. `goto-cc`, for instance, reports
  `clang version 21.0.0 (goto-cc 6.10.0 (cbmc-6.10.0))`.

Probing costs one process per tool, paid only when `--export-json` is
requested, once per run rather than per harness, and each binary once
however many harnesses use it. A probe that fails yields null and never
fails the run.

Verified across four configurations: a default run, `--solver kissat`,
`--synthesize-loop-contracts`, and a two-harness run where one harness
carries `#[kani::solver(kissat)]` -- which reports both solvers, matching
the per-harness `configuration.solver` values.
Two exported values could not be trusted to describe the run.

**The per-status property counts did not add up.** `CheckStatus` has ten
variants and only four were counted, so any run with cover statements,
coverage properties or a solver error produced counts that silently summed
to less than `total_properties` -- and a consumer had no way to tell that
from a run where they genuinely reconciled. A harness with two covers went
from

    total_properties: 3, passed: 1, failed: 0, unreachable: 0, undetermined: 0

to

    total_properties: 3, passed: 1, satisfied: 1, unsatisfiable: 1, ...

The counts now partition the properties exhaustively, via a `match` with no
wildcard so that a new `CheckStatus` fails to compile here rather than
quietly going uncounted. `Unknown` is grouped with `undetermined` because
that is how Kani renders it. The `CheckStatus::Error` count is exported as
`solver_error`, since `error` is already the message field on the
unmeasured path. The no-results path reports every count as null, as
before.

I should note this was partly self-inflicted: adding `undetermined` in an
earlier commit here made the set look authoritative when it still wasn't.

**`configuration.solver` ignored the `--cbmc-args` escape hatch.**
`--cbmc-args` is appended after Kani's own solver flags and CBMC takes the
last one it sees, so `--cbmc-args --z3` overrides `--solver` and the
harness attribute. `resolved_solver` knows nothing about that, so the
export named the wrong solver. `effective_solver` now scans `--cbmc-args`
for `--sat-solver`, `--external-sat-solver`, `--z3`, `--cvc5`, `--bitwuzla`
and `--smt2`, last one winning, and reports null when the override leaves
the choice to CBMC -- a wrong name is worse than no name.

Verified across six configurations:

    baseline                          "Cadical"
    --solver kissat                   "Kissat"
    --cbmc-args --z3                  "z3"
    --solver kissat --cbmc-args --z3  "z3"
    --cbmc-args --smt2                null
    --cbmc-args --sat-solver cadical  "cadical"

The multiple-harnesses test now asserts the reconciliation invariant, so a
future status that escapes the partition fails a test rather than shipping
incomplete statistics.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.

Suppressed comments (4)

kani-driver/src/frontend/schema_utils.rs:96

  • tools.solvers ignores solver overrides passed through --cbmc-args. Those arguments are appended after Kani's solver flags (call_cbmc.rs:357), and this file already resolves them for cbmc[].configuration.solver, so an invocation such as --solver cadical --cbmc-args --z3 exports contradictory metadata: Cadical here but Z3 in the per-harness configuration and actual run. Derive the version probes from the same effective-solver resolution, including --external-sat-solver.
        let (name, binary) = match session.resolved_solver(&harness.attributes.solver) {

kani-driver/src/harness_runner.rs:122

  • With --fail-fast, result contains only the failing harness because the Result<Vec<_>> collection discarded any successful results completed before the error. Serializing this singleton makes summary.executed, successful, duration_ms, and the per-harness arrays underreport what actually ran (especially with parallel jobs). Preserve completed results through the fail-fast path before generating the JSON summary.
                        add_runner_results_to_json(
                            handler,
                            &result,
                            harnesses.len(),
                            "completed_with_fail_fast",

scripts/validate_json_export.py:52

  • When the template expects an object but the exported value is not an object, this condition falls through to the leaf case and reports success. For example, {"metadata": null, ...} bypasses every required metadata field, so the schema tests can accept structurally malformed exports. Reject object type mismatches and explicitly model the few fields that are legitimately nullable.
    # Handle dict validation
    if isinstance(schema, dict) and isinstance(data, dict):

tests/json-handler/basic-export/test.sh:21

  • All new end-to-end scripts invoke standalone kani; none exercises the advertised cargo kani --export-json path. That leaves Cargo argument handling and Cargo-only project metadata such as workspace_root unverified. Add a cargo-kani integration case that exports and validates a small Cargo project.
# Run Kani with JSON export
kani -Z unstable-options test.rs --export-json "$OUTPUT_FILE"

Comment thread kani-driver/src/main.rs
Comment thread kani-driver/src/args/mod.rs Outdated
…tor holes

Five issues from review, all reproduced first.

**A run with no harnesses wrote a document missing four of its own keys.**
The per-harness arrays are filled in lazily, so `kani --harness
does_not_exist --export-json out.json` produced a file without
`harness_metadata`, `error_details`, `property_details` or `cbmc` -- one
that Kani's own validator rejects. Worse, the "no harnesses matched" error
is only reported after the export, so a consumer sees the malformed file
before Kani admits nothing ran. All four keys are now declared up front.

**`--no-codegen` is now rejected alongside `--only-codegen`.** For
`cargo kani` it returns an empty `Project`, so
`cargo kani --no-codegen --export-json out.json` wrote a document claiming
a completed run with zero harnesses. Standalone `kani --no-codegen` already
failed for unrelated reasons, but the combination was still accepted.

**`tools.solvers` and `configuration.solver` could contradict each other.**
`configuration.solver` accounted for solvers named in `--cbmc-args` while
`tools.solvers` did not, so `--solver cadical --cbmc-args --z3` exported
Cadical in one place and z3 in the other. Both now go through a single
`effective_solver`, which also distinguishes `--sat-solver` (built into
CBMC, so no binary to probe) from `--external-sat-solver` (a binary, so its
version is probed):

    --solver cadical --cbmc-args --z3         both report z3
    --cbmc-args --sat-solver minisat          minisat, version null
    --cbmc-args --external-sat-solver kissat  kissat, version 4.0.1
    --cbmc-args --smt2                        null, and no solver listed

**The validator passed structurally malformed exports.** A template object
paired with a non-object fell through to the leaf case and reported
success, so `{"metadata": null}` bypassed every field required beneath it.
Object and array mismatches are now errors. Since `cbmc_stats` is
legitimately null when CBMC reported no statistics, the schema gained a
`_nullable` marker rather than making nulls universally acceptable.
Verified it now rejects a null object, an object where an array belongs,
and a null array, while still accepting a real export and one from a failed
CBMC run.

**Nothing exercised `cargo kani --export-json`.** Every test drove
standalone `kani`, which can never check the Cargo-only project metadata --
`workspace_root` is null for standalone runs, so the field fixed earlier in
this branch had no automated coverage. The new `cargo-export` test
scaffolds a crate outside the repository, exports, validates, and asserts
that `workspace_root` is the crate root and differs from `output_dir`.
Confirmed it fails when that expectation is broken.

Two known gaps remain, both deliberate. The validator still does not check
leaf types, so a count could be the string "1" -- that is the JSON Schema
question RFC 0016 leaves open, since doing it properly means a `schemars`
dependency. And `--fail-fast` still under-reports what ran; that is
pre-existing behaviour affecting the rendered summary too, now tracked in
model-checking#4729.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated no new comments.

Suppressed comments (3)

kani-driver/src/frontend/schema_utils.rs:269

  • pretty_name is crate-relative (readable_name strips the local crate prefix), so two crates in one workspace can expose the same harness name. Using it as harness_id makes the export ambiguous and also causes the later find calls keyed only by pretty_name to attach both crates' details to the first matching result. Use a globally unique identifier, such as a crate-qualified name, consistently across metadata, results, and detail arrays.
        "harness_id": result.harness.pretty_name,  // Reference to harness instead of duplicating name

kani-driver/src/harness_runner.rs:115

  • This rebuilds the fail-fast output from only the failing harness, but collect::<Result<Vec<_>>>() has already discarded every successful result produced before that failure. Even with --jobs 1, if the second harness fails, the JSON reports executed: 1 and omits the first harness although it ran. Preserve completed results when stopping so the structured summary reflects all executions.
                    let result = vec![HarnessResult {
                        harness: sorted_harnesses[failed.index_to_failing_harness],
                        result: failed.result,
                    }];

kani-driver/src/frontend/schema_utils.rs:48

  • A tool that rejects --version but prints an error or usage line to stdout is recorded as having that text as its version. This contradicts the documented null-on-undetermined behavior and can mislead JSON consumers; require a successful exit status before parsing stdout.
    let output = Command::new(binary).arg("--version").output().ok()?;

@feliperodri
feliperodri enabled auto-merge August 12, 2026 04:46
@feliperodri feliperodri added the [C] Feature / Enhancement A new feature request or enhancement to an existing feature. label Aug 12, 2026
@feliperodri
feliperodri added this pull request to the merge queue Aug 12, 2026
Merged via the queue into model-checking:main with commit 3bebcca Aug 12, 2026
33 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[C] Feature / Enhancement A new feature request or enhancement to an existing feature. Z-CompilerBenchCI Tag a PR to run benchmark CI Z-EndToEndBenchCI Tag a PR to run benchmark CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC: Output - Other tool versions

8 participants