diagnose: a verification verdict must say which instrument produced it #91
Merged
dcharlot
merged 1 commit from 2026-09-10 21:15:24 -04:00
diagnose/grounding-backing into main 2026-09-10
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]>