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:
1 parent
f31e7c5ed8
commit
cb25796a6b
2 files changed
+160
-49
No files matched your search
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user