- Voice layer manifest (src/voice/layerManifest.js) generates the layer enums,
aliases and analyst field hints; a test fails when a catalog layer has no entry.
- analyst_query validates fields, operators, units, values and scopes, counts up
to 250,000 loaded records per layer, reports complete/partial/unanswered, tells
LAYER_OFF / NOT_READY / FEED_UNAVAILABLE apart from zero, refuses mismatched
follow-ups and never lets a superseded query replace follow-up memory. Seven
more layers expose analyst records.
- Tool results carry a code-built say line and a display card; the voice card in
the mic control shows captions, plan steps and the result
(src/voice/resultDisplay.js is the one adapter). Slow tools narrate their
current step out of band; holding Space while the assistant talks cuts it off.
Turn spans go to the debug log; caption transcription is metered; the log
omits what was said unless GEV_VOICE_LOG_CONTENT=1.
- Point and ask: the Space-down pointer snapshot resolves "this", "that one" and
"around here" through 'pointer' targets, and "the second one" through
referent:n from the last numbered list.
- Prompt states each policy once (instructions + tools 48.5k -> 46.7k chars).
- Voice benchmark harness (scripts/qa-voice-bench.mjs, scripts/voice-bench/)
shares its phrase table and grading with scripts/qa-voice-routing.mjs.
Ported onto the split provider layout: the routes now live in
server/providers/openai/{hud-summary,realtime}.js and the camera health
entry in server/providers/cctv.js.
The routes #263 names — celestrak, terrain, adsbdb — were sanitized by
#289. The same defect had moved:
- /api/openai/hud-summary relayed `data.error.message` verbatim on a
non-ok upstream. That is OpenAI's own error text, carrying request ids,
organization hints and quota wording. Its catch echoed the JS error too.
- /api/realtime/token echoed the JS error when minting failed, which for a
network fault is a resolver message naming the upstream host.
- cctvProxy stored the raw error as a camera's health `message`. This is
the least obvious of the three and the reason the PR is worth its size:
GET /api/cctv/health serializes `message`, and src/layers/cctv/frames.js
renders it as the camera's status label, so the detail reached the
screen through a different door than the sanitized 502 beside it.
Logs name the failure and the upstream status without the text, matching
the Launch Library and terrain paths; these lines get pasted into issues.
Coverage extends the existing harnesses rather than adding files —
localServices.test.mjs drives both OpenAI routes over a mocked upstream,
mediaProviders.test.mjs drives a failing media fetch and then reads
/health. All three assertions fail on the previous implementation.
Refs #263. Does not touch /api/realtime/debug-log's identical echo; that
route is the subject of the #23 PR, so the two do not overlap a line.