build(voice): authentic voice in one docker compose up (Cycle 15)#17
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cycle 15 — authentic voice in one
docker compose upRunning the authentic TruVoice voice was secretly a host chore: the voice image only
COPY'd a host-prebuiltdist/, so a user had to install Node 20 + pnpm,pnpm install, and run a typecheck beforedocker 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-0027What changed
services/voice-server/Dockerfile→ multi-stage. A newnode:20-slim AS buildstage runscorepack enable+pnpm install --frozen-lockfile+pnpm --filter @vivify/voice-server run build; the Wine/SAPI4 runtime stage doesCOPY --from=build /repo/services/voice-server/dist. The final image keeps only the Node runtime — no pnpm/TS toolchain. RuntimeCOPYsources are prefixedservices/voice-server/(context is now the repo root).package.json— added"build": "tsc --build"(emit-only; builds the@vivify/typesproject reference first).docker-compose.yml—voicebuilds fromcontext: .+dockerfile: services/voice-server/Dockerfile(mirrorsmash).services/voice-server/Dockerfile.dockerignore(new) — lets the voice build read the gitignoredvendor/, while the root.dockerignorestill keepsvendor/out of the MASH image (BuildKit uses the per-Dockerfile ignore for this build).speech.hstays 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.Verification
docker build --target build -f services/voice-server/Dockerfile .compileddist/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 exposesservices/voice-server/vendor/(incl.sdk/include/speech.h) +bridge/,pulse-null.pa,entrypoint.shto the build, while bridge build artifacts stay excluded.pnpm -r typecheck && pnpm -r test && pnpm lint && pnpm format(246 tests; compose YAML prettier-clean; nosrc/test change).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 builddistfirst).services/voice-server/vendor/, thendocker compose build --no-cache && docker compose up→ both containers up, upload a.acs→ Speak → authentic Genie.✅ Final minimal steps (this is the win)
pnpm install→pnpm --filter @vivify/voice-server typecheck(build dist) → drop 3 files →docker compose up.services/voice-server/vendor/—spchapi.exe,tv_enua.exe,sdk/include/speech.h(sourcing indocs/legal-and-assets.md) →docker compose up. Docker is the only host tool.IP rule holds: no
.exe/.acs/speech.hcommitted;vendor/gitignored; MASH image still excludesvendor/. Ports (8090/8080), TTS cache + named volume unchanged.Does not merge.
🤖 Generated with Claude Code