Files
serverAppConfigs/tests/frank_archon_unit.bats
T
Frank cb25796a6b refact(frank-archon): serve gates run on the shared library; the serve seam is behavioural
The image/.rad/template gates are require_image/require_min_mib/require_file,
and the gates an operator can only reach on the host (GPU nodes, the ttm pages
cap, the run line) are now drivable: FRANK_ARCHON_DEV_KFD, FRANK_ARCHON_DEV_DRI,
FRANK_ARCHON_TTM_SYSFS and FRANK_ARCHON_LOG default to the production paths.
Seven new seam tests EXECUTE the serve script against fixtures (stub podman,
stub nodes, stub sysfs, temp payload) and assert exit status, the refusal
words and the engine argv. The old text-grep gate test is deleted: it pinned
the words of the source, not the behaviour.
2026-10-13 21:44:38 -07:00

455 lines
23 KiB
Bash
Executable File

#!/usr/bin/env bats
# Seam: frank-archon — the Dankserver LLM endpoint on :7770.
#
# This file asserts the RADIANCE shape (StillDeadcode engine, digest-pinned
# stilldeadcode/radiance image, BOTH R9700s TP=2 on the wht6 wire, serving
# the Swift1.5 Qwen3.8-Flash-Next GPTQ-w4 .rad built by
# scripts/convert-swift15-w4dn8.sh). It replaces the FreeToken seam of
# 2026-10-05 (git tag frank-archon-freetoken-pr587-20261005) and pins what
# flags alone do not say: the served window clears the harness worst case,
# the KV pool backs seqs x max-model-len at the declared cache dtype (an
# engine HARD limit, not a ratio), the per-card VRAM pair stays inside the
# measured card ceiling, the pinned-host ledger has a ttm pages cap floor and
# the serve script ENFORCES it, the served window is what models.yml must
# quote, the host-networking contract (no --publish), the image gate on a
# name@sha256 ref, the .rad payload gate with its size floor, the
# chat-template override mount, the self-healing law (the run returns before
# the engine is up, so the serve script must wait on the container for
# Restart=always to mean anything), the deployer's false-green MainPID check,
# the vendored hfget fetch path (no token for this ungated repo), and the
# client contract (reasoning auto -> reasoning_content; models.yml maps the
# field on the harness side, not here).
# The serve script is EXECUTED against fixtures (stub podman, stub device
# nodes, stub ttm sysfs knobs, a temp payload dir) so the gates are observed
# as the operator observes them: exit status and the words on stderr. The
# knobs that make that possible (FRANK_ARCHON_DEV_KFD, _DEV_DRI,
# _TTM_SYSFS_DIR, _LOG) default to the production paths.
load helpers
UNIT="$REPO_ROOT/scripts/frank-archon.service"
SERVE="$REPO_ROOT/scripts/serve-frank-archon.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-archon.env"
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
DOWNLOAD="$REPO_ROOT/scripts/download-frank-archon-swift15.sh"
BUILD="$REPO_ROOT/scripts/convert-swift15-w4dn8.sh"
HFGET="$REPO_ROOT/scripts/hfget.sh"
TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
#
# Fixtures: a full env derived from the SHIPPED frank-archon.env with only
# the host paths redirected, so a payload rename moves the env, not this file.
setup() {
STAGE="$(mktemp -d)"
MODELS="$STAGE/models"
STATE="$STAGE/state"
TTM="$STAGE/ttm"
mkdir -p "$MODELS" "$STATE" "$TTM" "$STAGE/bin" "$STAGE/dri"
touch "$STAGE/kfd" "$STAGE/template.jinja"
# A 3 MiB .rad clears the 2 MiB floor the fixture env pins.
dd if=/dev/zero of="$MODELS/payload.rad" bs=1048576 count=3 status=none
# 200 GiB of pages, the shipped cap's value, in 4 KiB pages.
printf '52428800\n' > "$TTM/pages_limit"
cat > "$STAGE/bin/podman" <<'STUB'
#!/usr/bin/env bash
case "$1" in
image)
case " ${STUB_IMAGE:-} " in
*" $3 "*) exit 0 ;;
*) exit 1 ;;
esac ;;
rm) exit 0 ;;
run) echo "PODMAN $*" >> "${STUB_ARGV:-/dev/null}"; exit 0 ;;
*) exit 0 ;;
esac
STUB
chmod +x "$STAGE/bin/podman"
IMAGE="$(sed -nE 's/^FRANK_ARCHON_IMAGE=(.+)$/\1/p' "$ENV_FILE")"
# The fixture env IS the shipped env with only the host paths and the
# size floor redirected, so a payload rename moves frank-archon.env and
# this file follows with it.
local re="s#^FRANK_ARCHON_MODEL_DIR=.*#FRANK_ARCHON_MODEL_DIR=$MODELS#;"
re+=" s#^FRANK_ARCHON_MODEL_FILE=.*#FRANK_ARCHON_MODEL_FILE=payload.rad#;"
re+=" s#^FRANK_ARCHON_RAD_MIN_MIB=.*#FRANK_ARCHON_RAD_MIN_MIB=2#;"
re+=" s#^FRANK_ARCHON_TEMPLATE=.*#FRANK_ARCHON_TEMPLATE=$STAGE/template.jinja#;"
re+=" s#^FRANK_ARCHON_STATE_DIR=.*#FRANK_ARCHON_STATE_DIR=$STATE#;"
re+=" s#^FRANK_ARCHON_TTM_MIN_MIB=.*#FRANK_ARCHON_TTM_MIN_MIB=150000#"
sed -E "$re" "$ENV_FILE" > "$STAGE/serve.env"
{
echo "FRANK_ARCHON_TTM_SYSFS=$TTM"
echo "FRANK_ARCHON_DEV_KFD=$STAGE/kfd"
echo "FRANK_ARCHON_DEV_DRI=$STAGE/dri"
echo "FRANK_ARCHON_LOG=$STAGE/serve.log"
echo "STUB_IMAGE=$IMAGE"
echo "STUB_ARGV=$STAGE/argv.txt"
} >> "$STAGE/serve.env"
}
teardown() {
rm -rf "$STAGE"
}
# serve_run — execute the serve script against the fixtures.
serve_run() {
run env PATH="$STAGE/bin:$PATH" FRANK_ARCHON_ENV_FILE="$STAGE/serve.env" \
bash "$SERVE"
}
@test "the serve script refuses when the image is not in storage" {
# The env file wins over the ambient environment (the serve script
# sources it with `set -a`), so the stub's storage is emptied in the file.
sed -i 's#^STUB_IMAGE=.*#STUB_IMAGE=#' "$STAGE/serve.env"
serve_run
[ "$status" -ne 0 ]
[[ "$output" == *"not in podman storage"* ]]
[[ "$output" == *"podman pull"* ]]
}
@test "the serve script refuses a missing .rad and names its builder" {
rm -f "$MODELS/payload.rad"
serve_run
[ "$status" -ne 0 ]
[[ "$output" == *"container: missing"*"$MODELS/payload.rad"* ]]
[[ "$output" == *"scripts/convert-swift15-w4dn8.sh"* ]]
}
@test "the serve script refuses a .rad below the size floor, naming both sizes" {
# A truncated rad-convert is a guaranteed long-timeout boot; the floor is
# the fast failure (FRANK_ARCHON_RAD_MIN_MIB).
dd if=/dev/zero of="$MODELS/payload.rad" bs=1048576 count=1 status=none
serve_run
[ "$status" -ne 0 ]
[[ "$output" == *"1 MiB"* ]]
[[ "$output" == *"2 MiB"* ]]
}
@test "the serve script refuses an absent chat template" {
# The engine is started with --override-chat-template; a missing file is
# a container that dies at argv parse, not a fast refusal.
rm -f "$STAGE/template.jinja"
serve_run
[ "$status" -ne 0 ]
[[ "$output" == *"template: missing"*"$STAGE/template.jinja"* ]]
}
@test "the serve script refuses when the ttm pages cap is below the pinned ledger" {
printf '1048576\n' > "$TTM/pages_limit" # 4 GiB, under the ~147 GiB ledger
serve_run
[ "$status" -ne 0 ]
[[ "$output" == *"ttm pages cap is 4096 MiB"* ]]
[[ "$output" == *"150000"* ]]
}
@test "the serve script refuses when the GPU device nodes are absent" {
rm -f "$STAGE/kfd"
serve_run
[ "$status" -ne 0 ]
[[ "$output" == *"GPU device nodes absent"*"$STAGE/kfd"* ]]
}
@test "the serve script runs the engine with the .rad, the template and TP=2" {
serve_run
[ "$status" -eq 0 ]
argv="$(cat "$STAGE/argv.txt")"
grep -q -- "--model /models/payload.rad" <<<"$argv"
grep -q -- "-v $MODELS:/models:ro" <<<"$argv"
grep -q -- "--override-chat-template /templates/template.jinja" <<<"$argv"
grep -q -- "--tp $(sed -nE 's/^FRANK_ARCHON_TP=([0-9]+)$/\1/p' "$ENV_FILE")" <<<"$argv"
grep -q -- "--tp-wire $(sed -nE 's/^FRANK_ARCHON_TP_WIRE=(.+)$/\1/p' "$ENV_FILE")" <<<"$argv"
grep -q -- "--kv-cache-dtype $(sed -nE 's/^FRANK_ARCHON_KV_DTYPE=(.+)$/\1/p' "$ENV_FILE")" <<<"$argv"
# The container name is the unit's ExecStop target, and the host-network
# contract publishes no port.
grep -q -- "--name $(sed -nE 's/^FRANK_ARCHON_CONTAINER=(.+)$/\1/p' "$ENV_FILE")" <<<"$argv"
grep -q -- "--network host" <<<"$argv"
! grep -q -- "--publish" <<<"$argv"
}
@test "frank-archon unit, serve script, env, fetch and build scripts and template exist" {
[ -f "$UNIT" ]
[ -x "$SERVE" ]
[ -f "$ENV_FILE" ]
[ -f "$DOWNLOAD" ]
[ -x "$BUILD" ]
[ -x "$HFGET" ]
[ -f "$TEMPLATE" ]
# The FreeToken-era Dockerfile is retired with the runtime it built:
# the radiance image is the publisher's, pulled and digest-pinned, not
# built on the host.
[ ! -f "$REPO_ROOT/scripts/docker/frank-archon-freetoken.Dockerfile" ]
}
@test "frank-archon unit is boot-persistent, self-healing and unprivileged" {
# Root unit parity with frank-dragoon/frank-zealot: enable is the [Install]
# section, the restart policy survives a crash, and the pinned host RAM
# (expert pools + PLE table + prefix host tier, ~132 GiB) needs an
# unbounded memlock (a root unit grants it via PID1; the dank session's
# own hard limit is 8 MiB).
grep -q '^WantedBy=multi-user.target$' "$UNIT"
# Restart=always, not on-failure: a clean front-end exit after an
# engine fault reads as success to systemd, so on-failure never fires.
grep -q '^Restart=always$' "$UNIT"
! grep -q '^Restart=on-failure$' "$UNIT"
# ExecStop runs `podman stop -t 120`, so the stop timeout MUST exceed
# 120 s or systemd kills the stop and records 'Failed with result
# timeout' (measured 2026-10-01). Guard the relationship, not the
# literal.
stop_t=$(awk -F= '/^TimeoutStopSec=/{print $2}' "$UNIT")
[ -n "$stop_t" ] && [ "$stop_t" -gt 120 ]
grep -q '^ExecStop=.*podman stop -t 120' "$UNIT"
# StartLimitIntervalSec belongs to [Unit]; a copy in [Service] is an
# 'Unknown key' warning systemd logs on every load.
[ "$(grep -c '^StartLimitIntervalSec=' "$UNIT")" = "1" ]
awk '/^\[Service\]/{in_svc=1} /^StartLimitIntervalSec=/{if(in_svc) exit 1}' "$UNIT"
grep -q '^RestartSec=' "$UNIT"
grep -q '^User=dank$' "$UNIT"
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
}
@test "frank-archon runs the repo's own serve script with the repo's env" {
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-archon.sh$' "$UNIT"
grep -q 'FRANK_ARCHON_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-archon.env' "$UNIT"
}
@test "the unit stops the container child explicitly — container identity is the unit name" {
# The serve script blocks on podman wait, which cannot stop the
# container child; systemd stop goes through ExecStop or the engine
# outlives the unit and holds the GPU. The container name IS the
# service name across runtimes (Frank's 2026-10-05 call).
grep -q '^ExecStop=/usr/bin/podman stop' "$UNIT"
grep -q 'podman stop -t 120 frank-archon$' "$UNIT"
grep -q '^FRANK_ARCHON_CONTAINER=frank-archon$' "$ENV_FILE"
grep -q 'frank-archon-qwen' "$ENV_FILE"
}
@test "the serve shape is direct podman run, not a launcher or a predecessor runtime" {
grep -q 'podman run' "$SERVE"
! grep -q 'launch.sh' "$SERVE"
! grep -q 'podman-shim' "$SERVE"
! grep -q 'R9V_STATE_FILE' "$SERVE"
# Every predecessor runtime is RETIRED — its image name and its
# engine-specific flags must not survive in the radiance run line.
! grep -q 'tcclaviger' "$SERVE"
! grep -q 'tcclaviger' "$ENV_FILE"
! grep -q 'freetoken\|ft serve' "$SERVE"
! grep -q 'freetoken' "$ENV_FILE"
! grep -q 'HIP_VISIBLE_DEVICES' "$SERVE"
! grep -q -- '--tensor-parallel-size' "$SERVE"
! grep -q -- '--moe-cache-rate' "$SERVE"
! grep -q -- '--num-tokens ' "$SERVE"
}
@test "the image is the publisher's, digest-pinned by manifest" {
# stilldeadcode/radiance@sha256:<64 hex> — a name@digest ref exists in
# storage only if THAT manifest was pulled. The tag form would float.
grep -qE '^FRANK_ARCHON_IMAGE=stilldeadcode/radiance@sha256:[0-9a-f]{64}$' "$ENV_FILE"
}
@test "the radiance engine shape is in the env file" {
# Served window must clear the harness worst case: OMP compacts at 70%
# of contextWindow; for 200,000 that is 140,000, and a pre-compaction
# prompt can legally reach that height — 140,492 + 36,864 = 177,356 at
# the FreeToken-era window was the record; the same relation must hold
# here (140,000 + 36,864 = 176,864 <= 200,000).
grep -q '^FRANK_ARCHON_MAX_MODEL_LEN=200000$' "$ENV_FILE"
ml=$(awk -F= '/^FRANK_ARCHON_MAX_MODEL_LEN=/{print $2}' "$ENV_FILE")
[ "$ml" -ge 177356 ]
# Concurrency: 2 slots x the served window is the capacity contract, and
# the fused TP all-reduce is declared over seqs x (1 + spec) = 6 rows,
# under the 25-row bound at which the wht6 wire stays bit-exact. The
# "seqs 2 cannot boot" law belongs to the retired tcclaviger vLLM fork
# (torch.compile query_start_loc constraint, 2026-10-02); radiance booted
# seqs 2 in the fp8-native era and the 2026-10-06 sweep had seqs 2 and 3
# tied inside the +-3% noise floor.
grep -q '^FRANK_ARCHON_MAX_SEQS=2$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_TP=2$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_TP_WIRE=wht6$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_SPEC_TOKENS=2$' "$ENV_FILE"
# expert_tiered placement: vram_weights + the pinned host pool serve the
# per-rank resident set TOGETHER, so cutting the VRAM slab to pay for the
# KV pool moves the pool with it. KV is UNQUANTIZED bf16 (the engine takes
# only bf16|fp8 — no per-tensor K/V split), the PLE n-gram table is pinned
# in host RAM per Frank's law.
grep -q '^FRANK_ARCHON_PLACEMENT=expert_tiered$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_HOST_POOL_MIB=44928$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_VRAM_WEIGHTS_MIB=[1-9]' "$ENV_FILE"
grep -q '^FRANK_ARCHON_VRAM_KV_MIB=[1-9]' "$ENV_FILE"
grep -q '^FRANK_ARCHON_KV_DTYPE=bf16$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_NGRAM_PLACEMENT=ram$' "$ENV_FILE"
# Prefix cache tiers (publisher pattern; disk tier sized for 2 slots).
grep -q '^FRANK_ARCHON_PREFIX_HOST_MIB=8192$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_PREFIX_DISK_MIB=65536$' "$ENV_FILE"
# The pinned ledger (2 x pool + 47.7 GiB PLE + prefix host) must be
# under the ttm pages cap, and the env states the floor the serve
# script enforces (>= 140,000 MiB — the current cap ~123 GiB fails it,
# which is the point).
grep -q '^FRANK_ARCHON_TTM_MIN_MIB=' "$ENV_FILE"
ttmm=$(awk -F= '/^FRANK_ARCHON_TTM_MIN_MIB=/{print $2}' "$ENV_FILE")
pool=$(awk -F= '/^FRANK_ARCHON_HOST_POOL_MIB=/{print $2}' "$ENV_FILE")
ph=$(awk -F= '/^FRANK_ARCHON_PREFIX_HOST_MIB=/{print $2}' "$ENV_FILE")
# 2 pools + PLE 47.7 GiB (48,824 MiB) + prefix host, rounded up.
ledger=$(( 2 * pool + 48824 + ph + 2048 ))
[ "$ttmm" -ge "$ledger" ]
# The .rad size floor is positive (a truncated convert must fail fast).
grep -q '^FRANK_ARCHON_RAD_MIN_MIB=' "$ENV_FILE"
[ "$(awk -F= '/^FRANK_ARCHON_RAD_MIN_MIB=/{print $2}' "$ENV_FILE")" -gt 0 ]
}
@test "the KV pool backs seqs x max-model-len at the declared cache dtype" {
# The engine's own hard limit: a request longer than the pool's token
# count is "accepted by the API and then refused as too large to ever fit
# -- this is a hard limit, not a backing ratio". Rates are measured, not
# derived: per rank, kv_attn (13 layers) pages 26.00 KiB per 4 tokens at
# fp8 and 52.00 KiB at bf16, while kv_qsa_bkey (13 layers) pages 3.25 KiB
# at BOTH — the QSA block key is an index, not attention state, and does
# not follow --kv-cache-dtype — so the rate is 7,488 B/token at fp8 and
# 14,144 at bf16 (1.89x, not 2x). The dtype-independent terms also live in
# the pool: ~110 MiB a sequence of recurrent state (kv_gdn_state 54 MiB x 2
# copies + conv/tail/ple) and 448 MiB of prefix checkpoints (8 x 55.99).
# Verified against BOTH live boots: fp8 3543-330-448 = 2765 MiB -> 387,240
# tokens (ledger 386,904); bf16 6912-220-448 = 6244 MiB -> 462,946
# (ledger 462,752). Both within 0.1%.
kv=$(awk -F= '/^FRANK_ARCHON_VRAM_KV_MIB=/{print $2}' "$ENV_FILE")
seqs=$(awk -F= '/^FRANK_ARCHON_MAX_SEQS=/{print $2}' "$ENV_FILE")
maxlen=$(awk -F= '/^FRANK_ARCHON_MAX_MODEL_LEN=/{print $2}' "$ENV_FILE")
case "$(awk -F= '/^FRANK_ARCHON_KV_DTYPE=/{print $2}' "$ENV_FILE")" in
fp8) bpt=7488 ;;
bf16) bpt=14144 ;;
*) fail "unknown FRANK_ARCHON_KV_DTYPE" ;;
esac
tokens=$(( (kv - 110 * seqs - 448) * 1048576 / bpt ))
[ "$tokens" -ge $(( seqs * maxlen )) ]
}
@test "the per-card VRAM pair stays inside the measured card ceiling" {
# Measured on Dankserver 2026-10-11: card 31,856 MiB, a boot consumes
# 1,710 MiB OUTSIDE both budgets (driver context, the 990 MiB activation
# arena, hardware queues, headroom, step staging) and the fp8 pair settled
# at 1,379 MiB free. Claiming more than 29,120 MiB in total is an
# allocation failure at graph capture, not a warning — so the KV pool and
# the weights slab are one budget, and this seam is the guard.
w=$(awk -F= '/^FRANK_ARCHON_VRAM_WEIGHTS_MIB=/{print $2}' "$ENV_FILE")
kv=$(awk -F= '/^FRANK_ARCHON_VRAM_KV_MIB=/{print $2}' "$ENV_FILE")
[ $(( w + kv )) -le 29120 ]
}
@test "the serve script wires the engine flags from the env" {
grep -q -- '--model "/models/$FRANK_ARCHON_MODEL_FILE"' "$SERVE"
grep -q -- '--served-model-name "$FRANK_ARCHON_SERVED_NAME"' "$SERVE"
grep -q -- '--host :: --port "$FRANK_ARCHON_PORT"' "$SERVE"
grep -q -- '--override-chat-template /templates/template.jinja' "$SERVE"
grep -q -- '--tp "$FRANK_ARCHON_TP" --tp-wire "$FRANK_ARCHON_TP_WIRE"' "$SERVE"
grep -q -- '--max-num-seqs "$FRANK_ARCHON_MAX_SEQS"' "$SERVE"
grep -q -- '--max-model-len "$FRANK_ARCHON_MAX_MODEL_LEN"' "$SERVE"
grep -q -- '--placement "$FRANK_ARCHON_PLACEMENT"' "$SERVE"
grep -q -- '--host-pool-mib "$FRANK_ARCHON_HOST_POOL_MIB"' "$SERVE"
grep -q -- '--vram-weights-mib "$FRANK_ARCHON_VRAM_WEIGHTS_MIB"' "$SERVE"
grep -q -- '--vram-kv-mib "$FRANK_ARCHON_VRAM_KV_MIB"' "$SERVE"
grep -q -- '--kv-cache-dtype "$FRANK_ARCHON_KV_DTYPE"' "$SERVE"
grep -q -- '--prefix-cache-host-mib "$FRANK_ARCHON_PREFIX_HOST_MIB"' "$SERVE"
grep -q -- '--prefix-cache-disk-mib "$FRANK_ARCHON_PREFIX_DISK_MIB"' "$SERVE"
grep -q -- '--ngram-placement "$FRANK_ARCHON_NGRAM_PLACEMENT"' "$SERVE"
grep -q -- '--num-speculative-tokens "$FRANK_ARCHON_SPEC_TOKENS"' "$SERVE"
# The boot-ladder escape hatch is opt-in: unset renders NO extra argv.
grep -q -- '${FRANK_ARCHON_EXTRA_ARGS:+$FRANK_ARCHON_EXTRA_ARGS}' "$SERVE"
! grep -q '^FRANK_ARCHON_EXTRA_ARGS=.' "$ENV_FILE"
}
@test "the container contract is the publisher's (host network, io_uring, pin, cards)" {
# docs/DOCKER.md: EVERY one of these is needed — host networking (the
# server binds the host's addresses; no --publish), --init (the engine
# releases the cards on SIGTERM), seccomp=unconfined (the default
# profile refuses io_uring, which the n-gram reads use, and the NUMA
# binding calls), memlock unbounded (the pinned ledger), kfd + dri.
grep -q -- '--network host' "$SERVE"
! grep -q -- '--publish' "$SERVE"
! grep -q -- '-p 7770' "$SERVE"
grep -q '^FRANK_ARCHON_PORT=7770$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_SERVED_NAME=frank-archon$' "$ENV_FILE"
grep -q -- '--init' "$SERVE"
grep -q -- '--security-opt seccomp=unconfined' "$SERVE"
grep -q -- '--ulimit memlock=-1' "$SERVE"
grep -q -- '--device /dev/kfd' "$SERVE"
grep -q -- '--device /dev/dri' "$SERVE"
grep -q -- '-v "$FRANK_ARCHON_MODEL_DIR":/models:ro' "$SERVE"
grep -q -- '-v "$FRANK_ARCHON_STATE_DIR":/data' "$SERVE"
grep -q -- '-v "$FRANK_ARCHON_TEMPLATE":/templates/template.jinja:ro' "$SERVE"
}
@test "parsers are AUTO — the engine infers them natively, the harness maps the field" {
# radiance --reasoning-format defaults to auto and emits thinking in
# `reasoning_content`; tool calls ride the chat format. NO parser
# override flags exist in this engine. models.yml's
# reasoningContentField is the harness-side contract and lives in the
# operator's home, not this repo.
! grep -q -- '--reasoning-parser' "$SERVE"
! grep -q -- '--tool-call-parser' "$SERVE"
! grep -q -- '--enable-auto-tool-choice' "$SERVE"
! grep -q 'FRANK_ARCHON_REASONING_PARSER' "$ENV_FILE"
! grep -q 'FRANK_ARCHON_TOOL_PARSER' "$ENV_FILE"
}
@test "the serve script clears stale containers and self-heals via podman wait" {
# A crashed unit can leave the container name registered forever after;
# the legacy identities hold the same GPUs and die here too. Then block
# on the container so a crash is a nonzero ExecStart.
grep -q 'FRANK_ARCHON_LEGACY_CONTAINERS' "$ENV_FILE"
grep -q 'frank-archon-qwen' "$ENV_FILE"
grep -q 'frank-archon-r9v' "$ENV_FILE"
grep -q 'qwen-fp8' "$ENV_FILE"
grep -q 'exec podman wait "$FRANK_ARCHON_CONTAINER"' "$SERVE"
}
@test "the model id matches the payload's build and fetch scripts" {
# The payload dir/file are the convert script's output layout; the fetch
# script must name the repo the convert stages from. (FP8-native era,
# the rollback payload, keeps its own trio: build-frank-archon-rad.sh +
# download-frank-archon.sh.)
grep -q '^FRANK_ARCHON_MODEL_DIR=/home/dank/models/rad$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_MODEL_FILE=qwen4exp-swift15-w4dn8-i8-hc8m.rad$' "$ENV_FILE"
grep -q 'qwen4exp-swift15-w4dn8-i8-hc8m.rad' "$BUILD"
grep -q 'ukisai/Swift1.5-Qwen3.8-Flash-Next' "$DOWNLOAD"
grep -q 'hfget.sh' "$DOWNLOAD"
# The repo is UNGATED — no token may be required on the fetch path.
! grep -q 'HF_TOKEN' "$DOWNLOAD"
}
@test "no bare HIP-reserved env var is defined" {
# HIP reads bare TEMP for its JIT cache path (2026-08-13: TEMP=1.0
# segfaulted every load), and HOST/GPU_MAX_HEAP_SIZE are the same
# class. Every key here is FRANK_ARCHON_-prefixed.
! grep -qE '^(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$ENV_FILE"
! grep -qE '^(export )?(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$SERVE"
}
@test "the deployer gates the image, payload and containers, then health-checks" {
# The deployer refuses when the image is absent from dank's rootless
# storage or the .rad is missing/too small — then clears stale
# containers (including the legacy identities), waits out the port,
# relinks/enables/restarts the root unit and health-gates it.
grep -q 'LLM_ARCHON_CONTAINER=frank-archon$' "$REMOTE_DEPLOY"
grep -q 'FRANK_ARCHON_IMAGE' "$REMOTE_DEPLOY"
grep -q 'podman image exists' "$REMOTE_DEPLOY"
grep -q 'FRANK_ARCHON_MODEL_FILE' "$REMOTE_DEPLOY"
grep -q 'FRANK_ARCHON_RAD_MIN_MIB' "$REMOTE_DEPLOY"
grep -q 'frank-archon-qwen' "$REMOTE_DEPLOY"
grep -q 'qwen-fp8' "$REMOTE_DEPLOY"
grep -q 'glm-probe' "$REMOTE_DEPLOY"
grep -q 'wait_port_free 7770' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-archon' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-archon.service' "$REMOTE_DEPLOY"
grep -q 'systemctl restart frank-archon.service' "$REMOTE_DEPLOY"
# The health gate is a bare curl: the HTTP listener binds AFTER the
# full load (main.cpp — engine.start() then engine.serve()), so during
# load the port refuses connections and a bare curl is honest. The
# FreeToken-era body read ('status ok') is gone with that engine.
grep -q 'curl -fsS http://127.0.0.1:7770/health' "$REMOTE_DEPLOY"
! grep -q '"status":"ok"' "$REMOTE_DEPLOY"
}
@test "the deployer will not report green on a stray port-holder" {
# The 2026-09-26 false green: a leftover probe held :7770 and satisfied
# the health curl while the real unit was stopped. The deploy is only
# green when the unit's own MainPID is live behind the health answer.
grep -q 'systemctl show -p MainPID --value frank-archon.service' "$REMOTE_DEPLOY"
grep -q 'stray process holds the port' "$REMOTE_DEPLOY"
}