Wires the F2.2 ``toin_read_only`` field through the only consumer where
it's load-bearing (TOIN write surface) and extends the proxy's
structured ``policy_selected`` log event with all three F2.2 fields so
the bake dashboard has per-mode observability.
Wiring (gates only TOIN writes — compression itself still runs):
- headroom/transforms/smart_crusher.py: capture
kwargs["compression_policy"] onto self._runtime_compression_policy
at the start of apply(). _record_to_toin returns early when the
policy says toin_read_only=True. Direct crush() / crush_array_json()
callers don't go through apply() and keep pre-F2.2 write-enabled
behaviour (no auth context for non-proxy callers).
- headroom/transforms/content_router.py: same one-liner in apply(),
same gate in _record_to_toin. Mirrors the existing
_runtime_target_ratio / _runtime_kompress_model pattern.
Telemetry:
- crates/headroom-proxy/src/proxy.rs: extend the policy_selected
structured log with volatile_token_threshold, max_lossy_ratio, and
toin_read_only. F2.2 bake telemetry can now observe all five fields
on every request — load-bearing for the F2.2-followup tune decision
since volatile_token_threshold and max_lossy_ratio are plumbed-but-
unconsumed today and the log is the only signal that the values are
flowing correctly.
Plumbed-but-unconsumed (deliberate; flagged in PR body):
- volatile_token_threshold — the volatile detector in cache_aligner.py
is shape-based, not token-count-based; wiring it forces a detector
refactor outside F2.2 scope.
- max_lossy_ratio — distinct from the caller-driven target_ratio kwarg
in content_router.py; gating lossy paths on a policy cap is F2.2-
followup once telemetry decides whether to gate or just observe.
Tests (tests/test_compression_policy_toin_gate.py):
- 7 tests covering the gate. SmartCrusher tests skip when the
headroom._core Rust wheel isn't installed (matches the existing
test_smart_crusher_rust_parity.py pattern); the 3 ContentRouter
tests exercise the gate without the Rust dependency. CI's
ci-precheck-python target runs scripts/build_rust_extension.sh
before pytest so all 7 will run in the gate.
Refs: F2.1 (#400)