diagnose: a verification verdict must say which instrument produced it #91

Merged
dcharlot merged 1 commit from diagnose/grounding-backing into main 2026-09-10 21:15:24 -04:00

2026-09-10

David Charlot
diagnose: a verification verdict must say which instrument produced it
All checks were successful
standards workspaces build + test / aac-rs (pull_request) Successful in 57s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 45s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m20s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 35s
standards workspaces build + test / map-rs (pull_request) Successful in 30s
standards workspaces build + test / mesh-rs (pull_request) Successful in 30s
standards workspaces build + test / openpay (pull_request) Successful in 1m18s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 57s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m46s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m13s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m18s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m1s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m33s
standards workspaces build + test / arl-rs (pull_request) Successful in 10m10s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 10m12s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 10m7s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 10m28s
standards workspaces build + test / sense-rs (pull_request) Successful in 9m48s
The grounding labels under a `VerificationReport` come from a learned model — a
DeBERTa-v3 NLI head, per §6.4. That is the right instrument for the job: whether
prose entails a claim is not decidable, and a calibrated judgement is the honest
tool. The problem was never the model. It was that nothing downstream had to
notice there was one.

The provenance was already carried: `EntailmentResult` records `model_id`,
calibrated `label_probabilities` and `joules_spent`, `Provenance.entailments`
preserves the lot into the sealed artifact, and `FixtureEntailer` already names
itself. What was missing is the other half — the family's discipline call.
`jcp-action-receipt` has `check_discipline`, `proof-cascade` has
`require_substantive_proof`, `sandbox-attest` has `require_mechanism_evidence`;
the entailment path had no `require_*` at all. Carried provenance that nothing
can be made to check is a record, not a control.

So: `Backing` on the report, and `require_substantive_grounding()` to enforce it.
`Unstated`, `Unconsulted` and `Fixture` are refused; `SingleModel`, `Concurring`
and `Contested` pass. `require_grounding_from` additionally pins a model, for a
caller that has decided which instrument it trusts rather than only that one ran.

Three decisions carry the weight:

**The default is the floor.** `Entailer::backing()` defaults to `Unstated`, which
is refused — so an implementation that forgets under-claims instead of
over-claiming, the same discipline as `Vmm::attained_tier` returning S0.

**A declaration cannot outrun its evidence.** The check cross-references the
declared backing against the `model_id`s actually present in
`entailments_consulted`. A report claiming two concurring models while carrying
one model's results is refused: asserting corroboration a reader cannot obtain
from the artifact is worse than asserting nothing.

**Cache rehydration declares the floor, not a reconstructed backing.** The cached
entailments carry `model_id`s, so a backing *could* be derived from them — and
that is precisely the laundering path: a run backed by `FixtureEntailer` records
model_id "fixture-entailer", and reconstructing `SingleModel` from it would
promote a stand-in into a substantive backing on the way out of the cache.
Evidence that a model ran is not a claim about what its labels are worth. The
consequence is stated rather than hidden — a rehydrated report is refused, and
the fix is persisting the backing in `Provenance`, a cache-format change this
commit deliberately does not make.

`grounding_backing` is `Option`, `skip_serializing_if = "Option::is_none"`, and a
test asserts it never reaches the wire when unset, so reports and vectors written
before it existed serialize byte-identically and keep verifying.

The sharpest test is `a_clean_verdict_from_a_fixture_is_still_not_grounded`: an
existing fixture-backed report with no violations, verdict
`ProceedToComposition` and invariant I2 verified — every previously-checkable
signal saying "verified", and all of them true — is now refused as grounded. The
verdict is about the labels; the backing is about whether the labels are worth
anything.

Verified: 475 test binaries green across the workspace, 9 new. All 7 dependents
of `jouleclaw-schema` are in-workspace, checked rather than assumed, so
`--workspace` genuinely covers the blast radius of the schema change.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 12:18:04 -04:00