* feat(routing): wire effective-device + routing_status into /engines (#21 PR 3/5)
Surfaces the PR-1 probe + resolver through the engine registries so the
matrix UI (PR 5) and the no-silent-fallback gates can consume it.
- `engine_routing.routing_fields()`: shared helper returning the three
serialization-ready keys, centralizing the scrub rule — routing_reason is
scrubbed via `core.scrub.scrub_text` only when truthy, so a None reason
stays JSON `null` (never coerced to "").
- TTS/ASR `list_backends()` each gain `effective_device` / `routing_status` /
`routing_reason`, computed from a SINGLE `detect_host_caps()` call per
request (host caps are constant per process). ASR is brought to full TTS
parity: it now also carries `install_hint` / `last_error` / `isolation_mode`
and a SCRUBBED `reason` (closing a pre-existing ASR token-leak gap) — an
identical 11-key shape across families. ASR also gains the same
is_available()-raises resilience TTS has (degrade to available:false, never
500).
- LLM `list_backends()` reaches 11-key parity too but emits literal
`effective_device:"network"` / `routing_status:"n/a"` / `routing_reason:null`
(NOT via resolve_routing — LLM runs no local GPU model). `LLMBackend.gpu_compat
= ()`. "network" is a label, not a probe — nothing here touches the network.
- `select_engine` host-routing gate: refuses a pick whose `routing_status` is
`unavailable` on this host (400 with an actionable detail), while ALLOWING
`cpu_fallback` (it runs, just slower). LLM is never gated. Defensive `.get`
so legacy payloads still select. New typed `SelectEngineResponse`.
Tests: 11-key shape across all 3 families, well-formed tts/asr routing keys
(+ None-not-"" contract), LLM network/n/a labels, select gate (block
unavailable / allow cpu_fallback / never-gate LLM). Updated the registry
exact-shape test for the 3 new keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(cjk): allowlist docs/specs/ in the hardcoded-CJK guard
PR #429 merged the longform design specs, which legitimately quote functional
CJK (test-fixture descriptions, CosyVoice speaker IDs, multilingual sample
text). The CJK guard scans every tracked file, so those docs turned main red.
Specs are documentation, not shipped UI strings — allowlist the docs/specs/
prefix, matching the individually-allowlisted docs already in the set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>