feat(qwen38): the BF16 reference instrument - build it, drain once, record logits
Two tools the KLD gate depends on. build-qwen38-bf16-rad.sh converts the checkpoint to a BF16 .rad with NO recipe line, so every tensor stays bf16 - the reference must not carry the quantization it is there to measure. It gates before writing 335 GiB: the checkpoint present and fully verified by the fetcher's own verifier, the peak-disk bill (350,000 MiB: the checkpoint's 343,323 plus margin, because the .rad is built beside it), an existing output retired to ~/.AGENTTRASH by --force instead of deleted, and --plan-only before the real convert. --max-ctx 32768 is a declare-time check, not a weight change: dropped, the resolver is handed the checkpoint's 262,144 window, which at fp8 cache is 258.8 GiB on a 192 GiB machine. run-qwen38-bf16.sh serves that container in three modes - kldref (record the reference logits), score (record an arm's logits against that reference), calib (open the Gram tap for the imatrix). It drains frank-archon and restores it on every exit path, and the two logits modes ride ONE step size (FRANK_QWEN38_MNBT, default 2048) and ONE window (FRANK_QWEN38_MAX_MODEL_LEN, default 32768) so a reference and an arm cannot be measured at different shapes: that is the whole validity of the KL number. Four slots over 2,048 matches frank-archon.env's operating point (MAX_SEQS=4, MAX_BATCHED_TOKENS=2048); the smaller host pool (12,288 vs 49,152) buys residency, which moves speed, not logits. calib keeps 65,536 - decode-bound serving, never the gate. kldref refuses before the drain when the corpus's longest record cannot fit the declared window (limit = window x 3 chars, the pessimistic tokenizer bound; the measured corpus is 38,263 chars, 2.6x inside 32,768). It runs on --dry-run too, so the check is visible before a 20-minute drain is spent. 12 seam tests, each red-proofed by neutralising the gate it pins.
This commit is contained in:
1 parent
5fe04e569f
commit
a7329d190f
3 files changed
+886
No files matched your search
Executable
+337
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env bats
|
||||
# Seam: the BF16 REFERENCE container of the Qwen3.8-Flash-Next five-bit
|
||||
# campaign (feat/qwen38-w5nl64) — scripts/build-qwen38-bf16-rad.sh and
|
||||
# scripts/run-qwen38-bf16.sh.
|
||||
#
|
||||
# The reference is what every later stage is judged or derived from (the KLD
|
||||
# record scores the arms, the Grams derive the protected experts), so the
|
||||
# deliverable here is the set of refusals: a half checkpoint, a second convert,
|
||||
# a volume that cannot hold the convert, an engine-refused plan, and any run —
|
||||
# including a dry one — that would take the GPUs from frank-archon. Each test
|
||||
# drives the script's own exit status and printed argv. Offline: no engine, no
|
||||
# GPU, no container, no systemctl, no network. Path knobs aim at
|
||||
# $BATS_TEST_TMPDIR.
|
||||
#
|
||||
# The third mode, score, carries the campaign's comparability law: its engine
|
||||
# argv must equal kldref's except for --model and the --kld-* pair, or the KL
|
||||
# numbers of the FP8-native anchor, the served payload and the arms are not one
|
||||
# measurement series. The seam proves that by diffing the two dry runs.
|
||||
|
||||
# The 1.8.1 law asserted in two places below: --vram-kv-mib was REMOVED at
|
||||
# 1.3.0, so naming it is a boot refusal — the reference serve must carry the
|
||||
# budget-resolver knobs (--host-pool-mib / --gpu-headroom-mib) instead.
|
||||
|
||||
load helpers
|
||||
|
||||
BUILD="$REPO_ROOT/scripts/build-qwen38-bf16-rad.sh"
|
||||
RUNNER="$REPO_ROOT/scripts/run-qwen38-bf16.sh"
|
||||
|
||||
# The reference shape, spelled exactly as run-qwen38-bf16.sh must pass it: this
|
||||
# is the contract the recorded reference is measured at, so a silent edit here
|
||||
# is a silent edit to every KLD number in the campaign.
|
||||
SHAPE="--tp 2 --placement expert_tiered --weights-disk-tier --host-pool-mib 12288 --gpu-headroom-mib 768 --deterministic --num-speculative-tokens 0 --kv-cache-dtype fp8 --max-model-len 32768 --ngram-placement ram --override-chat-template /models/tpl.jinja"
|
||||
|
||||
# oneline — both dry runs print one shell-quoted argument a line; joining them
|
||||
# lets an option and its value be asserted as the pair they are.
|
||||
oneline() { local s="${output//$'\n'/ }"; printf '%s' "$s"; }
|
||||
|
||||
# engine_argv — the arguments AFTER the image line of a dry run, with --model
|
||||
# and every --kld-* flag and its value removed: what is left is the measuring
|
||||
# shape, and it must be identical between kldref and score.
|
||||
engine_argv() {
|
||||
local seen=0 skip=0 a
|
||||
while IFS= read -r a; do
|
||||
if [ "$seen" = 0 ]; then
|
||||
[[ "$a" == *"radiance@sha256:"* ]] && seen=1
|
||||
continue
|
||||
fi
|
||||
case "$a" in
|
||||
--model|--kld-record|--kld-corpus|--kld-ref|--kld-out) skip=1; continue ;;
|
||||
esac
|
||||
if [ "$skip" = 1 ]; then skip=0; continue; fi
|
||||
printf '%s\n' "$a"
|
||||
done <<< "$1"
|
||||
}
|
||||
|
||||
# flag_value FLAG — the value a dry run printed for FLAG (the argv is one
|
||||
# argument a line, so the value is the line after it). Lets a test ask what
|
||||
# number the script chose instead of re-declaring the literal it guards.
|
||||
flag_value() {
|
||||
local want="$1" prev="" a
|
||||
while IFS= read -r a; do
|
||||
if [ "$prev" = "$want" ]; then printf '%s' "$a"; return 0; fi
|
||||
prev="$a"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
@test "the build dry run prints the rad-convert argv for the checkpoint and the bf16 output" {
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
run env FRANK_QWEN38_MODELS="$M" bash "$BUILD" --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"rad-convert"* ]]
|
||||
# the argv is the container's view: the models dir rides the /models mount
|
||||
[[ "$output" == *"$M:/models"* ]]
|
||||
flat="$(oneline)"
|
||||
[[ "$flat" == *"/models/Qwen3.8-Flash-Next -o /models/rad/qwen38-bf16.rad"* ]]
|
||||
# a reference is the checkpoint re-headed, not re-encoded, and it declares a
|
||||
# window the long KLD corpus can be read inside of
|
||||
[[ "$output" != *"--recipe"* ]]
|
||||
[[ "$output" != *"--reuse"* ]]
|
||||
[[ "$flat" == *"--max-ctx 32768"* ]]
|
||||
# and a dry run looked at nothing: the fixture dirs were never created
|
||||
[ ! -e "$M" ]
|
||||
}
|
||||
|
||||
@test "the build refuses a checkpoint that is not on disk and names the fetch command" {
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
run env FRANK_QWEN38_MODELS="$M" bash "$BUILD"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"$M/Qwen3.8-Flash-Next"* ]]
|
||||
[[ "$output" == *"scripts/download-qwen38-bf16.sh --fetch"* ]]
|
||||
[ ! -e "$M/rad/qwen38-bf16.rad" ]
|
||||
}
|
||||
|
||||
@test "the build refuses an existing reference container and names --force as the way past it" {
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
mkdir -p "$M/Qwen3.8-Flash-Next" "$M/rad"
|
||||
echo "not a container" > "$M/rad/qwen38-bf16.rad"
|
||||
run env FRANK_QWEN38_MODELS="$M" QWEN38_MIN_FREE_MIB=1 \
|
||||
QWEN38_TRASH="$BATS_TEST_TMPDIR/trash" bash "$BUILD"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"$M/rad/qwen38-bf16.rad"* ]]
|
||||
[[ "$output" == *"--force"* ]]
|
||||
# refusing retires nothing: the trash law means the file survives a refusal
|
||||
[ -f "$M/rad/qwen38-bf16.rad" ]
|
||||
[ ! -e "$BATS_TEST_TMPDIR/trash" ]
|
||||
}
|
||||
|
||||
@test "the build refuses when the output filesystem cannot hold a second 335 GiB" {
|
||||
# The campaign's peak-disk law: 343,323 MiB of container written next to
|
||||
# 343,323 MiB of checkpoint. The wanted figure is set above whatever this
|
||||
# box really has, which is what the gate reads.
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
mkdir -p "$M/Qwen3.8-Flash-Next" "$M/rad"
|
||||
run env FRANK_QWEN38_MODELS="$M" QWEN38_MIN_FREE_MIB=999999999 bash "$BUILD"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"REFUSED"* ]]
|
||||
[[ "$output" =~ [0-9]+" MiB free" ]]
|
||||
[[ "$output" == *"999999999 MiB wanted"* ]]
|
||||
# the disk gate is the cheap one: it refuses long before the Hub sweep runs
|
||||
[[ "$output" != *"VERIFY_OK"* ]]
|
||||
[ ! -e "$M/rad/qwen38-bf16.rad" ]
|
||||
}
|
||||
|
||||
@test "the reference serve rejects an unknown mode and names every valid mode" {
|
||||
# `probe` is the swift15 era's third mode; it is retired and must not boot
|
||||
# a container here either.
|
||||
run bash "$RUNNER" probe
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"probe"* ]]
|
||||
[[ "$output" == *"kldref"* ]]
|
||||
[[ "$output" == *"calib"* ]]
|
||||
[[ "$output" == *"score"* ]]
|
||||
[[ "$output" != *"podman"* ]]
|
||||
run bash "$RUNNER"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"kldref"* ]]
|
||||
[[ "$output" == *"calib"* ]]
|
||||
[[ "$output" == *"score"* ]]
|
||||
# score without its two positionals is the same usage refusal
|
||||
run bash "$RUNNER" score
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"score RAD OUT"* ]]
|
||||
}
|
||||
|
||||
@test "the kldref dry run carries the record flags at the reference shape and no --vram-kv-mib" {
|
||||
run bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
flat="$(oneline)"
|
||||
[[ "$flat" == *"--kld-record /data/kld-ref"* ]]
|
||||
[[ "$flat" == *"--kld-corpus /data/kld.jsonl"* ]]
|
||||
# the corpus arrives read-only, the record dir writable
|
||||
[[ "$flat" == *":/data/kld.jsonl:ro"* ]]
|
||||
[[ "$flat" == *":/data/kld-ref "* ]]
|
||||
[[ "$flat" == *"$SHAPE"* ]]
|
||||
# 1.8.1: the flag does not exist — naming it is a boot refusal
|
||||
[[ "$output" != *"--vram-kv-mib"* ]]
|
||||
}
|
||||
|
||||
@test "the calib dry run opens the tap at 12M tokens and serves on 7770" {
|
||||
run bash "$RUNNER" calib --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
flat="$(oneline)"
|
||||
[[ "$flat" == *"-e RADIANCE_CALIB_TOKENS=12000000"* ]]
|
||||
[[ "$flat" == *"-e RADIANCE_CALIB_DIR=/data/calib"* ]]
|
||||
[[ "$flat" == *"--port 7770"* ]]
|
||||
[[ "$flat" == *":/data/calib "* ]]
|
||||
# calib serves; it does not record
|
||||
[[ "$output" != *"--kld-record"* ]]
|
||||
}
|
||||
|
||||
@test "a dry run prints argv only: no drain, no container, nothing written" {
|
||||
L="$BATS_TEST_TMPDIR/logs"
|
||||
run env FRANK_QWEN38_LOG_DIR="$L" \
|
||||
bash "$RUNNER" kldref --dry-run --image example.invalid/radiance@sha256:0000000000000000000000000000000000000000000000000000000000000000
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" != *"systemctl"* ]]
|
||||
[[ "$output" != *"frank-archon stopped"* ]]
|
||||
[[ "$output" != *"podman rm"* ]]
|
||||
# not vacuous: the podman argv WAS printed, and --image replaced the pin
|
||||
[[ "$output" == *"podman"* ]]
|
||||
[[ "$output" == *"example.invalid/radiance@sha256:000"* ]]
|
||||
[[ "$output" != *"stilldeadcode/radiance"* ]]
|
||||
[ ! -d "$L" ]
|
||||
}
|
||||
|
||||
@test "the score dry run is the kldref argv with only --model and the --kld-* pair changed" {
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
C="$BATS_TEST_TMPDIR/campaign"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" \
|
||||
bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
ref_argv="$(engine_argv "$output")"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" \
|
||||
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json" --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
score_argv="$(engine_argv "$output")"
|
||||
# The whole value of the campaign's three KL numbers is that they rode this
|
||||
# one shape: same pin, --tp 2, fp8 KV, step size, --deterministic, no spec.
|
||||
if [[ "$score_argv" != "$ref_argv" ]]; then
|
||||
echo "--- kldref engine argv ---"; echo "$ref_argv"
|
||||
echo "--- score engine argv ---"; echo "$score_argv"
|
||||
return 1
|
||||
fi
|
||||
flat="$(oneline)"
|
||||
[[ "$flat" == *"--model /models/rad/arm-w5nl64.rad"* ]]
|
||||
[[ "$flat" == *"--kld-ref /data/kld-ref --kld-out /data/out/arm1.json"* ]]
|
||||
# the zero point mounts read-only; the report needs a writable bind because
|
||||
# --kld-out F also writes F.rows beside it
|
||||
[[ "$flat" == *":/data/kld-ref:ro "* ]]
|
||||
[[ "$flat" == *":/data/out "* ]]
|
||||
[[ "$output" != *"--kld-record"* ]]
|
||||
[[ "$output" != *"--kld-corpus"* ]]
|
||||
[[ "$output" != *"--vram-kv-mib"* ]]
|
||||
}
|
||||
|
||||
@test "score refuses a missing reference, a missing container and an existing report" {
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
C="$BATS_TEST_TMPDIR/campaign"
|
||||
L="$BATS_TEST_TMPDIR/logs"
|
||||
mkdir -p "$M/rad"
|
||||
: > "$M/rad/arm-w5nl64.rad"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_LOG_DIR="$L" \
|
||||
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"$C/kld-ref"* ]]
|
||||
[[ "$output" == *"run-qwen38-bf16.sh kldref"* ]]
|
||||
# refused before the log dir, so before anything that could reach systemctl
|
||||
[ ! -d "$L" ]
|
||||
|
||||
mkdir -p "$C/kld-ref"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_LOG_DIR="$L" \
|
||||
bash "$RUNNER" score "$M/rad/gone.rad" "$C/scores/arm1.json"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"$M/rad/gone.rad"* ]]
|
||||
[ ! -d "$L" ]
|
||||
|
||||
mkdir -p "$C/scores"
|
||||
: > "$C/scores/arm1.json"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_LOG_DIR="$L" \
|
||||
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json"
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"$C/scores/arm1.json"* ]]
|
||||
[[ "$output" == *"trash"* ]]
|
||||
# the trash law: a refusal keeps the old report, it does not overwrite it
|
||||
[ -f "$C/scores/arm1.json" ]
|
||||
[ ! -d "$L" ]
|
||||
}
|
||||
|
||||
@test "the reference and a score pass ride ONE step size, legal on the file tier" {
|
||||
# The gate's claim is one shape; that shape's ceiling is set by the tightest
|
||||
# payload it boots, and the served iq4r and every arm are file-tier
|
||||
# containers, which stream only at <=2048. A reference recorded above that is
|
||||
# a KL between two shapes, not two weights.
|
||||
M="$BATS_TEST_TMPDIR/models"
|
||||
C="$BATS_TEST_TMPDIR/campaign"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
ref_step="$(flag_value --max-num-batched-tokens <<< "$output")"
|
||||
[ -n "$ref_step" ]
|
||||
[ "$ref_step" -le 2048 ]
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" \
|
||||
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json" --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
score_step="$(flag_value --max-num-batched-tokens <<< "$output")"
|
||||
[ "$ref_step" = "$score_step" ]
|
||||
# one knob moves both, so the operator can lower it at 2 a.m. without an edit
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_MNBT=1024 \
|
||||
bash "$RUNNER" kldref --dry-run
|
||||
[ "$(flag_value --max-num-batched-tokens <<< "$output")" = "1024" ]
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_MNBT=1024 \
|
||||
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json" --dry-run
|
||||
[ "$(flag_value --max-num-batched-tokens <<< "$output")" = "1024" ]
|
||||
# calibration is exempt: grams are not logits, so the tap keeps the wide step
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_MNBT=1024 \
|
||||
bash "$RUNNER" calib --dry-run
|
||||
[ "$(flag_value --max-num-batched-tokens <<< "$output")" = "65536" ]
|
||||
# a step size that is not a token count is refused, never handed to the engine
|
||||
run env FRANK_QWEN38_MNBT=broad bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"FRANK_QWEN38_MNBT"* ]]
|
||||
[[ "$output" == *"must be a token count"* ]]
|
||||
}
|
||||
|
||||
@test "kldref refuses a corpus its window cannot hold, before the service is touched" {
|
||||
M="$BATS_TEST_TMPDIR/models" R="$M/rad"
|
||||
mkdir -p "$M/tpl" "$R" "$BATS_TEST_TMPDIR/out"
|
||||
: > "$M/tpl/qwen38-froggeric-v22.5.jinja"
|
||||
: > "$R/qwen38-bf16.rad"
|
||||
CORPUS="$BATS_TEST_TMPDIR/long.jsonl"
|
||||
{
|
||||
printf '{"prompt":"short one"}\n'
|
||||
printf '{"prompt":"%s"}\n' "$(head -c 120000 /dev/zero | tr '\0' 'x')"
|
||||
printf '{"prompt":"short three"}\n'
|
||||
} > "$CORPUS"
|
||||
# 32,768 tokens x 3 chars is the pessimistic limit; the 120,002-char line
|
||||
# that opens record 2 is well past it, so the campaign says no up front.
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||
bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 2 ]
|
||||
[[ "$output" == *"REFUSED"* ]]
|
||||
[[ "$output" == *"record 2"* ]]
|
||||
[[ "$output" == *"120013 chars"* ]]
|
||||
[[ "$output" == *"past the 98304 a 32768-token window"* ]]
|
||||
[[ "$output" == *"FRANK_QWEN38_MAX_MODEL_LEN"* ]]
|
||||
# the same corpus is fine once the window is raised past it, and the raised
|
||||
# window is what the boot is asked for
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||
FRANK_QWEN38_MAX_MODEL_LEN=65536 FRANK_QWEN38_LOG_DIR="$BATS_TEST_TMPDIR/logs" \
|
||||
bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"corpus fits"* ]]
|
||||
flat="$(oneline)"
|
||||
[[ "$flat" == *"--max-model-len 65536"* ]]
|
||||
# the shipped corpus of three short records fits the shipped window
|
||||
SHORT="$BATS_TEST_TMPDIR/short.jsonl"
|
||||
printf '{"prompt":"tiny"}\n{"prompt":"also tiny"}\n' > "$SHORT"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$SHORT" \
|
||||
FRANK_QWEN38_LOG_DIR="$BATS_TEST_TMPDIR/logs" bash "$RUNNER" kldref --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"corpus fits: longest record 22 chars (number 2)"* ]]
|
||||
# a real kldref run refuses at the same place: no log dir, no drain, and the
|
||||
# service would still be up when the script gave up
|
||||
L="$BATS_TEST_TMPDIR/logs-real"
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||
FRANK_QWEN38_LOG_DIR="$L" bash "$RUNNER" kldref
|
||||
[ "$status" -eq 2 ]
|
||||
[ ! -d "$L" ]
|
||||
# and it is kldref that reads the corpus: score and calib never consult it
|
||||
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||
FRANK_QWEN38_KLD_REF_DIR="$BATS_TEST_TMPDIR/ref" \
|
||||
FRANK_QWEN38_LOG_DIR="$BATS_TEST_TMPDIR/logs" \
|
||||
bash "$RUNNER" score "$R/qwen38-bf16.rad" "$BATS_TEST_TMPDIR/out/arm.json" --dry-run
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" != *"corpus fits"* ]]
|
||||
[[ "$output" != *"REFUSED"* ]]
|
||||
}
|
||||
Reference in new issue
Block a user