feat(frank-tts): deploy wiring for the 7772 voice endpoint, and voice naming written down
Stage 2 of the voice stack. Repo state only: nothing deployed, no host touched, no container started. - `frank-tts` is now registered everywhere a dankclaw service has to be: `SERVICE_ORDER` (after `frank-tts-engine`, before the consumers), the planner's dankclaw set, `deploy-remote.sh`'s allowlist + dispatch + a `deploy_frank_tts` handler, and `deploy.sh`'s dispatch regex — plus the `build_compose_image` arm naming `dankclaw/voiceservice/Containerfile`, without which the build silently returns 0 and the unit's `Pull=never` tag never exists. - The unit joins TWO networks, in order: `dankclaw_default` first (default route, where consumers and open-notebook live), then the voice bridge. `TTS_UPSTREAM` moves from `host.containers.internal:8880` to `http://frank-tts-engine:8880` — the actual fix for a shape that could never work, since a bridge container cannot reach a host-loopback bind and the engine publishes nothing at all. - `Wants=`/`After=` on the engine, never `Requires=`: the `llama-tts-cli` rollback backend needs no upstream, so a broken engine may delay this endpoint but must not take its unit down. - The handler installs the engine's quadlet pair too, because a referenced `Network=` file the generator cannot find means no façade unit is produced at all, and `--only frank-tts` has no other way to get it. It creates `/home/dank/voices` as the deploy user (rootless podman would auto-create a missing bind source root-owned, which reads as "no voices"), and gates on podman's recorded health. - Voice naming is now written down where it can be found: a voice is a lowercase directory name matching `^[a-z0-9][a-z0-9._-]*$`, at most 64 chars. That shape is the intersection of two rules that disagree — the shim would accept mixed case, the engine lowercases names — so a mixed-case pack resolves on one side only, and the boot warmer refuses it loudly. `narrator1/2/3` are load-bearing for open-notebook. - Homepage card for the pair, `wip: down` and hidden, probing `127.0.0.1:7772`; the engine gets no card because it publishes nothing and could never be probed. - **No Prometheus job, deliberately.** No `/metrics` exists on the shim, this Prometheus 3 refuses HTML/JSON bodies (the existing `comfyui` and `services` probes are documented down for exactly that), and `LlmEndpointDown` is scoped `up{job=~"frank-.*"}` — so a `/health` job would page on Signal for a healthy endpoint. Recorded in the page instead of quietly skipped. - Vault: new [[Frank TTS]] page linked from the index; [[Frank TTS Engine]] now points at it for the pair's public signal; log entry for the day. - Seam grows to 25 tests: the generator's rendering of the pair (both attachments, first-network order, one publish, Wants-not-Requires, bounded per-unit blocks), the env file's upstream checked as a network JOIN rather than a string, planner separation and order, the handler's steps, the card's gating, routing that selects both seams for either deployable.
This commit is contained in:
@@ -2490,3 +2490,49 @@ every restart since the cutover.
|
||||
nothing and exposes no metrics, so there is nothing to probe, which is said out
|
||||
loud in the page rather than papered over. No ICL/`ref_text` work: the packs
|
||||
keep `reference.spk`/`reference.rvq` so it stays a per-voice opt-in.
|
||||
|
||||
## 2026-10-18 — frank-tts: the 7772 endpoint gets its deploy wiring, and voice naming is written down
|
||||
|
||||
- Stage 2 of the voice stack: the **façade** in front of the engine is now a
|
||||
deployable service too. Nothing deployed, no host touched. Runbook:
|
||||
[[Frank TTS]].
|
||||
- **Two attached networks, in a meaningful order.** `dankclaw_default` first — it
|
||||
owns the default route and is where consumers live (open-notebook dials
|
||||
`http://frank-tts:7772/v1` by container name) — then the voice pair's private
|
||||
bridge. `scripts/frank-tts.env`'s `TTS_UPSTREAM` moves from
|
||||
`host.containers.internal:8880` to `http://frank-tts-engine:8880`, which is the
|
||||
actual fix for a shape that could never have worked: a bridge container cannot
|
||||
reach a host-loopback bind, and the engine publishes nothing.
|
||||
- **`Wants=` + `After=` on the engine, never `Requires=`:** the façade still
|
||||
carries the `llama-tts-cli` rollback backend, which needs no upstream, so an
|
||||
engine that will not boot must delay this endpoint, not take it down.
|
||||
- The façade handler installs the **engine's** quadlet pair as well. Not tidiness:
|
||||
with a referenced `Network=` file absent, the v1 Quadlet generator produces no
|
||||
container unit at all, and `--only frank-tts` has no other way to obtain it. It
|
||||
also `mkdir -p /home/dank/voices` as the deploy user, because rootless podman
|
||||
auto-creates a missing bind source **root-owned** and the endpoint then reports
|
||||
"no voices" instead of an error.
|
||||
- **A voice is a lowercase directory name.** `^[a-z0-9][a-z0-9._-]*$`, ≤ 64 chars,
|
||||
no slash/`..`/space. The shape is the intersection of two rules that do not
|
||||
agree: `app.py` would accept mixed case, `tts-server` lowercases names — so a
|
||||
pack named `Narrator1` resolves through the façade and not through the engine,
|
||||
and the boot warmer refuses such a directory loudly. `narrator1/2/3` are
|
||||
load-bearing for open-notebook, and `frank-tts-add-cloned-voice` is the skill
|
||||
that walks the pack → register → warm → prove-a-second-request flow.
|
||||
- Registered everywhere the open-notebook entry named as law (planner order puts
|
||||
the engine before the façade, so a warm registry precedes the first synthesis),
|
||||
plus the homepage card — `wip: down` and hidden, because nothing listens on
|
||||
7772 yet.
|
||||
- **No Prometheus job, on purpose.** The façade exports no `/metrics`, Prometheus 3
|
||||
refuses HTML/JSON content types (the `comfyui` and `services` probes are
|
||||
documented down for exactly that), and `LlmEndpointDown` is scoped
|
||||
`up{job=~"frank-.*"}` — so a `/health` scrape would page on Signal every few
|
||||
minutes for a healthy endpoint. That is the frank-archon never-green lesson
|
||||
pointed the other way, and it is written in the page instead of being quietly
|
||||
ignored.
|
||||
- Tests added to the voice seam: the generator's own rendering of the pair (both
|
||||
attachments, first-network order, one publish, Wants-not-Requires), the
|
||||
env-file upstream checked as a **network join** rather than a string
|
||||
comparison, planner separation and order, the façade handler's steps, the
|
||||
homepage card's gating, and routing that selects both seams for either
|
||||
deployable's paths.
|
||||
|
||||
Reference in New Issue
Block a user