jouleclaw: an answer carries its verifier's backing, so a cached answer is not refused as ungrounded #276
Merged
dcharlot
merged 1 commit from 2026-10-16 09:41:29 -04:00
fix/answer-carries-entailment-backing into main 2026-10-16
jouleclaw: an answer carries its verifier's backing, so a cached answer is not refused as ungrounded
All checks were successful
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m6s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 52s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 4s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m7s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m2s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 3m35s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 1m9s
An answer's `Provenance` stored the entailment results the verifier consulted, but not the backing the verifier declared for them (`VerificationReport::grounding_backing`). So jouleclaw-edge-cli's cache rebuild could only declare the floor, `Backing::Unstated`, and `require_substantive_grounding` refused every cached answer, including those a single real NLI model had grounded. The rebuild's own comment named the fix: persist the backing beside the entailments. - `Provenance.entailment_backing: Option<Backing>`. It is omitted from the wire when absent, so answers and caches written before it keep their bytes and deserialize as before; absent reads as unstated. - `compose_verified_answer` carries the report's backing into the answer. - The cache rebuild restores that backing and never derives one from the cached `model_id`s. `require_substantive_grounding` still cross-checks it against the cached results: a fixture stays a fixture, and a backing that names a model other than the one whose results are attached is refused. Tests: - `the_answer_carries_the_verifiers_backing`: compose carries any backing, the floor included, unchanged. - `a_cached_answer_keeps_the_backing_its_verifier_declared`: a single model's labels are grounded after the round trip. - `a_cached_backing_is_never_promoted_or_trusted_past_its_results`: fixture stays refused, a mismatched model is refused, and an old entry is unstated. - The answer round trip: the field is absent from the wire when None and round-trips when Some. `AnswerOrRefusal` gains `allow(clippy::large_enum_variant)` with its reason. There is one per query, and boxing would change a public enum for no measurable gain. Co-Authored-By: Claude Opus 5.5 <[email protected]>