Adds the P5a recall-precision predicate that was listed as NOT_COVERED in
the L10 continuity lane. Closes the gap in the schema-as-proof discipline
(CLAUDE.md): every predicate must have both a *_holds test (real soak) and
a *_bites mutation test so a passing lane cannot silently miss the violation
it nominally catches.
Changes:
- runner.py: ProbeRecord dataclass + probe_at/verify_probes_at params on
run_soak(). Registers a field state (float32 bytes, matching vault's
_exact_index dtype) at a named turn and recalls it against the vault at a
later turn — including after a reboot, which is the cross-reboot claim.
- predicates.py: evaluate_p5a_recall_precision — fails if any ProbeRecord
has rank=None or rank>top_k, or if no cross-reboot probe was recorded.
- report.py: wires P5a into build_report(); probe registered at turn 1,
verified at reboot_turn+2 (intentionally before the vault's
null_project auto-reproject cycle at store_count=20, which would destroy
all CGA inner-product scores — documented as a real finding, deferred to
a follow-up increment); NOT_COVERED is now empty ().
- contract.md: P5a row in the predicate table + reprojection-boundary
scope note.
- test_l10_continuity.py: 4 tests — holds (real 6-turn soak across reboot)
+ 3 bites (rank=None, no cross-reboot probe, empty probe_records).
Key finding: vault.null_project() fires every vault_reproject_interval=20
stores and produces CGA-orthogonal versors (inner product → 0.0 with the
original), completely destroying both exact-match and ranked recall. This
is a long-horizon vault stability issue, recorded here rather than silently
avoided. The P5a probe window is constrained to the pre-reproject interval
to keep wrong=0 intact while documenting the gap.