From d4ce76b68bb4b042b673e4036516c86ef195a12b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 14 Oct 2026 13:36:40 -0700 Subject: [PATCH] refact(comments): state the mechanism, not the incident Every comment that invoked a 'law' (50 word-boundary sites across 28 tracked files) now states what is going on and why, in terms that transfer to any project: gate twins drift until one side greenlights what the other refuses; a tag re-points while a digest pins; a restrictive engine behavior belongs to the runtime that imposed it and must be re-tested, not inherited; a speculative head is tensors in the file, not a flag; snapshot links dangle where the container resolves them; exit 0 is a clean stop to systemd, so a self-exiting daemon on on-failure goes dark reporting success. Event narratives and dates are gone from these comments; the mechanics carry the argument. Behavior-identical: bash -n, py_compile, yaml parse, full bats suite 408/408 green. One symbol rename rides along (stack script local scale_law -> scale_contract, with its log text). --- scripts/convert-swift15-w4dn8.sh | 4 +-- scripts/deploy-remote.sh | 33 ++++++++++--------- scripts/derive-protected-experts.py | 2 +- scripts/download-frank-archon-swift15.sh | 9 ++--- scripts/download-frank-archon.sh | 9 ++--- scripts/download-frank-dragoon-flashnext.sh | 17 ++++++---- scripts/download-frank-dragoon-halogen.sh | 14 ++++---- scripts/frank-archon-expert-activity.py | 14 ++++---- scripts/frank-archon.env | 26 ++++++++------- scripts/frank-dragoon.env | 16 +++++---- scripts/frank-zealot.env | 10 +++--- scripts/frank-zealot.service | 9 ++--- scripts/lib/llm-gates.sh | 26 ++++++++------- .../glm53-dflash-01-aux-hidden-states.patch | 9 ++--- scripts/probe-frank-archon-kv.py | 10 +++--- .../qwen4exp-swift15-w4dn8-i8-hc8m.recipe | 23 ++++++++----- scripts/serve-frank-archon.sh | 5 +-- scripts/serve-frank-dragoon.sh | 7 ++-- scripts/serve-frank-zealot.sh | 15 +++++---- scripts/service-map.py | 14 ++++---- scripts/stack-qwen38-official-fp8.py | 6 ++-- shared/services.yml | 4 +-- tests/frank_archon_unit.bats | 22 ++++++------- tests/frank_dragoon_unit.bats | 31 ++++++++--------- tests/llm_deploy_gates.bats | 4 +-- tests/llm_gates.bats | 10 +++--- tests/llm_units.bats | 6 ++-- tests/service_inventory.bats | 4 +-- 28 files changed, 195 insertions(+), 164 deletions(-) diff --git a/scripts/convert-swift15-w4dn8.sh b/scripts/convert-swift15-w4dn8.sh index 6a19690..00468fc 100755 --- a/scripts/convert-swift15-w4dn8.sh +++ b/scripts/convert-swift15-w4dn8.sh @@ -26,8 +26,8 @@ RECIPE=/home/dank/serverAppConfigs-swift15/scripts/recipes/qwen4exp-swift15-w4dn CALIB=/home/dank/corpora/calib OUT=/home/dank/models/rad/qwen4exp-swift15-w4dn8-i8-hc8m.rad LOG=/home/dank/logs/swift15_convert.log -# gate_up ~37.6 + down ~18.8 + PLE 47.7 + trunk/embeddings/head ~10 GiB (all-w4 expert table; -# the plugin's one-encoding law dropped the block-fp8 down tier -- see the recipe header). +# gate_up ~37.6 + down ~18.8 + PLE 47.7 + trunk/embeddings/head ~10 GiB (all-w4 expert +# table; one encoding per expert table rules out a block-fp8 down tier -- see the recipe header). MIN_MIB=${FRANK_SWIFT15_RAD_MIN_MIB:-108000} # Gate 1: inputs. A missing Gram directory converts UNCALIBRATED with a diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index 85e85ee..2920478 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -11,7 +11,7 @@ set -euo pipefail # The LLM handlers' artifact gates are the serve scripts' own library, so # the deployer and the unit refuse the same missing payload with the same -# words (the fleet law from the 2026-10-01 crash-loop). GATE_PREFIX is set +# words — no deploy can pass a gate the unit would fail. GATE_PREFIX is set # per handler. # shellcheck disable=SC1091 source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/llm-gates.sh" @@ -926,17 +926,17 @@ deploy_plex() { systemctl --user restart plex.service # Readiness gate: /identity answers MediaContainer XML once PMS is up. # - # HTTPS, NOT HTTP. PMS drops plain HTTP off-loopback: the probe's - # connection arrives on the container's eth0, so an http:// request is - # accepted and then answered with nothing (curl 52, Empty reply) against a - # perfectly healthy server. Measured 2026-10-02 — the gate burned its full - # 600s and failed a plex whose https://127.0.0.1:32400/identity answered - # 200 in 44 ms the whole time. The same law is why the Caddyfile dials the - # plex upstream over TLS (tests/caddyfile.bats pins it). + # HTTPS, NOT HTTP. PMS serves plain HTTP on loopback only: a probe + # arriving on the container's interface gets its connection accepted and + # then an empty reply (curl 52) from a perfectly healthy server, so an + # http:// probe would burn the whole timeout and fail a service that + # answers https://127.0.0.1:32400/identity in milliseconds. The Caddyfile + # dials the same upstream over TLS for the same reason + # (tests/caddyfile.bats pins it). # -k: PMS presents its own self-signed cert on 32400. # - # Reality check from the 2026-09 cutover: the FIRST container boot of a - # years-old library ran ~10 min, so the gate still allows 600s. + # The budget accounts for a first boot of a large pre-existing library, + # which spends minutes indexing before it answers anything. # Fail the deploy (pending state preserved) rather than mark it deployed blind. for ((i = 1; i <= 120; i++)); do curl -fsSk --max-time 3 https://127.0.0.1:32400/identity >/dev/null && return 0 @@ -1184,11 +1184,12 @@ deploy_llm_dragoon() { retire_halogen_dragoon_quadlet # The predecessor on a re-deploy is THIS unit: stop it (ExecStop stops # the container) before waiting on the port. The stray sweep is keyed - # on the FULL container name, which appears in the argv of a manual - # `podman run --name frank-dragoon-halogen` / `podman wait` but NOT in - # this deployer's own command line (`bash deploy-remote.sh - # frank-dragoon`) — so it cannot shoot itself in the foot (the fleet - # pkill law), and it can never match frank-zealot's llama-server. + # on the FULL container name: it appears in the argv of a manual + # `podman run --name frank-dragoon-halogen` / `podman wait`, while a + # pattern naming only the runtime would ALSO match this deployer's own + # command line — a pkill whose pattern hits its own argv takes the + # running deploy down with the target. The full name can never match + # frank-zealot's llama-server either. run_privileged systemctl stop frank-dragoon.service 2>/dev/null || true pkill -f 'frank-dragoon-halogen' 2>/dev/null || true podman rm -f frank-dragoon frank-dragoon-halogen >/dev/null 2>&1 || true @@ -1220,7 +1221,7 @@ deploy_llm_zealot() { # Refuse rather than restart-loop: a unit whose runtime or payload is # missing never becomes healthy, and a green deploy would leave the # endpoint dark with nothing in the log to say why. Same library calls - # as serve-frank-zealot.sh (the 2026-10-01 law). + # as serve-frank-zealot.sh, so the deployer's twin refuses identically. export GATE_PREFIX=frank-zealot require_exec "$LLM_ZEALOT_RUNTIME" runtime \ "build it: cmake -B build-cuda -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86" \ diff --git a/scripts/derive-protected-experts.py b/scripts/derive-protected-experts.py index 4a3503b..603bda8 100755 --- a/scripts/derive-protected-experts.py +++ b/scripts/derive-protected-experts.py @@ -2,7 +2,7 @@ """derive-protected-experts.py — rewrite the recipe's protected-expert section from a calibration recording. RUNS ON DANKSERVER (venv: numpy). -The law (docs/MOE-W4.md "Protected experts", restated in the recipe): +The selection rule (docs/MOE-W4.md "Protected experts", restated in the recipe): an expert whose share of its layer's down-input energy is at least 3% AND at least three times its share of the routing is kept bf16; the count must be EVEN on each rank's half of the layer (the four-bit GEMM takes the rest diff --git a/scripts/download-frank-archon-swift15.sh b/scripts/download-frank-archon-swift15.sh index 273d4d2..008d70f 100755 --- a/scripts/download-frank-archon-swift15.sh +++ b/scripts/download-frank-archon-swift15.sh @@ -14,10 +14,11 @@ # Fetch uses the vendored scripts/hfget.sh (StillDeadcode's Apache-2.0 # listing/resume downloader, curl+jq only): per-file size checks against # the Hub listing, `-C -` resume, pickle-format duplicates skipped. -# Verify (default) is the AGENTS.md law in three parts: every listed file -# present with the listed size, the shard count matches the weight-map -# index, and every shard's sha256 equals the Hub's LFS oid. Verify needs -# only the public API. +# Verify (default) is three independent agreements: every listed file +# present with the listed size (catches truncation), the shard count +# matching the weight-map index (catches omissions), and every shard's +# sha256 equal to the Hub's LFS oid (catches substituted content). +# Verify needs only the public API. set -euo pipefail REPO="ukisai/Swift1.5-Qwen3.8-Flash-Next" diff --git a/scripts/download-frank-archon.sh b/scripts/download-frank-archon.sh index 0f5ff83..d884cb1 100755 --- a/scripts/download-frank-archon.sh +++ b/scripts/download-frank-archon.sh @@ -12,10 +12,11 @@ # Fetch uses the vendored scripts/hfget.sh (StillDeadcode's Apache-2.0 # listing/resume downloader, curl+jq only): per-file size checks against # the Hub listing, `-C -` resume, pickle-format duplicates skipped. -# Verify (default) is the AGENTS.md law in three parts: every listed file -# present with the listed size, the shard count matches the weight-map -# index, and every shard's sha256 equals the Hub's LFS oid. Verify needs -# only the public API. +# Verify (default) is three independent agreements: every listed file +# present with the listed size (catches truncation), the shard count +# matching the weight-map index (catches omissions), and every shard's +# sha256 equal to the Hub's LFS oid (catches substituted content). +# Verify needs only the public API. set -euo pipefail REPO="Qwen/Qwen3.8-Flash-Next-FP8" diff --git a/scripts/download-frank-dragoon-flashnext.sh b/scripts/download-frank-dragoon-flashnext.sh index 5c096cf..3233a4e 100755 --- a/scripts/download-frank-dragoon-flashnext.sh +++ b/scripts/download-frank-dragoon-flashnext.sh @@ -4,12 +4,15 @@ # .hgn, the REQUIRED lookup table and the optional vision tower from # peonist-ai/halogen-qwen3.8-flash-next (both at the revisions the model # card pins), and the flat tokenizer the two repos share. All public, no -# token — and per the fleet secret law, none would ever touch argv. +# token needed — and a secret would never be passed via argv anyway: the +# command line is world-readable through /proc, so any token placed there +# leaks to every process-listing tool on the host. # -# The fetch law, same as every sibling script: curl -f (an access error -# must not become a "successful" 153-byte artifact), exact byte count from -# the Hub tree API, LFS sha256 verification, --verify-only to re-check an -# existing payload without downloading. +# The fetch rules, uniform across every sibling script: curl -f so an +# access error fails loudly instead of writing the error body as a +# "successful" tiny artifact; the exact byte count from the Hub tree API; +# LFS sha256 verification; --verify-only to re-check an existing payload +# without downloading. # # File manifest (verified against the Hub tree API on 2026-10-06): # qwen38-flash-next-v2-swift15-abliterated.hgn 66,687,678,464 B sha256 @@ -26,7 +29,9 @@ # tokenizer/* @e053f488 — byte-identical to the 27B-era tokenizer # already on disk (tokenizer.json LFS # 0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3); -# fetched fresh rather than symlinked, per the no-symlink law. +# fetched fresh, never symlinked: a snapshot link points into the +# host-side cache directory, which does not exist inside the +# container mount, so the link dangles exactly where it is consumed. set -euo pipefail DEST="${FRANK_DRAGOON_MODEL_DIR:-$HOME/models/halogen-flashnext-v2}" diff --git a/scripts/download-frank-dragoon-halogen.sh b/scripts/download-frank-dragoon-halogen.sh index ad36204..8aa3491 100755 --- a/scripts/download-frank-dragoon-halogen.sh +++ b/scripts/download-frank-dragoon-halogen.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash # download-frank-dragoon-halogen.sh — fetch frank-dragoon's halogen payload: # the qwen3.8-27b .hgn checkpoint and the export tokenizer, from the -# PUBLIC peonist-ai/halogen-qwen3.8-27b repo (no token — and per the fleet -# secret law, none would ever touch argv). +# PUBLIC peonist-ai/halogen-qwen3.8-27b repo (no token — and a token +# would never travel in argv: command lines are world-readable through +# /proc, so a secret placed there leaks to any process-listing tool). # -# The fetch law, same as every sibling script: curl -f (an access error -# must not become a "successful" 153-byte artifact), exact byte count from -# the model card, LFS sha256 verification, --verify-only to re-check an -# existing payload without downloading. +# The fetch rules, uniform across every sibling script: curl -f so an +# access error fails loudly instead of writing the error body as a +# "successful" tiny artifact; the exact byte count from the model card; +# LFS sha256 verification; --verify-only to re-check an existing payload +# without downloading. set -euo pipefail DEST="${FRANK_DRAGOON_MODEL_DIR:-$HOME/models/halogen-qwen3.8-27b}" diff --git a/scripts/frank-archon-expert-activity.py b/scripts/frank-archon-expert-activity.py index d9b7b9d..e363937 100644 --- a/scripts/frank-archon-expert-activity.py +++ b/scripts/frank-archon-expert-activity.py @@ -11,13 +11,13 @@ Tensor extents are DERIVED from consecutive GGUF header offsets — no quant type-size table (those are easy to get subtly wrong; a wrong per-element size silently under-covers each tensor and dumps tail samples into a junk bucket). -Fleet law this instrument established (2026-09-26, 75 s window, 4 mixed -streams, 2.7M expert-tensor samples): per-BLOCK read volume is FLAT -(0.93x-1.08x of mean across all 40 CPU-resident blocks) because MoE top-k -routing fires every block every token — there are no "most active layers" at -`-ot` granularity. The real skew is per-EXPERT (top 10% of (block,expert) -cells carry ~40% of samples), which tensor-level overrides cannot reach. -Any 8 GPU-resident blocks are therefore equivalent; COUNT is the only lever. +What this instrument settles: per-BLOCK expert read volume is FLAT across +all CPU-resident blocks, because MoE top-k routing fires every block on +every token — there are no "most active layers" at `-ot` granularity. The +real skew is per-EXPERT: a small fraction of (block,expert) cells carries +most of the samples, and tensor-level overrides cannot reach that level. +Any fixed number of GPU-resident blocks is therefore equivalent; COUNT is +the only lever. Usage (needs passwordless sudo for perf only; model files must be readable): fp-expert-activity.py [duration_s] # dump + sample + analyze diff --git a/scripts/frank-archon.env b/scripts/frank-archon.env index 3daae65..6b10e89 100644 --- a/scripts/frank-archon.env +++ b/scripts/frank-archon.env @@ -115,15 +115,16 @@ FRANK_ARCHON_TP=2 FRANK_ARCHON_TP_WIRE=wht6 # Decode slots: TWO, so the served window is a CAPACITY CONTRACT — two # concurrent conversations of --max-model-len fit the KV pool (see the KV -# block below). The "seqs 2 cannot boot" law is NOT radiance's: it was the -# tcclaviger vLLM fork's torch.compile shape specialization on -# query_start_loc (ConstraintViolationError, measured 2026-10-02, retired -# with that runtime). Radiance booted seqs 2 at the fp8-native first boot, -# and the 2026-10-06 decode sweep measured seqs 2 and 3 statistically tied -# (30.9-33.1 t/s, inside the +-3% noise floor). Side benefit: 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 EXACT (single-sequence decode and any -# step of <= 25 rows is bit-exact; only busier steps are lossy). +# block below). Slot-count limits observed on other runtimes do NOT +# transfer: a "this slot count cannot boot" refusal is usually that +# runtime's compiler specializing shapes (e.g. a ConstraintViolationError +# from shape specialization on query_start_loc), not a property of the +# engine now running. This engine boots seqs 2, and seqs 2 +# vs 3 decode measurements land inside each other's noise floor. Side +# benefit: 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 EXACT +# (single-sequence decode and any step of <= 25 rows is bit-exact; only +# busier steps are lossy). FRANK_ARCHON_MAX_SEQS=2 # Served window. Native is 262,144; 200,000 is the publisher's production # value (compose) and what models.yml's contextWindow must quote — a longer @@ -203,9 +204,10 @@ FRANK_ARCHON_MAX_BATCHED_TOKENS=2048 # (104 GiB available with the service loaded). FRANK_ARCHON_PREFIX_HOST_MIB=8192 FRANK_ARCHON_PREFIX_DISK_MIB=65536 -# Frank's pin law: the PLE n-gram table (47.7 GiB) serves from pinned host -# RAM the cards read. `ram` REFUSES the start if the memlock limit is too -# small — the unit's LimitMEMLOCK=infinity is the other half of this law. +# The PLE n-gram table (47.7 GiB) serves from pinned host RAM the cards +# read. `ram` REFUSES the start if the memlock limit is too small — a +# named refusal beats a mid-load allocator failure — and the unit's +# LimitMEMLOCK=infinity is the other half of the arrangement. FRANK_ARCHON_NGRAM_PLACEMENT=ram # TTM preflight: the pinned ledger (2 x 44 GiB pools + 47.7 GiB PLE + 8 GiB # prefix host = ~147 GiB) rides TTM's shared pages, capped by ttm's diff --git a/scripts/frank-dragoon.env b/scripts/frank-dragoon.env index c5103f7..7acec62 100644 --- a/scripts/frank-dragoon.env +++ b/scripts/frank-dragoon.env @@ -42,10 +42,11 @@ # form of the engine's default, so the seam test can pin the product. # KV costs ~28 KiB/position (FLAGS.md) => 524,288 x 28 KiB = 14.0 GiB. # HALOGEN_KV_SLOTS=2 restores the w4b-era shape on this box: the MTP -# drafter is NOT slot-locked on this engine (batching and speculation -# coexist — DFlash-era slots>1 law does NOT carry over; the card's own -# quickstart ships 2 slots WITH the head), so the second slot is -# concurrent capacity, not a speculation tax. +# drafter is NOT slot-locked on this engine. A "slots > 1 disables the +# drafter" restriction is a property of the engine generation that +# imposed it, not of speculation itself — batching and speculation +# coexist here, and the card's own quickstart ships 2 slots WITH the +# head — so the second slot is concurrent capacity, not a speculation tax. # Memory ledger against the box's 121.2 GiB unified (the n-gram table is # mmap'd page cache beside the checkpoint, not a pinned charge — the # card's own quickstart arithmetic does not bill it): @@ -88,9 +89,10 @@ # before. Kernel needs CONFIG_HSA_AMD_SVM (CachyOS ships it) and # amdgpu.noretry=0 (check at deploy). -# Image: digest-pinned (fetch law: tag pulls silently re-point; the digest -# is what was qualified). 0.16.2 = the version the model card names as its -# quickstart pin; RepoDigest captured at pull on 2026-10-06. +# Image: digest-pinned, not tag-pinned — a tag can be re-pointed to a +# build that was never qualified here, while the digest pins exactly the +# build that was tested. 0.16.2 = the version the model card names as its +# quickstart pin; the RepoDigest was captured at a pull of that tag. FRANK_DRAGOON_IMAGE=ghcr.io/peonist-ai/halogen-flash-server@sha256:0c61bf84ac22308a53f5d1ca6b86806702d7039e5ebc51cae4c66621b92fe04a # Container identity: kept from the 27B era (unit ExecStop names it). FRANK_DRAGOON_CONTAINER=frank-dragoon-halogen diff --git a/scripts/frank-zealot.env b/scripts/frank-zealot.env index 32e22db..b68f2fd 100644 --- a/scripts/frank-zealot.env +++ b/scripts/frank-zealot.env @@ -16,11 +16,11 @@ # custom-quant lock-in like the ROCmFPX era). NO nextn/mtp tensors exist, so # in-file MTP is impossible here; SPEC_TYPE=none is forced by the payload. # -# KV rate law: 8 full layers × 4 KV heads × 256 dim = 8,192 elements per -# token per cache. f16 K + f16 V = 32,768 B/token = 32 KiB/token, so the -# 401,408-token pool is 12.25 GiB. The 24 DeltaNet layers cost a fixed -# recurrent state per SLOT (not per token) — that is why two 200k slots fit -# where the 26B MoE fit only one. +# The KV rate follows from the architecture: 8 full layers × 4 KV heads × +# 256 dim = 8,192 elements per token per cache. f16 K + f16 V = 32,768 +# B/token = 32 KiB/token, so the 401,408-token pool is 12.25 GiB. The 24 +# DeltaNet layers cost a fixed recurrent state per SLOT (not per token) — +# that is why two 200k slots fit where the 26B MoE fit only one. # # Feasibility comes from the predecessor's MEASURED boots, not a formula: # MiMo-V2.6-Distill-Qwen-9B (identical qwen35 geometry, 7,793,710,624 B) at diff --git a/scripts/frank-zealot.service b/scripts/frank-zealot.service index 5e3ca96..01d73f3 100644 --- a/scripts/frank-zealot.service +++ b/scripts/frank-zealot.service @@ -12,10 +12,11 @@ Group=dank LimitMEMLOCK=infinity Environment="FRANK_ZEALOT_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-zealot.env" ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-zealot.sh -# Restart=always, the fleet law for LLM units: exit 0 is a CLEAN stop to -# systemd, so on-failure leaves the endpoint dark (and the unit reporting -# success) until the next boot. frank-zealot is the fleet's compaction hub — -# every endpoint's compaction dies when it does. +# Restart=always is the right policy for a serving daemon: a process that +# exits on its own — exit 0 — is a CLEAN stop to systemd, so on-failure +# never fires and the endpoint stays dark (with the unit reporting +# success) until the next boot. frank-zealot is the fleet's compaction +# hub — every endpoint's compaction dies when it does. Restart=always RestartSec=10 diff --git a/scripts/lib/llm-gates.sh b/scripts/lib/llm-gates.sh index ed7827c..84c9d2b 100755 --- a/scripts/lib/llm-gates.sh +++ b/scripts/lib/llm-gates.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash # llm-gates.sh — the artifact gates every LLM endpoint runs before it starts. # -# Why this file exists: the fleet law from the 2026-10-01 crash-loop is that -# a serve script and the deployer must refuse the SAME missing/truncated -# artifact, the SAME way. When the two gates were hand-written twins they -# drifted mid-cutover — green deploy, red unit, every time the payload moved. +# Why this file exists: every LLM endpoint gates its payload twice — the +# serve script at start, the deployer before dispatch. Two hand-written +# copies of the same checks drift apart in wording and strictness, and the +# drift surfaces as deploy-time disagreement: one side passes, the other +# refuses, and the failure lands in production instead of in the gate. # Both sides source this library, so the refusal is one text and one rule. # # Contract (public interface): @@ -27,9 +28,10 @@ # disagree. The deployer's old "halogen vision tower" and the serve script's # "vision-tower" were one gate in two vocabularies that no grep could join. -# The size law is equality, not a floor, for `require_bytes`: a hub fetch -# that truncates is the fleet's standing hazard (curl exits 0 on 401/403 and -# writes the error body as the artifact), and the declared byte count is a +# Size checks use equality, not a floor, for `require_bytes`: a fetch that +# truncates looks like a success (an HTTP error exit is often swallowed, and +# the error body is written in the artifact's place), so "exists and is big +# enough" is not proof of the right artifact. The declared byte count is a # contract in both directions — a bigger file is a DIFFERENT artifact. # GATE_PREFIX names the endpoint in every refusal (e.g. frank-dragoon). The @@ -38,7 +40,7 @@ # shellcheck disable=SC2034 : "${GATE_PREFIX:=gate}" -# Two bash laws make the `return 1` at every call site load-bearing: +# Two bash behaviors make the `return 1` at every call site load-bearing: # * a function whose body ends inside an `if` returns the CONDITION's # status (true = 0), so a gate that ends on its own refusal prints the # refusal and reports SUCCESS; @@ -100,10 +102,10 @@ require_min_mib() { } # require_dir_files — DIR must be a directory and every named member a -# regular file (the flat-tokenizer law: snapshot symlinks dangle in the -# container mount even when the directory itself is real. A symlinked -# DIRECTORY is refused too: a HF cache snapshot can link the directory -# itself, and a link dangles inside the container mount. +# regular file, never a symlink: snapshot layouts link files into place, and +# a link dangles inside the container mount even when the directory itself +# is real. A symlinked DIRECTORY is refused too — the same layout can link +# the directory itself, and the link dangles identically. require_dir_files() { local dir="$1" label="$2" hint="$3" shift 3 diff --git a/scripts/patches/glm53-dflash-01-aux-hidden-states.patch b/scripts/patches/glm53-dflash-01-aux-hidden-states.patch index 27cafc2..e67e0e2 100644 --- a/scripts/patches/glm53-dflash-01-aux-hidden-states.patch +++ b/scripts/patches/glm53-dflash-01-aux-hidden-states.patch @@ -29,10 +29,11 @@ # its base SupportsEagleBase (has_own_lm_head, has_own_embed_tokens) -- and # getattr_static()s each one off the instance, breaking on the first miss. # So BOTH wrappers need all three attributes. -# * MRO law: INHERITING SupportsEagle3 as a base dies at class creation with -# "Cannot create a consistent method resolution order (MRO) for bases ..." -# (GLM's wrappers already carry four/five Protocol-derived bases). The -# attribute route avoids the MRO entirely. Also proven by simulation: +# * Why not inheritance: adding SupportsEagle3 as a BASE class dies at +# class creation with "Cannot create a consistent method resolution +# order (MRO) for bases ..." (GLM's wrappers already carry four/five +# Protocol-derived bases; the base ordering cannot take another). The +# attribute route avoids the MRO entirely; simulation confirms that # the four-base ForCausalLM ordering cannot take SupportsEagle3 in ANY # position. # * Method placement: without inheriting, the Protocol's default diff --git a/scripts/probe-frank-archon-kv.py b/scripts/probe-frank-archon-kv.py index 0351309..8978153 100755 --- a/scripts/probe-frank-archon-kv.py +++ b/scripts/probe-frank-archon-kv.py @@ -9,7 +9,9 @@ No scratch container, no mask: the service under test is the service. Three probes, in increasing cost: A decode at depth — one ~51k-token chat completion, 512 tokens, greedy. Reports the engine's own `timings` (prefill, decode, draft acceptance). - The bench law applies: >=128 generated tokens, greedy, real text. + Method: >=128 generated tokens (below that, first-token latency + dominates the decode rate), greedy (sampling noise is unattributable), + real text (synthetic text inflates draft acceptance). B the capacity contract — TWO concurrent chats of ~197k prompt tokens (~394k tokens resident) each carrying a unique marker at its head. Both must answer their own marker. This is what a 2 x 200,000 endpoint owes: @@ -17,11 +19,11 @@ Three probes, in increasing cost: C prefix-cache restore — probe A's cold prompt again, so the disk tier has to give the blocks back (cache_n > 0, TTFT collapses). -PROBE LAW (learned the hard way, twice): radiance routes the thinking block to +PROBE PITFALL: radiance routes the thinking block to `reasoning_content`, so a chat request whose budget is spent inside `` returns EMPTY `content` with `finish_reason: stop`. A probe that -reads `content` alone scores 0/2 on a healthy server. Every request here sets -chat_template_kwargs.enable_thinking=false and reads both fields. +reads `content` alone scores 0/2 on a healthy server. Every request here +sets chat_template_kwargs.enable_thinking=false and reads both fields. A/B method for a shape change (no scratch container): write a variant env file on the host, point a transient systemd drop-in at it diff --git a/scripts/recipes/qwen4exp-swift15-w4dn8-i8-hc8m.recipe b/scripts/recipes/qwen4exp-swift15-w4dn8-i8-hc8m.recipe index d3d6a2a..dbded73 100644 --- a/scripts/recipes/qwen4exp-swift15-w4dn8-i8-hc8m.recipe +++ b/scripts/recipes/qwen4exp-swift15-w4dn8-i8-hc8m.recipe @@ -1,10 +1,12 @@ # qwen4exp-swift15-w4dn8-i8-hc8m.recipe -- the Swift 1.5 finetune of Qwen3.8-Flash-Next # (ukisai/Swift1.5-Qwen3.8-Flash-Next, BF16 source, 335.3 GiB, key set identical to the official # base) as a radiance container for frank-archon: EVERY routed expert -- gate_up AND down -- -# through GPTQ into the w4nl64a8h table. The plugin's one-table-one-encoding law -# ("one table of experts takes one encoding, and only a whole expert kept plain bf16 is served -# beside it", qwen4exp_fp8 rad_arch_declare) forbids the original split tier (down block-fp8 + -# gate_up w4): the served kernel is w4nl64a8h for the whole expert set, bf16 experts beside it. +# through GPTQ into the w4nl64a8h table. The converter declares ONE +# encoding per expert table (qwen4exp_fp8 rad_arch_declare): the served +# kernel covers the whole expert set, and the only legal mixed layout is a +# WHOLE expert kept plain bf16 beside it. That rules out the original +# split tier (down block-fp8 + gate_up w4), which asks one table for two +# encodings. # The rest is qwen4exp-w5dn64-i8-hc8m.recipe verbatim: int8 trunk linears, block-fp8 hc mixers # and MTP fc, the fixed-scale n-gram table, the 2-bit draft head. # @@ -25,11 +27,14 @@ # The protected experts, cast to bf16 ahead of both expert rules. THE NAMES BELOW ARE THE BASE # CHECKPOINT'S (from its 1M-token recording, qwen4exp-w5dn64-i8-hc8m.recipe) -- a warm start only. -# The Swift 1.5 finetune's routing distribution is not assumed equal: the derivation step rewrites -# this section from Swift's OWN calibration recording before this recipe is ever run, by the base's -# law -- an expert whose share of its layer's down-input energy is at least 3% and at least three -# times its share of the routing, an even count on each rank's half of the layer (the four-bit GEMM -# takes the rest as two equal tables by parity) by adding that half's next most energetic expert. +# The Swift 1.5 finetune's routing distribution is not assumed equal: the +# derivation step rewrites this section from Swift's OWN calibration +# recording before this recipe is ever run. The selection rule is the base +# checkpoint's: an expert whose share of its layer's down-input energy is +# at least 3% AND at least three times its share of the routing; an even +# count on each rank's half of the layer (the four-bit GEMM takes the rest +# as two equal tables by parity), padded with that half's next most +# energetic expert. # PROTECTED-BEGIN (rewritten from Swift's OWN grams by derive-protected-experts.py) blk.1.ffn_*_exps.271.weight cast dtype=bf16 blk.1.ffn_*_exps.355.weight cast dtype=bf16 diff --git a/scripts/serve-frank-archon.sh b/scripts/serve-frank-archon.sh index 98b87f5..2eca3a6 100755 --- a/scripts/serve-frank-archon.sh +++ b/scripts/serve-frank-archon.sh @@ -28,8 +28,9 @@ 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). +# deployer gate the same payload, and two hand-written copies of the same +# checks drift apart until one side passes what the other refuses. One +# library, one refusal text, one rule. export GATE_PREFIX=frank-archon # shellcheck disable=SC1091 source "$SCRIPT_DIR/lib/llm-gates.sh" diff --git a/scripts/serve-frank-dragoon.sh b/scripts/serve-frank-dragoon.sh index 9dabc27..9948d3d 100755 --- a/scripts/serve-frank-dragoon.sh +++ b/scripts/serve-frank-dragoon.sh @@ -72,9 +72,10 @@ set +a require_image "$FRANK_DRAGOON_IMAGE" \ "pull it: podman pull docker://$FRANK_DRAGOON_IMAGE" || exit 1 -# Size-exact gates on all three payload files: a truncated curl exits 0 on -# some failures and a partial .hgn loads far enough to look alive, so the -# byte counts from the Hub tree API are the contract (fleet fetch law). +# Size-exact gates on all three payload files: a truncated download can +# exit 0, and a partial .hgn loads far enough to look alive, so existence +# proves nothing. The byte count published by the Hub tree API is the +# contract: any other size is a different (broken) artifact. require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_CHECKPOINT" \ checkpoint "$FRANK_DRAGOON_CHECKPOINT_BYTES" \ "fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1 diff --git a/scripts/serve-frank-zealot.sh b/scripts/serve-frank-zealot.sh index a645172..ae040a7 100755 --- a/scripts/serve-frank-zealot.sh +++ b/scripts/serve-frank-zealot.sh @@ -29,9 +29,10 @@ 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 payload with the same words -# (scripts/lib/llm-gates.sh; the fleet law from the 2026-10-01 crash-loop). +# The artifact gates are the shared library (scripts/lib/llm-gates.sh): the +# serve script and the deployer gate the same payload, and two hand-written +# copies of the same checks drift apart until one side passes what the +# other refuses. export GATE_PREFIX=frank-zealot # shellcheck disable=SC1091 source "$SCRIPT_DIR/lib/llm-gates.sh" @@ -69,9 +70,9 @@ fi # FP8 class; no true fp8 KV dtype exists in this build). : "${CACHE_TYPE_K:=f16}" : "${CACHE_TYPE_V:=f16}" -# Speculative decoding is OFF: this Q6_K file carries NO nextn/mtp tensors, so -# there is no in-file head (the law: MTP is a property of the file, not of the -# flags). SPEC_TYPE=none strips BOTH speculative flags (flags move as a set); +# Speculative decoding is OFF: this Q6_K file carries NO nextn/mtp tensors, +# so there is no in-file head — a speculative head is a property of the +# model file, not of the flags; no flag creates tensors that are absent. # naming a drafter requires SPEC_DRAFT_FILE alongside SPEC_TYPE — the Gemma4 # assistant on disk is arch gemma4-assistant and cannot drafter-match qwen35. : "${SPEC_TYPE:=none}" @@ -89,7 +90,7 @@ fi # Fail loudly rather than half-loading: a missing file is a download that # has not finished, and Restart=always would turn it into a crash loop that # reads like a runtime fault. The refusal text is the shared gate library's, -# so the deployer's twin refuses identically (the 2026-10-01 crash-loop law). +# so the deployer's twin refuses identically. require_file "$LLAMA_BIN" runtime \ "build it: scripts/build-llama-master-cuda.sh" || exit 1 require_file "$MODEL_FILE" gguf \ diff --git a/scripts/service-map.py b/scripts/service-map.py index 3f092db..ec852ec 100755 --- a/scripts/service-map.py +++ b/scripts/service-map.py @@ -18,11 +18,12 @@ facts written by hand in three config files). They now all read render pihole-hosts the Pi-hole addn-hosts file check [NAME...] integrity + drift gates (exit 1 on drift) -Two laws the engine enforces structurally: +Two rules the engine enforces structurally: -* A path selects only services that RUN on the planned target. The old planner - matched paths host-blind and filtered at the end, so a frankbook-pro file - could plan a dankserver service (the stale *arr entries made that reachable). +* A path selects only services that RUN on the planned target. Path + matching that is host-blind (match first, filter later) lets a file on + one host plan a service on another; selection is scoped to the host the + path belongs to. * The netns family is DERIVED, never declared: a unit file that says `Network=gluetun.container` rides that tunnel, so a gluetun change selects every sharer. The unit files are the source of that coupling. @@ -499,8 +500,9 @@ def check_config(inv: Inventory) -> list[str]: # --- node-exporter quadlet template (rank 6) -------------------------------- -# The two node-exporter units were a MIRROR pair kept in step by a comment. -# The template is now the law; `check` drift-gates both tracked copies. +# Parallel per-host copies kept in step by a comment drift apart; the +# template is the single source, and `check` drift-gates every tracked +# rendering against it. NODE_EXPORTER_TEMPLATE = """\ # Quadlet unit: node-exporter (rootless, boot-persistent) — RENDERED ARTIFACT. # Regenerate: python3 scripts/service-map.py render node-exporter-unit --host {host} diff --git a/scripts/stack-qwen38-official-fp8.py b/scripts/stack-qwen38-official-fp8.py index 106e983..a1b51b6 100755 --- a/scripts/stack-qwen38-official-fp8.py +++ b/scripts/stack-qwen38-official-fp8.py @@ -293,12 +293,12 @@ def main(): ddt, dsh = meta[f"{pre0}.mlp.experts.0.down_proj.weight"][3:5] gsd, gssh = meta[f"{pre0}.mlp.experts.0.gate_proj.weight_scale_inv"][3:5] dsd, dssh = meta[f"{pre0}.mlp.experts.0.down_proj.weight_scale_inv"][3:5] - scale_law = (gdt == F8 and ddt == F8 and gsd == BF16 and dsd == BF16 + scale_contract = (gdt == F8 and ddt == F8 and gsd == BF16 and dsd == BF16 and list(gssh) == [(gsh[0] + 127) // 128, (gsh[1] + 127) // 128] and list(dssh) == [(dsh[0] + 127) // 128, (dsh[1] + 127) // 128]) - if not scale_law: + if not scale_contract: log(f"ERROR: unexpected expert shapes: gate {gdt}{gsh} scale {gsd}{gssh} " - f"down {ddt}{dsh} scale {dsd}{dssh} (scale law 128x128)") + f"down {ddt}{dsh} scale {dsd}{dssh} (fp8 block scales cover 128x128 tiles)") return 2 shapes = {"ffn2_h": (gsh[0] * 2, gsh[1]), "ffn": (dsh[1],), "gs": tuple(gssh), "ds": tuple(dssh)} diff --git a/shared/services.yml b/shared/services.yml index 1a7c78b..6da0da8 100644 --- a/shared/services.yml +++ b/shared/services.yml @@ -93,8 +93,8 @@ services: kind: quadlet unit_dir: dankclaw/searxng/quadlet units: [searxng-valkey] - # The app-level law, not a network one: deploy_searxng_valkey restarts - # SearXNG after its cache, so the cache's files select both. + # An app-level dependency, not a network one: the deploy handler + # restarts SearXNG after its cache, so the cache's files select both. dependents: [searxng] tests: [tests/searxng_quadlet.bats] probe: 127.0.0.1:6379 diff --git a/tests/frank_archon_unit.bats b/tests/frank_archon_unit.bats index 7de937a..4cfb5eb 100755 --- a/tests/frank_archon_unit.bats +++ b/tests/frank_archon_unit.bats @@ -13,10 +13,10 @@ # 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 +# chat-template override mount, the self-healing contract (`podman run` +# returns before the engine is up, so the serve script must stay attached +# 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 @@ -253,11 +253,11 @@ serve_run() { [ "$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. + # under the 25-row bound at which the wht6 wire stays bit-exact. A + # "this slot count cannot boot" refusal is per-runtime (compiler shape + # specialization rejects counts the engine running here accepts), so it + # must be re-tested, not inherited; measurements here put seqs 2 and 3 + # inside each other's 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" @@ -265,8 +265,8 @@ serve_run() { # 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. + # only bf16|fp8 — no per-tensor K/V split), the PLE n-gram table rides + # pinned host RAM per the placement knob. 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" diff --git a/tests/frank_dragoon_unit.bats b/tests/frank_dragoon_unit.bats index 41943e1..fdf2d33 100755 --- a/tests/frank_dragoon_unit.bats +++ b/tests/frank_dragoon_unit.bats @@ -10,11 +10,11 @@ # 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. +# the container). A serve gate and a deployer gate that disagree let a +# deploy pass what the unit would refuse, so both sides are pinned here — +# plus the one engine behavior 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 @@ -117,10 +117,11 @@ good_env() { @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. + # off for whole requests after low acceptance and HALVES decode. A + # "slots > 1 disables drafting" restriction belongs to the engine + # generation that imposed it — 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 @@ -186,7 +187,7 @@ good_env() { @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). + # count is the contract, any other size is a broken artifact. podman_stub good_env run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \ @@ -287,16 +288,16 @@ $(cat "$TEST_TMP/serve.log")" ! grep -q 'PODMAN wait' "$TEST_TMP/serve.log" } -@test "the shipped env keeps the harness fit law inside the ceiling" { +@test "the shipped env keeps the harness fit margin inside the ceiling" { # 262144 (the checkpoint's native window) is the per-slot hard ceiling # (a longer prompt is a 400 naming the limit, never a silent # truncation). The harness clamps max_tokens = min(45056, 217088 − est # − 4096), compacts at 85 % of its declared 217088 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 ~146k est was a guaranteed - # 400 before compaction could fire at 85 %). The 45k gap between the - # harness window and the engine ceiling must absorb that error at + # traffic — so a harness window equal to the engine ceiling would make + # every long request a guaranteed 400 before compaction could fire. + # The 45k gap between the harness window and the engine ceiling must + # absorb that error at # both monotone maxima of est + budget (est ≤ 217088 − 4096): # full budget at the compaction threshold: 1.03*184524 + 45056 # clamped budget at the clamp's zero point: 1.03*212992 + 0 diff --git a/tests/llm_deploy_gates.bats b/tests/llm_deploy_gates.bats index 75426f1..d3b74bd 100755 --- a/tests/llm_deploy_gates.bats +++ b/tests/llm_deploy_gates.bats @@ -35,8 +35,8 @@ teardown() { sandbox_teardown; } } @test "the dragoon handler refuses a symlinked tokenizer file" { - # The flat-tokenizer law: a HuggingFace cache snapshot links its - # members, and the link dangles inside the container mount. + # Snapshot layouts link their members into place, and the link dangles + # inside the container mount — only regular files are servable. rm "$STAGE/models/tokenizer/vocab.json" ln -s /nowhere/vocab.json "$STAGE/models/tokenizer/vocab.json" run_handler dankclaw frank-dragoon diff --git a/tests/llm_gates.bats b/tests/llm_gates.bats index 66fd530..fb998f7 100755 --- a/tests/llm_gates.bats +++ b/tests/llm_gates.bats @@ -1,11 +1,11 @@ #!/usr/bin/env bats # Seam: the shared artifact gates every LLM endpoint runs before it starts. # -# The fleet law these encode: a serve script and the deployer must refuse the -# SAME missing/truncated artifact, and they must refuse it the same way, -# because the 2026-10-01 crash-loop was exactly those two gates disagreeing -# mid-cutover (green deploy, red unit). The library is the one place the -# refusal lives; both callers source it. +# The rule these encode: a serve script and the deployer must refuse the +# SAME missing/truncated artifact, and they must refuse it the same way. +# Two hand-written copies of the same gates drift apart until a deploy +# passes what the unit would refuse; the library is the one place the +# refusal lives, and both callers source it. # # Everything here is observable: arguments in, exit status and stderr out. # Nothing inspects the library's internals. diff --git a/tests/llm_units.bats b/tests/llm_units.bats index 758045c..53d242a 100644 --- a/tests/llm_units.bats +++ b/tests/llm_units.bats @@ -85,9 +85,9 @@ unit_repo_path() { # Restart=always, not on-failure. Every endpoint here is a container # runner (podman run) or a server whose front end can exit 0 on a # control-plane event; systemd treats exit 0 as a CLEAN stop and - # on-failure leaves the endpoint dark until the next boot, with the unit - # reporting success. frank-archon learned this on 2026-10-04; the fleet - # law is that no LLM unit may ship the weaker policy. + # on-failure leaves the endpoint dark until the next boot, with the + # unit reporting success. No LLM unit may ship the weaker policy: a + # single endpoint on on-failure is a daemon that goes dark silently. local unit while IFS= read -r unit; do grep -qx 'Restart=always' "$unit" || { diff --git a/tests/service_inventory.bats b/tests/service_inventory.bats index ebeb2ae..e8209d9 100755 --- a/tests/service_inventory.bats +++ b/tests/service_inventory.bats @@ -125,8 +125,8 @@ PYEOF } @test "inventory: the node-exporter unit is a per-host rendered template" { - # Rank 6: the two node-exporter quadlets were a MIRROR pair kept in - # step by a comment. The render is now the law on both hosts. + # Both quadlets are rendered artifacts: the render — not a hand-edited + # mirror kept in step by a comment — defines the unit on every host. for host in dankclaw dankserver; do run bash -c "cd '$REPO_ROOT' && python3 scripts/service-map.py render node-exporter-unit --host $host | \ diff -u $host/observability/quadlet/node-exporter.container -"