feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) (#39)
CI / Deploy via GitOps (push) Successful in 7s
CI / Lint / Test / Vet (push) Successful in 33s
CI / Build & Import (push) Successful in 23s

Upload a recording (audio, or video with the camera off), get a private Swedish transcript, and chat about it.

- ADR-033: RLS-scoped private transcript store; ffprobe/ffmpeg intake; gateway transcription (iguana kb-whisper, berget fallback); in-process single-replica worker; chat over the transcript (local models only).
- ADR-034: optional two-engine mode (pianissimo + kb-whisper) reconciled by a LOCAL model; off unless TAPIR_RECONCILE_BASE_MODEL is set.
- ADR-035: real progress (phases, running/quiet/lost), phase logging, every judged spot stored, merged segments.
- Hardening from four adversarial reviews: errors logged and stored as kinds never text; per-engine deadlines; converted-length check; bounded queue; client hang-ups are not errors; strict local-alias check.
- Dockerfile: static ffmpeg/ffprobe 7.0.2 pinned by sha256 (#34). Built and inspected by hand; CI does not build the image on a PR, so its first CI build is this merge.

Uploads stay OFF until TAPIR_UPLOADS_ENABLED is set in the deployment.

Closes #33, #34, #42, #43, #44.

Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #39.
This commit is contained in:
2026-10-03 14:13:37 +00:00
committed by mathias
parent 4bafd5f082
commit 4a2067063d
104 changed files with 14214 additions and 311 deletions
+40
View File
@@ -16,12 +16,52 @@ COPY . .
# no libc. Trim symbols/DWARF to shrink the layer.
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /out/tapir ./cmd/tapir
# ── ffmpeg ──────────────────────────────────────────────────────────────────
# Audio upload probes files with ffprobe and converts them with ffmpeg (ADR-033,
# #34 option A). Static builds, so they run in the distroless image below.
#
# Pinned by version AND sha256: a changed upstream file fails the build instead of
# shipping. The checksum proves these are the bytes that were reviewed (2026-10-02;
# the publisher's md5 matched too). It does NOT prove authenticity: the publisher
# signs nothing we can check. amd64 only (the cluster is one amd64 node).
#
# These builds are GPLv3 (--enable-gpl --enable-version3). The service is private and
# the image is not distributed; the licence text is copied into the image anyway.
#
# The release URL moves on when upstream releases a newer version, so the pinned
# version is tried in releases/ and then old-releases/. If neither has it the build
# fails; vendoring the verified tarball is the durable fix.
FROM golang:1.26 AS ffmpeg
ARG FFMPEG_VERSION=7.0.2
ARG FFMPEG_SHA256=abda8d77ce8309141f83ab8edf0596834087c52467f6badf376a6a2a4c87cf67
# xz is not in the golang image (found by building it: `tar: xz: Cannot exec`).
RUN set -eu; \
apt-get update; \
apt-get install -y --no-install-recommends xz-utils; \
rm -rf /var/lib/apt/lists/*; \
mkdir -p /out; \
for dir in releases old-releases; do \
curl -fsSL -o /out/ffmpeg.tar.xz "https://johnvansickle.com/ffmpeg/${dir}/ffmpeg-${FFMPEG_VERSION}-amd64-static.tar.xz" && break; \
done; \
echo "${FFMPEG_SHA256} /out/ffmpeg.tar.xz" | sha256sum -c -; \
tar -xJf /out/ffmpeg.tar.xz -C /out --strip-components=1 --wildcards '*/ffmpeg' '*/ffprobe' '*/GPLv3.txt'; \
rm /out/ffmpeg.tar.xz
# ── runtime ─────────────────────────────────────────────────────────────────
# distroless static + nonroot: no shell, no package manager, runs as uid 65532.
# ca-certificates are bundled, which the OIDC/HTTPS clients need.
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/tapir /tapir
COPY --from=ffmpeg /out/ffmpeg /out/ffprobe /usr/local/bin/
COPY --from=ffmpeg /out/GPLv3.txt /usr/share/doc/ffmpeg/GPLv3.txt
# Where the service finds them. Uploads stay OFF until TAPIR_UPLOADS_ENABLED is set
# (a deployment decision, not an image one).
ENV TAPIR_FFMPEG_BIN=/usr/local/bin/ffmpeg \
TAPIR_FFPROBE_BIN=/usr/local/bin/ffprobe
EXPOSE 8080
USER nonroot:nonroot