Files
serverAppConfigs/tests/frank_dragoon_unit.bats
T
Frank 7c4dbebe24 frank-dragoon: lower the context to 200704 (ctx and pool together)
Frank's call the same evening as the native-payload cutover: HALOGEN_CTX and
HALOGEN_KV_POOL_POSITIONS both 262144 -> 200704 (a multiple of 4096, the rung
frank-zealot already runs). The checkpoint's native window is 262,144 and the
engine serves anything below it, so this is a RAM decision, not a capability
limit: the KV reservation drops 7.2 GiB -> ~5.4 (the 2026-10-06 pool measured
14.6), and by the FLAGS.md law -- RAM the pool takes is RAM that n-gram table
loses -- the freed positions become page cache for the 47.68 GiB table that
long prompts read. Engine-side total ~74 GiB instead of 75.8.

The 400-zone law is invariant to the value, so the harness moves WITH the
ceiling: models.yml contextWindow 217088 -> 155648 = ceiling - 45056, keeping
the gap that absorbs the chars/4 under-count (worst-case request ~156.2k vs
the 200,704 ceiling, ~44k margin). Compaction now fires at 85% = 132,300 est
(~136k measured); the 180k target of the 262,144 era is unreachable because
the window itself is 155,648. maxTokens 45056 and the coupled
cap/timeout 65536/7200 are untouched.

Cost stated plainly: with pool == ctx, ONE maximum-length request fills the
pool, so two long conversations never coexist (two of <=100k do). Acceptable
because frank-dragoon carries only the slow role and the security-reviewer
subagent, not the interactive workhorse.

Seam re-keyed: the fit-law test now proves the invariant at the new pair, and
the pool law (ctx <= pool <= slots * ctx) still holds with pool == ctx.
2026-10-17 22:29:22 -07:00

384 lines
18 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bats
# Seam: frank-dragoon serves Quat3rnion's NATIVE v2 conversion (2026-10-11)
# of the abliterated Qwen3.8-Flash-Next (a .hgn flash-server payload) on the
# Strix Halo iGPU through the halogen-flash-server CONTAINER (0.16.2,
# digest-pinned), driven by the root unit's serve script — 2 slots over ONE
# 200,704-position KV pool with the MTP head always on (HALOGEN_SPEC_ADAPT=0)
# and the vision tower mounted. 2026-10-17 cutover from the Swift1.5-finetune
# shape, which itself cut over the halogen 27B-dense shape on 2026-10-06.
#
# This seam exists because the artifacts are a triangle that must agree on
# exact strings the engine reads: the image DIGEST, the checkpoint/n-gram/
# vision file names AND byte counts inside the /models mount, and the
# tokenizer's flat-file shape (a HF cache snapshot's symlinks dangle inside
# the container). A serve gate and a deployer gate that disagree is the
# 2026-10-01 crash-loop; both sides are pinned here, plus the one engine
# law the card is emphatic about: HALOGEN_SPEC_ADAPT=0 — the default
# adaptive policy silently disables the MTP head per request and HALVES
# decode.
#
# Everything is tested from what an operator or supervisor can observe —
# the command line podman would run, the exit code, the deployer's error
# text — never from anything inside the engine.
setup() {
ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
SERVE="$ROOT/scripts/serve-frank-dragoon.sh"
ENV_FILE="$ROOT/scripts/frank-dragoon.env"
UNIT="$ROOT/scripts/frank-dragoon.service"
DEPLOYER="$ROOT/scripts/deploy-remote.sh"
BUILD="$ROOT/scripts/build-rocmfpx.sh"
TEST_TMP="$(mktemp -d)"
touch "$TEST_TMP/empty.env"
}
teardown() {
rm -rf "$TEST_TMP"
}
# podman_stub — a fake podman on PATH that prints the argv of `run` and
# `wait` and answers `images` with whatever digests the test names, so
# "what would the supervisor run" is observable output and the digest
# gate is testable without a 3.6 GB image.
podman_stub() {
mkdir -p "$TEST_TMP/bin"
cat > "$TEST_TMP/bin/podman" <<'STUB'
#!/usr/bin/env bash
case "$1" in
images) printf '%s\n' "${STUB_DIGESTS:-}" ;;
rm) exit 0 ;;
run|wait) echo "PODMAN $*"; exit "${STUB_WAIT_RC:-0}" ;;
*) exit 0 ;;
esac
STUB
chmod +x "$TEST_TMP/bin/podman"
}
# good_env — the minimum env the serve script demands, with the digest
# stub agrees on and zero-byte artifacts passing the size gates.
good_env() {
: > "$TEST_TMP/empty.env"
touch "$TEST_TMP/qwen.hgn" "$TEST_TMP/ngram.hgn" "$TEST_TMP/vision.hgn"
mkdir -p "$TEST_TMP/tokenizer" && touch "$TEST_TMP/tokenizer/tok.json"
# A host with plenty of RAM: the pin-gate preflight passes by default so
# the artifact gates below are the thing under test.
printf 'MemTotal: 127108112 kB\nMemAvailable: 100000000 kB\n' > "$TEST_TMP/meminfo"
GOOD_ENV=(
"FRANK_DRAGOON_ENV_FILE=$TEST_TMP/empty.env"
"FRANK_DRAGOON_IMAGE=ghcr.io/peonist-ai/halogen-flash-server@sha256:cafe"
"FRANK_DRAGOON_CONTAINER=frank-dragoon-halogen"
"FRANK_DRAGOON_LEGACY_CONTAINERS=frank-dragoon"
"FRANK_DRAGOON_DEV_KFD=$TEST_TMP/qwen.hgn"
"FRANK_DRAGOON_DEV_DRI=$TEST_TMP"
"FRANK_DRAGOON_LOG=$TEST_TMP/serve.log"
"FRANK_DRAGOON_PUBLISH=7770:8731"
"FRANK_DRAGOON_MODEL_DIR=$TEST_TMP"
"FRANK_DRAGOON_CHECKPOINT=qwen.hgn"
"FRANK_DRAGOON_CHECKPOINT_BYTES=0"
"FRANK_DRAGOON_NGRAM_TABLE=ngram.hgn"
"FRANK_DRAGOON_NGRAM_BYTES=0"
"FRANK_DRAGOON_VISION_TOWER=vision.hgn"
"FRANK_DRAGOON_VISION_BYTES=0"
"FRANK_DRAGOON_TOKENIZER_FILES=tok.json"
"HALOGEN_MODEL_ID=frank-dragoon"
"HALOGEN_CTX=200704"
"HALOGEN_KV_POOL_POSITIONS=200704"
"HALOGEN_KV_SLOTS=2"
"HALOGEN_SPEC_ADAPT=0"
"HALOGEN_MAX_TOK=8192"
"FRANK_DRAGOON_MEMINFO=$TEST_TMP/meminfo"
"STUB_DIGESTS=localhost/x ghcr.io/peonist-ai/halogen-flash-server@sha256:cafe"
)
}
@test "the env file defines the flash-server deployment values" {
grep -qE '^FRANK_DRAGOON_IMAGE=ghcr\.io/peonist-ai/halogen-flash-server@sha256:[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CONTAINER=frank-dragoon-halogen$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_PUBLISH=7770:8731$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CHECKPOINT=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CHECKPOINT_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CHECKPOINT_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_NGRAM_TABLE=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_NGRAM_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_NGRAM_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_VISION_TOWER=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_VISION_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_VISION_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_TOKENIZER_FILES="tokenizer\.json( [a-z_.]+)+"$' "$ENV_FILE"
grep -qE '^HALOGEN_MODEL_ID=frank-dragoon$' "$ENV_FILE"
grep -qE '^HALOGEN_CTX=[0-9]+$' "$ENV_FILE"
}
@test "the head stays on: adaptive spec must stay 0 and the window shape pinned" {
# The card is emphatic: the default adaptive policy turns the MTP head
# off for whole requests after low acceptance and HALVES decode. The
# two-slot law does NOT carry over from the 27B engine — on this line
# batching and speculation coexist (the card's quickstart ships 2
# slots WITH the head), so 2 is concurrent capacity, not a tax.
grep -qE '^HALOGEN_SPEC_ADAPT=0$' "$ENV_FILE"
grep -qE '^HALOGEN_KV_SLOTS=2$' "$ENV_FILE"
# The cap/timeout pair is coupled (FLAGS.md table): a request at the
# cap must not outlast the queue timeout, or everyone queued behind
# it eats a 503. The per-request default must stay under the cap.
grep -qE '^HALOGEN_MAX_TOK=[0-9]+$' "$ENV_FILE"
grep -qE '^HALOGEN_MAX_TOKENS_CAP=[0-9]+$' "$ENV_FILE"
grep -qE '^HALOGEN_QUEUE_TIMEOUT=[0-9]+$' "$ENV_FILE"
mt="$(sed -nE 's/^HALOGEN_MAX_TOK=([0-9]+)$/\1/p' "$ENV_FILE")"
cap="$(sed -nE 's/^HALOGEN_MAX_TOKENS_CAP=([0-9]+)$/\1/p' "$ENV_FILE")"
[ "$mt" -le "$cap" ]
}
@test "the serve script refuses when there is no GPU to serve on" {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_DEV_KFD="$TEST_TMP/absent-node" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"no gfx1151 to serve on"* ]]
}
@test "the pool and memory ledger fit unified memory with margin" {
# Pool arithmetic is the engine's documented shape: the explicit
# HALOGEN_KV_POOL_POSITIONS must back at least one full window, and KV
# costs
# ~28 KiB/position (FLAGS.md). Against it: ~62 GiB resident weights,
# the arena scratch 3.8 + 8.4*(MAX_TOK/16384) GiB, the vision tower
# ~2 GiB pool charge, the 20 GiB host reserve and the host baseline
# + co-tenancy (frank-zealot, ComfyUI, Caddy/observability) ~15. The
# box has 121 GiB unified. The n-gram table rides as mmap'd page
# cache beside the checkpoint and is not a pinned charge (the card's
# own quickstart arithmetic does not bill it).
ctx="$(sed -nE 's/^HALOGEN_CTX=([0-9]+)$/\1/p' "$ENV_FILE")"
slots="$(sed -nE 's/^HALOGEN_KV_SLOTS=([0-9]+)$/\1/p' "$ENV_FILE")"
pool="$(sed -nE 's/^HALOGEN_KV_POOL_POSITIONS=([0-9]+)$/\1/p' "$ENV_FILE")"
mt="$(sed -nE 's/^HALOGEN_MAX_TOK=([0-9]+)$/\1/p' "$ENV_FILE")"
reserve="$(sed -nE 's/^HALOGEN_HOST_RESERVE_GIB=([0-9]+)$/\1/p' "$ENV_FILE")"
# The pool is ONE budget behind the slots (boot ledger form: "2 slots over
# ONE <pool>-position KV pool, each request up to HALOGEN_CTX"), NOT
# slots x ctx — one window behind both slots IS the memory saving. What
# still binds: the pool must back ONE full-length request (or the
# advertised per-request ceiling would be unreachable, hence pool >= ctx),
# it must not pretend to be slots x ctx (pool <= slots * ctx), and the
# ledger sum below must stay inside unified memory.
[ "$pool" -ge "$ctx" ]
[ "$pool" -le $(( slots * ctx )) ]
kv_gib=$(( pool * 28 * 1024 / 1073741824 ))
arena_tenths=$(( 38 + 84 * mt / 16384 ))
total_gib=$(( 62 + kv_gib + arena_tenths / 10 + 2 + reserve + 15 ))
[ "$total_gib" -le 121 ]
}
@test "the serve script refuses when the image is not in storage" {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" STUB_DIGESTS="" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"not in podman storage"* ]]
[[ "$output" == *"podman pull"* ]]
}
@test "the serve script refuses a missing checkpoint and names the fetch script" {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_CHECKPOINT=absent.hgn "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"checkpoint"*"missing"* ]]
[[ "$output" == *"download-frank-dragoon-v2abl.sh"* ]]
}
@test "the serve script refuses a checkpoint of the wrong byte count" {
# A truncated curl exit-0 artifact must not boot; the Hub's byte
# count is the contract (fleet fetch law).
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_CHECKPOINT_BYTES=42 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"expected 42"* ]]
}
@test "the serve script refuses a missing or wrong-size n-gram table" {
# The lookup table is REQUIRED: the engine reads it beside the
# checkpoint (the card names it explicitly), and prompt-lookup
# drafting dies without it.
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_NGRAM_TABLE=absent.hgn "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"n-gram-table"*"missing"* ]]
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_NGRAM_BYTES=42 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"expected 42"* ]]
}
@test "the serve script refuses a missing or wrong-size vision tower" {
# Optional on the engine, ENABLED here (image input is a fleet
# contract) — a silent absence would degrade the endpoint to
# text-only with no error anywhere.
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_VISION_TOWER=absent.hgn "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"vision-tower"*"missing"* ]]
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_VISION_BYTES=42 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"expected 42"* ]]
}
@test "the serve script refuses a symlinked tokenizer file" {
# A HuggingFace cache snapshot's entries are symlinks into blobs/ and
# dangle inside the container mount — even when the directory itself
# is real.
podman_stub
good_env
rm "$TEST_TMP/tokenizer/tok.json"
ln -sf /nowhere/x "$TEST_TMP/tokenizer/tok.json"
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"missing or a symlink"* ]]
}
@test "the serve script refuses to start when host RAM is below the pin gate" {
# The engine's pin gate: MemAvailable must clear the checkpoint plus a
# 16 GiB floor, or the unit crash-loops (each boot dying in ~5 s) until
# the RAM clears. The 2026-10-06 cutover hit this — 8 boots, 74.77 GiB
# vs 78.11 needed. RAM_WAIT=0 so the preflight refuses instead of
# waiting; the checkpoint is zero bytes (good_env) so the floor is the
# whole gate.
podman_stub
good_env
printf 'MemTotal: 127108112 kB\nMemAvailable: 10000000 kB\n' > "$TEST_TMP/meminfo-short"
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_DRAGOON_MEMINFO="$TEST_TMP/meminfo-short" FRANK_DRAGOON_RAM_WAIT=0 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"pin gate"* ]]
[[ "$output" == *"refusing to start"* ]]
}
@test "the render names the container, the port map and the engine shape" {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" "$SERVE"
[ "$status" -eq 0 ]
# `podman run` output is redirected to the log by design (the unit
# journal must not absorb engine chatter); the stub's printed argv
# lands there, so the render assertions read the same log.
output="$output
$(cat "$TEST_TMP/serve.log")"
[[ "$output" == *"--name frank-dragoon-halogen"* ]]
[[ "$output" == *"--publish 7770:8731"* ]]
[[ "$output" == *"-e HALOGEN_MODEL_ID=frank-dragoon"* ]]
[[ "$output" == *"-e HALOGEN_CTX=200704"* ]]
[[ "$output" == *"-e HALOGEN_KV_POOL_POSITIONS=200704"* ]]
[[ "$output" == *"-e HALOGEN_KV_SLOTS=2"* ]]
[[ "$output" == *"-e HALOGEN_SPEC_ADAPT=0"* ]]
[[ "$output" == *"-e HALOGEN_CHECKPOINT=/models/qwen.hgn"* ]]
[[ "$output" == *"-e HALOGEN_NGRAM_TABLE=/models/ngram.hgn"* ]]
[[ "$output" == *"-e HALOGEN_VISION_TOWER=/models/vision.hgn"* ]]
[[ "$output" == *"--device $TEST_TMP/qwen.hgn"* ]]
[[ "$output" == *"$TEST_TMP/tokenizer:/tokenizer:ro"* ]]
[[ "$output" == *"ghcr.io/peonist-ai/halogen-flash-server@sha256:cafe"* ]]
# Foreground run, never -d: the engine ledger must land in the log
# file (`podman run -d` captured only the container ID and the
# runbook's ledger grep read a one-line hex file, found 2026-10-06).
! grep -qE '(^|[[:space:]])-d([[:space:]]|$)' "$TEST_TMP/serve.log"
! grep -q 'PODMAN wait' "$TEST_TMP/serve.log"
}
@test "the shipped env keeps the harness fit law inside the ceiling" {
# HALOGEN_CTX is the hard per-request ceiling (a longer prompt is a 400
# naming the limit, never a silent truncation). The law is INVARIANT to
# the value: the harness window must sit ~45k UNDER it, because the
# harness clamps max_tokens = min(45056, window − est − 4096), compacts
# at 85 % of its declared window, and its chars/4 est UNDER-counts the
# rendered prompt by ~3 % on code/agent traffic (measured 4,450 tokens at
# est 152,938 on 2026-10-07 — with window == ceiling, every request over
# ~65 % of the window was a guaranteed 400 before compaction could fire).
# The shipped pair is ctx 200704 / window 155648 (lowered from
# 262144/217088 on 2026-10-17); the gap must absorb the error at BOTH
# monotone maxima of est + budget (est ≤ window − 4096):
# full budget at the compaction threshold: 1.03*132300 + 45056
# clamped budget at the clamp's zero point: 1.03*151552 + 0
# and the compaction request that rides on frank-zealot (its
# 200,704-token slot) must fit: input ≈ threshold − keepRecent(20k).
grep -qE '^HALOGEN_CTX=200704$' "$ENV_FILE"
ctx=200704
hw=155648; mt=45056
thr=$(( hw * 85 / 100 ))
[ $(( thr * 103 / 100 + mt )) -le "$ctx" ]
zero=$(( hw - 4096 ))
[ $(( zero * 103 / 100 )) -le "$ctx" ]
[ $(( (thr - 20000) * 103 / 100 )) -le 200704 ]
}
@test "no HIP-sensitive bare names leak through the env file" {
# HIP reads bare env vars out of a `set -a` source; a tunable named
# HOST or TEMP used to send the JIT cache to path 1.0 and segfault
# every load (kept from the llama.cpp era — the container gets only
# the explicit -e list, but the host env still reaches podman itself).
! grep -qE '^(HOST|TEMP|GPU_MAX_HEAP_SIZE)=' "$ENV_FILE"
}
@test "the deployer gates exactly the artifacts the serve script opens" {
# Serve and deploy must name the SAME image digest, checkpoint and
# tokenizer: the 2026-10-01 crash-loop was exactly these twins
# disagreeing mid-cutover.
img="$(sed -nE 's/^FRANK_DRAGOON_IMAGE=(.*)$/\1/p' "$ENV_FILE")"
grep -qF "LLM_DRAGOON_IMAGE=\"\${LLM_DRAGOON_IMAGE:-$img}\"" "$DEPLOYER"
dir="$(sed -nE 's/^FRANK_DRAGOON_MODEL_DIR=(.*)$/\1/p' "$ENV_FILE")"
ckpt="$(sed -nE 's/^FRANK_DRAGOON_CHECKPOINT=(.*)$/\1/p' "$ENV_FILE")"
tokf="$(sed -nE 's/^FRANK_DRAGOON_TOKENIZER_FILES=\"(.*)\"$/\1/p' "$ENV_FILE")"
grep -qF "LLM_DRAGOON_TOK_DIR=\"\${LLM_DRAGOON_TOK_DIR:-$dir/tokenizer}\"" "$DEPLOYER"
grep -qF "LLM_DRAGOON_TOK_FILES=\"\${LLM_DRAGOON_TOK_FILES:-$tokf}\"" "$DEPLOYER"
bytes="$(sed -nE 's/^FRANK_DRAGOON_CHECKPOINT_BYTES=([0-9]+)$/\1/p' "$ENV_FILE")"
grep -qF "LLM_DRAGOON_CKPT_BYTES=\"\${LLM_DRAGOON_CKPT_BYTES:-$bytes}\"" "$DEPLOYER"
}
@test "the deployer points a missing artifact at the v2abl fetch script" {
grep -qE 'download-frank-dragoon-v2abl\.sh' "$DEPLOYER"
grep -qE 'podman pull' "$DEPLOYER"
}
@test "the stray sweep cannot take frank-zealot or the deployer down" {
# frank-zealot also listens on this host (7771, llama-server) and the
# deployer's own command line contains "frank-dragoon"; only the FULL
# container name matches a stray manual flash-server run and nothing
# else.
grep -q "pkill -f 'frank-dragoon-halogen'" "$DEPLOYER"
! grep -qE "pkill -f 'llama-server'" "$DEPLOYER"
! grep -qE "pkill -f 'frank-dragoon'" "$DEPLOYER"
}
@test "the deployed unit keeps the fleet's restart and boot-persistence shape" {
grep -qE '^Restart=(always|on-failure)$' "$UNIT"
grep -qE '^WantedBy=' "$UNIT"
grep -qE '^ExecStart=.*/serve-frank-dragoon\.sh$' "$UNIT"
grep -qE '^User=dank$' "$UNIT"
grep -qE '^LimitMEMLOCK=infinity$' "$UNIT"
# The service's lifetime is the container's; stopping the service
# must stop the container by its real name.
grep -qE '^ExecStop=.*podman stop .* frank-dragoon-halogen$' "$UNIT"
}
@test "the unit runs the flash-server runtime and no retired path" {
grep -q 'halogen-flash-server' "$UNIT"
# Every retired payload/build stays on the host, so a stale path in
# a unit still resolves — to a runtime nobody has measured in weeks.
! grep -qE 'GitPackages/(ROCmFPX-kairic|llama.cpp-ik_llama|llama-builds|ciru-orca)' "$UNIT"
! grep -qE 'kingjones-ROCmFPX' "$UNIT"
}
@test "the rollback door stays qualified: the ROCmFPX build pin survives" {
# Rolling back to the pre-halogen shape means rebuilding the fork the
# old payload needs; the pin (real commit, gfx1151, no webui fetch)
# is what makes that door trustworthy months later.
grep -qE 'ROCMFPX_COMMIT=.*[0-9a-f]{40}' "$BUILD"
grep -qE 'GPU_TARGET=.*gfx1151' "$BUILD"
grep -qE 'DLLAMA_BUILD_WEBUI=OFF' "$BUILD"
}