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.
This commit is contained in:
Frank committed 2026-10-13 21:44:38 -07:00
1 parent f31e7c5ed8
commit cb25796a6b
2 files changed
+160 -49

No files matched your search

+28 -32
View File
@@ -26,6 +26,13 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# The artifact gates are the shared library: the serve script and the
# deployer must refuse the same missing/truncated payload with the same
# words (the fleet law from the 2026-10-01 crash-loop).
export GATE_PREFIX=frank-archon
# shellcheck disable=SC1091
source "$SCRIPT_DIR/lib/llm-gates.sh"
ENV_FILE="${FRANK_ARCHON_ENV_FILE:-$SCRIPT_DIR/frank-archon.env}"
if [ ! -f "$ENV_FILE" ]; then
@@ -65,38 +72,22 @@ set +a
# The image gate is the digest-pinned ref itself: a name@sha256 ref exists
# in storage only if THAT manifest was pulled — a retagged look-alike or a
# newer latest does not satisfy it. `podman image exists` takes a
# name@digest ref in one command — no pipefail/grep race (the 2026-09-30
# lesson). Runs as dank: the unit's process uses dank's ROOTLESS podman
# storage (root unit, User=dank — the fleet's frank-archon shape).
if ! podman image exists "$FRANK_ARCHON_IMAGE"; then
echo "ERROR: radiance image absent: $FRANK_ARCHON_IMAGE" >&2
echo "frank-archon: podman pull docker://stilldeadcode/radiance:latest (as dank)" >&2
exit 1
fi
# newer latest does not satisfy it. One command, no pipefail/grep race (the
# 2026-09-30 lesson). Runs as dank: the unit's process uses dank's ROOTLESS
# podman storage (root unit, User=dank — the fleet's frank-archon shape).
require_image "$FRANK_ARCHON_IMAGE" \
"podman pull docker://stilldeadcode/radiance:latest (as dank)" || exit 1
# The payload gate: the .rad container file, sized. A truncated convert
# would otherwise buy a guaranteed long-timeout boot instead of this fast
# failure. The staging dir / raw checkpoint are NOT this script's concern
# (scripts/build-frank-archon-rad.sh built the file; its rad-info output
# is the record).
RAD="$FRANK_ARCHON_MODEL_DIR/$FRANK_ARCHON_MODEL_FILE"
if [ ! -f "$RAD" ]; then
echo "ERROR: .rad container missing: $RAD" >&2
echo "frank-archon: run scripts/build-frank-archon-rad.sh" >&2
exit 1
fi
RAD_MIB=$(( $(stat -c %s "$RAD") / 1048576 ))
if [ "$RAD_MIB" -lt "$FRANK_ARCHON_RAD_MIN_MIB" ]; then
echo "ERROR: .rad container too small: $RAD is ${RAD_MIB} MiB, want >= $FRANK_ARCHON_RAD_MIN_MIB" >&2
echo "frank-archon: the convert was truncated — rebuild it" >&2
exit 1
fi
# (the convert script built the file; its rad-info output is the record).
require_min_mib "$FRANK_ARCHON_MODEL_DIR/$FRANK_ARCHON_MODEL_FILE" \
container "$FRANK_ARCHON_RAD_MIN_MIB" \
"build it: scripts/convert-swift15-w4dn8.sh (rad-info is the record)" || exit 1
if [ ! -f "$FRANK_ARCHON_TEMPLATE" ]; then
echo "ERROR: chat template missing: $FRANK_ARCHON_TEMPLATE" >&2
exit 1
fi
require_file "$FRANK_ARCHON_TEMPLATE" template \
"the chat template is vendored: scripts/qwen38-froggeric-v22.5.jinja" || exit 1
mkdir -p "$FRANK_ARCHON_STATE_DIR/kvcache/frank-archon"
# The engine's create is NOT recursive: with only the state dir mounted, its
@@ -112,8 +103,12 @@ mkdir -p "$FRANK_ARCHON_STATE_DIR/tune"
# that wrote it. The dir is pre-created for the same reason as kvcache
# (the engine's create is not recursive).
if [ ! -e /dev/kfd ] || [ ! -d /dev/dri ]; then
echo "ERROR: GPU device nodes absent (/dev/kfd, /dev/dri)" >&2
# GPU access is mandatory; the node paths are knobs so the seam can drive
# this gate on a box without AMD GPUs (production defaults).
DEV_KFD="${FRANK_ARCHON_DEV_KFD:-/dev/kfd}"
DEV_DRI="${FRANK_ARCHON_DEV_DRI:-/dev/dri}"
if [ ! -e "$DEV_KFD" ] || [ ! -d "$DEV_DRI" ]; then
echo "ERROR: GPU device nodes absent ($DEV_KFD, $DEV_DRI)" >&2
exit 1
fi
@@ -129,8 +124,9 @@ ttm_cap_mib() {
# min over the knobs THIS kernel exposes — the current kernel has
# pages_limit only (no pages_max); a knob that is absent is skipped,
# not a zero (a zero would refuse a healthy cap).
local v cur=""
for p in /sys/module/ttm/parameters/pages_limit /sys/module/ttm/parameters/pages_max; do
local v cur="" dir
dir="${FRANK_ARCHON_TTM_SYSFS:-/sys/module/ttm/parameters}"
for p in "$dir/pages_limit" "$dir/pages_max"; do
[ -r "$p" ] || continue
v="$(cat "$p" 2>/dev/null)" || continue
[ -n "$v" ] || continue
@@ -155,7 +151,7 @@ for CTR in $FRANK_ARCHON_LEGACY_CONTAINERS "$FRANK_ARCHON_CONTAINER"; do
podman rm -f "$CTR" >/dev/null 2>&1 || true
done
LOG=/home/dank/logs/frank_archon.log
LOG="${FRANK_ARCHON_LOG:-/home/dank/logs/frank_archon.log}"
mkdir -p "$(dirname "$LOG")"
# Publisher-vetted container contract (docs/DOCKER.md, compose/common.yaml):