Skip to content

build(voice): authentic voice in one docker compose up (Cycle 15)#17

Merged
kbennett2000 merged 1 commit into
mainfrom
cycle-15-voice-one-command
Jun 22, 2026
Merged

build(voice): authentic voice in one docker compose up (Cycle 15)#17
kbennett2000 merged 1 commit into
mainfrom
cycle-15-voice-one-command

Conversation

@kbennett2000

Copy link
Copy Markdown
Owner

Cycle 15 — authentic voice in one docker compose up

Running the authentic TruVoice voice was secretly a host chore: the voice image only COPY'd a host-prebuilt dist/, so a user had to install Node 20 + pnpm, pnpm install, and run a typecheck before docker compose up. This cycle moves that build into the image — now Docker is the only host tool.

Spec: docs/cycles/cycle-15-voice-one-command.md · decision: ADR-0027

What changed

  • services/voice-server/Dockerfile → multi-stage. A new node:20-slim AS build stage runs corepack enable + pnpm install --frozen-lockfile + pnpm --filter @vivify/voice-server run build; the Wine/SAPI4 runtime stage does COPY --from=build /repo/services/voice-server/dist. The final image keeps only the Node runtime — no pnpm/TS toolchain. Runtime COPY sources are prefixed services/voice-server/ (context is now the repo root).
  • package.json — added "build": "tsc --build" (emit-only; builds the @vivify/types project reference first).
  • docker-compose.ymlvoice builds from context: . + dockerfile: services/voice-server/Dockerfile (mirrors mash).
  • services/voice-server/Dockerfile.dockerignore (new) — lets the voice build read the gitignored vendor/, while the root .dockerignore still keeps vendor/ out of the MASH image (BuildKit uses the per-Dockerfile ignore for this build).
  • speech.h stays user-supplied — it's Microsoft © 1994-1998 "All rights reserved" with no redistribution grant, so auto-fetching it would violate the zero-bundled-IP rule (ADR-0006). The build fails loudly with the exact drop path if missing.
  • Docs — README one-command flow, cycle-15 doc, ADR-0027.

Verification

  • In-sandbox (real Docker, verified — not assumed): docker build --target build -f services/voice-server/Dockerfile . compiled dist/ inside the image (pnpm install + tsc --build/repo/services/voice-server/dist/main.js), proving the host needs no toolchain. Running that stage confirmed the per-Dockerfile ignore exposes services/voice-server/vendor/ (incl. sdk/include/speech.h) + bridge/, pulse-null.pa, entrypoint.sh to the build, while bridge build artifacts stay excluded.
  • CI green: pnpm -r typecheck && pnpm -r test && pnpm lint && pnpm format (246 tests; compose YAML prettier-clean; no src/test change).
  • Reviewed by code-reviewer: approved, no blockers — re-verified multi-stage correctness, no toolchain in the runtime image, all COPY paths prefixed, IP hygiene (root ignore unchanged, nothing proprietary committed), ports/cache/volume intact. One nit applied (the README "local dev" note now says to build dist first).
  • Operator (the acceptance — full Wine path isn't reproducible in the sandbox): from a clean checkout, drop the 3 files into services/voice-server/vendor/, then docker compose build --no-cache && docker compose up → both containers up, upload a .acs → Speak → authentic Genie.

✅ Final minimal steps (this is the win)

  • Was: install Node + pnpm → pnpm installpnpm --filter @vivify/voice-server typecheck (build dist) → drop 3 files → docker compose up.
  • Now: drop 3 user-supplied files into services/voice-server/vendor/spchapi.exe, tv_enua.exe, sdk/include/speech.h (sourcing in docs/legal-and-assets.md) → docker compose up. Docker is the only host tool.

IP rule holds: no .exe/.acs/speech.h committed; vendor/ gitignored; MASH image still excludes vendor/. Ports (8090/8080), TTS cache + named volume unchanged.

Does not merge.

🤖 Generated with Claude Code

…r compose up` (Cycle 15)

Running the authentic TruVoice voice secretly required a host toolchain: the voice
image only COPY'd a host-prebuilt dist/, so users had to install Node 20 + pnpm,
`pnpm install`, and run a typecheck before `docker compose up`. Now the image builds
its own dist/ — the only host tool is Docker.

- services/voice-server/Dockerfile: multi-stage. New `node:20-slim AS build` stage runs
  corepack + `pnpm install --frozen-lockfile` + `pnpm --filter @vivify/voice-server run
  build` (tsc --build); the Wine/SAPI4 runtime stage does `COPY --from=build`. Final image
  keeps only the Node runtime (no pnpm/TS toolchain). Runtime COPY sources prefixed
  `services/voice-server/` (context is now the repo root). speech.h fail message clarified.
- services/voice-server/package.json: add `"build": "tsc --build"` (emit-only; builds the
  @vivify/types project ref first).
- docker-compose.yml: voice service builds from `context: .` +
  `dockerfile: services/voice-server/Dockerfile` (like mash); header comment updated.
- services/voice-server/Dockerfile.dockerignore (new): lets the voice build read the
  gitignored vendor/ while the ROOT .dockerignore still keeps vendor out of the MASH image.
- speech.h stays USER-SUPPLIED (Microsoft © "All rights reserved", no redistribution grant —
  not auto-fetched; ADR-0027 / ADR-0006). Build fails loudly with the exact drop path.
- Docs: README one-command flow (3 user-supplied files), cycle-15 doc, ADR-0027.

Verified in-sandbox: `docker build --target build` compiled dist/ in-image (no host
toolchain), and the per-Dockerfile ignore correctly exposes vendor/ (incl. sdk/include/
speech.h) to the voice build while bridge artifacts stay excluded. CI green. The full
Debian+Wine+SAPI4 image + end-to-end voice remain operator-validated (no Wine in sandbox).

IP rule holds: no .exe/.acs/speech.h committed; vendor/ gitignored; MASH image still
excludes vendor. Ports (8090/8080), TTS cache + volume unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kbennett2000 kbennett2000 merged commit 427a4ca into main Jun 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant