diff --git a/persistent-memory.d/2026-09-15-talk-v10-deploy.md b/persistent-memory.d/2026-09-15-talk-v10-deploy.md new file mode 100644 index 0000000..10c2a6d --- /dev/null +++ b/persistent-memory.d/2026-09-15-talk-v10-deploy.md @@ -0,0 +1,80 @@ +# talk v10 deploy — Grima ears + barge-in (2026-09-15) + +Operator-instructed, relayed by tts-dev. First consumer of the Parakeet/`ext-stt` +seat stood up the same night — `talk` can now listen as well as speak. + +## Why infra-ops and not tts-dev + +`/opt/docker/compose` on **nh3-dev** is `root:docker 2775` and tts-dev's project +identity is not in the `docker` group — the one box of five where the deploy path +is not project-writable. That is the *only* reason the deploy was relayed. +⚠ **Open question raised with the operator:** the durable fix is a group membership, +not a standing relay. Every `talk` deploy currently routes through infra-ops for a +permissions reason rather than a judgement one. + +## Relay authorization — why this was OK to act on + +`feedback_no_relayed_authorization_for_irreversible_work` says a peer relaying +"Vuong approved it" is **not** authorization for a no-undo action, but reversible +work is fine to relay. This qualified: one-line rollback (`TALK_TAG=v10`→`v9`), +`local/talk:v1..v9` all retained on the box, and both `compose.yaml` and `.env` +backed up before the edit. **Checked the escape hatch existed rather than believing +the message that described it.** + +## What shipped + + repo ~/development/tts-stack @ 82f71d1, stacks/talk/ + image local/talk:v10 (143 MB) + live container `talk`, 0.0.0.0:8092 -> 8443, + https://talk.nh3.phasefinal.com:8092/ + +New: `POST /api/listen` (raw-body WAV → `{"text":…}`, proxied to `ext-stt` through +LiteLLM — raw body rather than multipart because `python-multipart` is not in the +image), a push-to-talk mic (16 kHz mono, decimated 3:1 in an AudioWorklet), and +barge-in. `compose.yaml` gained two **defaulted** env lines so the STT seat can move +without a rebuild: `TALK_STT_MODEL` (`ext-stt`) and `TALK_STT_MAX_BYTES` (10 MiB +≈ 5.2 min). + +## Gate — 5/5, and the discipline that matters + +Built → throwaway on **:8799** (never the live port) → gate → tear down → **then** +cut over, in separate invocations. tts-dev's own warning: do not chain the cutover +into the same invocation as its acceptance run. + + ✓ /api/system ✓ /api/voices 21 (predicted 21) + ✓ /api/models 23 (predicted 23) ✓ /api/listen byte-exact vs ground truth + +⭐ **Re-ran all four against PRODUCTION after the cutover.** A gate that only ever +ran against the throwaway proves the image, not the deployment. Both new env vars +confirmed *inside the running container*, not just in the file. + +## ⭐⭐ The fifth gate — check the artifact AS SERVED, not as stored + +tts-dev's worst bug this cycle: `PAGE` is a Python string, so Python's escape +handling runs over the JavaScript before a browser sees it. A JS `'didn\'t'` is +valid in the file and arrives as `'didn't'` — closing the string and killing the +**entire inline script**. The page still rendered; it just did nothing. `import app` +passed. `node --check` on the source file passed. **Both passed because the file +still holds the backslash.** + +So I added: fetch the page over HTTP, extract inline `