Repo state only: nothing deployed, no host touched, no container started. - `frank-tts-engine` on Dankclaw: `qwentts.cpp` commit 5916bf7c79dbb4e66fc059cdc507a79a23a28bb4, resident `tts-server`, 1.7 B base talker Q4_K_M + Q8_0 tokenizer, container port 8880. It is deliberately NOT a fleet endpoint — `frank-tts` on 7772 is the endpoint consumers address. - Publishes NOTHING. A host-loopback publish would have been useless to the façade (netavark DNATs published ports only, so a bridge container cannot reach a host loopback bind — the shape that 502'd hermes) and would have been the one LAN-reachable surface of an unauthenticated synthesiser. Both voice containers sit on one committed quadlet network, `frank-tts-engine.network` pinned to 10.89.5.0/24 (next free /24 after open-notebook's 10.89.4.0/24), and peers address the engine by container name. Diagnostics and the deployer's health gate run inside the netns (`podman exec`, `wait_container_healthy`). - Cold-voice law honoured in the unit: the first request for a registered voice decodes its whole reference to snapshot codec state (6.2-8.0 s, first audio byte 5 530 ms), later ones restore in 0.5 ms, and the registry is memory-only. `ExecStartPost=-/usr/bin/podman exec frank-tts-engine /app/warm-voices.sh` with `TimeoutStartSec=900`; the `-` prefix keeps a failed warm from crash-looping the endpoint under `Restart=always`. It logs cold=/warm= per voice, so the under-a-second second request is proven on every boot. - Thread law recorded: `tts-server` has no `--threads`; src/backend.h uses hardware_concurrency()/2, so a cpuset pin silently changes the thread count. - Image builds from the pinned commit with the proven line (GGML_BLAS=OFF forced by this host, every GPU backend off, -j 8, no -DGGML_NATIVE because ggml defaults to -march=native for the building host). Two stages; the runtime stage COPYs both the entrypoint and the warmer, because a container that publishes nothing is reached with `podman exec`, not a port. - `scripts/download-frank-tts-engine-qwentts.sh` fetches the two GGUFs with byte and LFS-oid checks (talker 1 219 245 248 B, tokenizer 291 150 624 B); the deployer gates on --verify-only instead of pulling 1.4 GiB mid-deploy. - Registered in SERVICE_ORDER, DANKCLAW_SERVICES, the deploy-remote.sh dispatch, its pre-deploy requirement, deploy.sh's regex, the planner's path map (the whole `dankclaw/voiceservice/` tree mapped to nothing before this), and test-changed.sh ABOVE the `dankclaw/*.container` case whose `*` crosses `/`. - New seam tests/frank_tts_engine.bats (19): the Quadlet generator's own output for network/publish/boot/warm shape, entrypoint argv driven from the tracked env file, the warmer against a stub tts-server over a real socket (registration keys, ref_text absent, late engine, hung synthesis, name refusal), the fetcher's verifier, the pinned cmake line, and both routing tables. Suite: 422 passed. - Vault: [[Frank TTS Engine]] linked from the index; log.md entry for the day.
346 lines
14 KiB
Bash
Executable File
346 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Run only the Bats tests affected by the staged paths.
|
|
#
|
|
# Usage:
|
|
# scripts/test-changed.sh # inspect the staged commit
|
|
# scripts/test-changed.sh --list # print selected test files
|
|
# scripts/test-changed.sh --paths FILE ...
|
|
#
|
|
# --paths is intended for tests and local experimentation. The normal mode is
|
|
# deliberately staged-only so a pre-commit hook cannot accidentally validate
|
|
# an unrelated dirty working-tree file.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
LIST_ONLY=0
|
|
USE_PATHS=0
|
|
declare -a CHANGED=()
|
|
|
|
die() {
|
|
printf 'ERROR: %s\n' "$*" >&2
|
|
exit 2
|
|
}
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--list)
|
|
LIST_ONLY=1
|
|
shift
|
|
;;
|
|
--paths)
|
|
USE_PATHS=1
|
|
shift
|
|
while [ "$#" -gt 0 ] && [[ "$1" != --* ]]; do
|
|
CHANGED+=("$1")
|
|
shift
|
|
done
|
|
;;
|
|
--)
|
|
shift
|
|
CHANGED+=("$@")
|
|
break
|
|
;;
|
|
*)
|
|
die "unknown option '$1' (use --list or --paths FILE ...)"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
cd "$REPO_ROOT"
|
|
if [ "$USE_PATHS" -eq 0 ]; then
|
|
while IFS= read -r -d '' path; do
|
|
CHANGED+=("$path")
|
|
done < <(git diff --cached --name-only -z --diff-filter=ACMRTUXB)
|
|
fi
|
|
|
|
[ "${#CHANGED[@]}" -gt 0 ] || {
|
|
printf 'No staged paths; no focused tests to run.\n'
|
|
exit 0
|
|
}
|
|
|
|
declare -a TESTS=()
|
|
add_test() {
|
|
local test="$1"
|
|
local existing
|
|
for existing in "${TESTS[@]:-}"; do
|
|
[ "$existing" != "$test" ] || return 0
|
|
done
|
|
TESTS+=("$test")
|
|
}
|
|
|
|
for path in "${CHANGED[@]}"; do
|
|
case "$path" in
|
|
tests/*.bats)
|
|
add_test "$path"
|
|
;;
|
|
tests/helpers.bash|tests/run.sh)
|
|
die "$path is shared test infrastructure; run ./tests/run.sh explicitly"
|
|
;;
|
|
.githooks/*)
|
|
add_test tests/changed_tests.bats
|
|
;;
|
|
dankserver/compose.yaml|dankclaw/compose.yaml)
|
|
add_test tests/compose_validity.bats
|
|
add_test tests/env_consistency.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
dankserver/Caddyfile|dankclaw/Caddyfile)
|
|
add_test tests/caddyfile.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
tests/e2e/*)
|
|
add_test tests/e2e_scaffold.bats
|
|
;;
|
|
dankserver/.env.example|dankclaw/.env.example)
|
|
add_test tests/env_consistency.bats
|
|
;;
|
|
shared/homepage/*)
|
|
add_test tests/homepage_caddy.bats
|
|
add_test tests/homepage_config.bats
|
|
add_test tests/homepage_deploy.bats
|
|
add_test tests/homepage_pages.bats
|
|
add_test tests/homepage_checker.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
shared/searxng/*)
|
|
add_test tests/searxng_limiter.bats
|
|
add_test tests/searxng_quadlet.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# The five observability containers left compose for Quadlet units on
|
|
# 2026-09-11. Their seam pins boot persistence and the loopback-only
|
|
# publish posture that keeps them off every host interface. This case
|
|
# precedes the dankclaw/*/quadlet/*.container fallback below, which
|
|
# would otherwise swallow it into unrelated seams.
|
|
dankclaw/observability/*)
|
|
add_test tests/observability_quadlet.bats
|
|
add_test tests/caddyfile.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/docs_completeness.bats
|
|
;;
|
|
# Caddy left compose.yaml on 2026-08-26 for a quadlet; caddyfile.bats
|
|
# now asserts against the quadlet itself. Route every change under
|
|
# dankclaw/caddy/ to both seams (this block must precede the
|
|
# dankclaw/*.container glob, which would otherwise swallow the unit).
|
|
dankclaw/caddy/*)
|
|
add_test tests/caddy_quadlet.bats
|
|
add_test tests/caddyfile.bats
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# open-notebook is the fleet's podcast generator: an app quadlet, the
|
|
# SurrealDB quadlet behind it, the private network between them, and the
|
|
# one env file both units read. Like the caddy block above, this case
|
|
# has to precede the dankclaw/*.container glob, whose `*` crosses `/`
|
|
# and would otherwise swallow dankclaw/open-notebook/quadlet/*.
|
|
dankclaw/open-notebook/*|scripts/open-notebook.env)
|
|
add_test tests/open_notebook.bats
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# The voice stack: the arm A engine unit, the private bridge it owns,
|
|
# its image and its entrypoint. Like the open-notebook block above,
|
|
# this case MUST precede the dankclaw/*.container glob — that `*`
|
|
# crosses `/`, so without this case
|
|
# dankclaw/voiceservice/quadlet/frank-tts-engine.container is routed to
|
|
# the open-webui/pihole/searxng seams and its own seam never runs.
|
|
# The façade's seam rides along: the two share a directory, a network
|
|
# and the request path a consumer takes.
|
|
dankclaw/voiceservice/*)
|
|
add_test tests/frank_tts_engine.bats
|
|
add_test tests/frank_tts_unit.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/docs_completeness.bats
|
|
;;
|
|
dankclaw/*.container|dankclaw/*.network)
|
|
add_test tests/open_webui_quadlet.bats
|
|
add_test tests/pihole.bats
|
|
add_test tests/searxng_quadlet.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# Anubis is a quadlet + Caddyfile pair: the chain (public listener ->
|
|
# anubis:8923 -> internal :8016 listener) spans both, so any change
|
|
# under the service dir must re-run the caddyfile seam too. This case
|
|
# precedes the dankclaw/*/quadlet/*.container fallback below, which
|
|
# would otherwise swallow it into unrelated seams.
|
|
dankclaw/anubis/*)
|
|
add_test tests/anubis.bats
|
|
add_test tests/caddyfile.bats
|
|
add_test tests/homepage_caddy.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# frank-dragoon is a rootless quadlet again since 2026-09-16 (the
|
|
# halogen-flash-server restore). Its unit carries the fleet's GPU and
|
|
# podman posture, so it gets its own seam rather than the generic
|
|
# fallback below.
|
|
dankclaw/frank-dragoon/*)
|
|
add_test tests/frank_dragoon_unit.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/docs_completeness.bats
|
|
;;
|
|
# Quadlets live under dankclaw/<svc>/quadlet/ (reorganized out of the
|
|
# top-level dankclaw/*.container globs above). Map the whole subtree so
|
|
# changes to the pihole, open-webui, searxng, and gitea quadlets hit
|
|
# their seam tests. The pihole-specific block must come first: shell
|
|
# case matches the first pattern, and dankclaw/pihole/quadlet/*.container
|
|
# also satisfies the dankclaw/*/quadlet/*.container fallback.
|
|
dankclaw/pihole/*)
|
|
add_test tests/pihole.bats
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
dankclaw/*/quadlet/*.container|dankclaw/*/quadlet/*.network)
|
|
add_test tests/open_webui_quadlet.bats
|
|
add_test tests/pihole.bats
|
|
add_test tests/searxng_quadlet.bats
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
scripts/deploy.sh|scripts/deploy-remote.sh|scripts/deploy-plan.py|scripts/deploy-*-quadlet.sh)
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
scripts/mount-entropy.sh)
|
|
add_test tests/mount_entropy.bats
|
|
;;
|
|
# Archive material: a path under any .AGENTTRASH/ directory is
|
|
# retired-by-policy (deploy-plan.py plans nothing from it), so it
|
|
# selects no focused test. It still needs its own case: without one
|
|
# the wrapper refuses retirement commits, whose trash moves show up
|
|
# as additions, with "no focused-test mapping".
|
|
.AGENTTRASH/*|*/.AGENTTRASH/*)
|
|
;;
|
|
# The two voice env files both reach a container through a Quadlet unit
|
|
# (EnvironmentFile=), so they select the voice seams plus the
|
|
# script-syntax seam. This case has to precede the generic
|
|
# scripts/*.service and scripts/* fallbacks below, or a change to
|
|
# scripts/frank-tts-engine.env would run nothing but bash -n. The
|
|
# warmer is NOT here: it ships inside the engine image and is routed
|
|
# with the rest of dankclaw/voiceservice/* above.
|
|
scripts/frank-tts-engine.env|scripts/frank-tts.env)
|
|
add_test tests/frank_tts_engine.bats
|
|
add_test tests/frank_tts_unit.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
# frank-archon is llama.cpp (the unsloth b11030-mix prebuilt) on the
|
|
# two R9700s as a ROOT unit — the 2026-09-26 swap that replaced the
|
|
# Lucebox DS4.1 quadlet on :7770. Its env, unit and serve script all
|
|
# live in scripts/, which is why this case has to precede the generic
|
|
# scripts/*.service and scripts/* fallbacks below.
|
|
scripts/frank-archon.*|scripts/serve-frank-archon.sh)
|
|
add_test tests/frank_archon_unit.bats
|
|
add_test tests/llm_units.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
# frank-dragoon's chat template lives in scripts/ (moved there on
|
|
# 2026-09-26 when its old home was retired): a template edit changes
|
|
# what the live endpoint renders, so it selects the large unit seam.
|
|
scripts/qwen-sharp-v22.4.0.jinja)
|
|
add_test tests/frank_dragoon_unit.bats
|
|
;;
|
|
# frank-zealot is llama.cpp on the 3090 as a ROOT unit: a native CUDA
|
|
# binary, so no generated quadlet is involved. Its env, unit and serve
|
|
# script all live in scripts/, which is why this case has to precede
|
|
# the generic scripts/*.service and scripts/* fallbacks below.
|
|
scripts/frank-zealot.*|scripts/serve-frank-zealot.sh)
|
|
add_test tests/frank_zealot_unit.bats
|
|
add_test tests/llm_units.bats
|
|
add_test tests/deployment_scoping.bats
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
scripts/*.service)
|
|
add_test tests/llm_units.bats
|
|
add_test tests/scripts_syntax.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
scripts/*)
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
serverAppConfigs-docs/*)
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
dankclaw/hermes/*)
|
|
add_test tests/docs_completeness.bats
|
|
;;
|
|
README.md|AGENTS.md)
|
|
# Project guidance changes can affect the documented deployment
|
|
# contract; validate the docs vault and scoped deployment seams.
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
dankclaw/comfyui/*)
|
|
add_test tests/docs_completeness.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# loki and alloy images are built from this repo's Containerfiles; the
|
|
# loki one carries the healthcheck command (see its comment), so both
|
|
# seams read it.
|
|
dankclaw/loki/*|dankclaw/alloy/*)
|
|
add_test tests/observability_quadlet.bats
|
|
add_test tests/caddyfile.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
dankclaw/udev/*)
|
|
# Host-level udev rules are applied by manual sudo install + udevadm
|
|
# trigger, not dispatched as a service; nothing here is bats-testable.
|
|
;;
|
|
.AGENTTRASH/*)
|
|
# Trash moves are not testable; skip.
|
|
;;
|
|
.gitignore|.env.example)
|
|
# Ignore/ignore-list changes change nothing at runtime; not testable.
|
|
;;
|
|
old/scripts/*|old/scripts/**)
|
|
add_test tests/scripts_syntax.bats
|
|
;;
|
|
# Cutover-era dankserver quadlet units (romm trio, jellyfin, calibre-web,
|
|
# copyparty, plex, tautulli): the seam checks boot-persistence +
|
|
# v1-generator contract; deploy-flow seams guard the deployer routing.
|
|
dankserver/romm/*|dankserver/jellyfin/*|dankserver/calibre-web/*|dankserver/copyparty/*|dankserver/plex/*|dankserver/tautulli/*)
|
|
add_test tests/dankserver_quadlet.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# FrankBook-Pro units live inside the podman machine VM; the seam
|
|
# pins boot persistence, the gluetun netns invariant, and the
|
|
# plan-locked volume layout. Deployer changes route here too.
|
|
frankbook-pro/*)
|
|
add_test tests/frankbook_quadlet.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
# Seerr unit on dankclaw: boot persistence, loopback-only port
|
|
# posture, and the deployer routing that installs it.
|
|
dankclaw/seerr/*)
|
|
add_test tests/seerr_quadlet.bats
|
|
add_test tests/deployment_flow.bats
|
|
add_test tests/deployment_scoping.bats
|
|
;;
|
|
report/*)
|
|
# Frozen audit reports are point-in-time artifacts; not testable.
|
|
;;
|
|
*)
|
|
die "no focused-test mapping for '$path'; add one before committing"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Mapped-but-untestable paths (udev rules, trash moves) legitimately produce an
|
|
# empty selection; that is a pass, not a bats usage error.
|
|
[ "${#TESTS[@]}" -gt 0 ] || exit 0
|
|
|
|
if [ "$LIST_ONLY" -eq 1 ]; then
|
|
printf '%s\n' "${TESTS[@]}"
|
|
exit 0
|
|
fi
|
|
|
|
command -v bats >/dev/null 2>&1 || die 'bats is not installed'
|
|
printf 'Running focused tests for %d changed path(s):\n' "${#CHANGED[@]}"
|
|
printf ' %s\n' "${TESTS[@]}"
|
|
bats --print-output-on-failure "${TESTS[@]}"
|