Operator's framing, and it is a better argument than the terminology
correction that preceded it. Under v4, exposing a host needed two
affirmative acts -- a DNAT and an accept rule -- so missing either left
the host dark. There is no v4 misconfiguration that exposes an internal
host by accident. NAT was load-bearing security whether or not anyone
designed it that way.
v6 removes the first control entirely. The path exists inherently, so
the firewall is the only thing left, and the failure mode inverts from
fail-closed to fail-open. Rule-ordering slips, rulesets that silently
match only one address family, new VLANs added without policy, and
re-delegated prefixes unmatching address-literal rules all become
exposure events rather than no-ops.
Records the practical consequences: key rules on interface/zone rather
than address literals, treat enabling v6 on a segment as requiring
policy to exist first, and verify default-deny from off-net rather than
by reading the ruleset -- which is lesson 3's assert-the-effective-value
discipline applied to firewall policy.
Also corrects my own claim from the previous commit that the pending
firewall pass was 'smaller' than I had implied. It is not smaller, it is
different in kind.
The link died when ESH lost its public IP during the fiber cutover. Two
independent causes, and the second would have defeated the obvious fix:
- phase1 ana-to-eshudm was type static, pinned to 70.181.90.232, an
address that no longer exists.
- nattraversal was disable, so ESP could not have crossed NAT even with
the peer IP corrected. pfi-ana-nh3 shares that setting and survives
only because NH3 is publicly addressed, which is why the two tunnels
diverged.
FortiOS refuses `set type dynamic` on an existing tunnel -- "Cannot
change tunnel type once configured" -- and rolled back cleanly, so the
fix could not be an edit. Rather than delete and recreate, which
cascades into the phase2, two static routes and ten policies, the
replacement was built alongside: new phase1+phase2 ana-eshudm-dyn
(type dynamic, ikev2, aes256-sha1, dh14, NAT-T on, PSK read from the ESH
UDM API so neither side needed a new key), static route id 10 at
distance 20, and two consolidated multi-zone policies 73/74. The old
tunnel is left in place, dead and harmless, as rollback.
Verified up: ana-eshudm-dyn_0 97.170.236.56:4500 selectors 1/1 -- the _0
suffix is a dialup child, :4500 is NAT-T, and the address is the
carrier's, which is precisely what could never have been pinned. ESH
reaches all four colo hosts at 40-56ms, the colo reaches all three ESH
hosts, and traceroute drops from eight hops leaking into the carrier
network to three hops fully encapsulated.
Config was backed up before any write (1.17MB, 36903 lines, off-box).
Residual fragility recorded: the UDM's ipsec_local_ip demands a literal
address -- empty is rejected as api.err.InvalidPayload -- so it still
needs updating when the fiber changes ESH's WAN address. The gateway end
is now address-agnostic; the UniFi end is not.
Correcting an over-generalisation from earlier today. Proving that NAT
does not break Site Magic, I wrote it up as "no addressing outcome
threatens the inter-site tunnel." That is wrong: the fleet has two
inter-site links with opposite NAT behaviour.
- NH3<->ESH is Site Magic, i.e. WireGuard. It survives arbitrary NAT,
proven live on RFC1918 double-NAT (192.168.200.111) with nh3-dev and
nh3-docker reachable at ~40ms. It dials out to NH3's public edge and
never needs inbound reachability.
- colo<->ESH is IPsec on the ana-gw FortiGate, and it is broken right
now under those same conditions. ana-docker, pfi-pve and pbs-ana all
fail from esh-pve-nas, and traceroute shows packets for 10.250.x
leaving the UDM to the 5G modem and then wandering the carrier network
before dying -- not encapsulated at all, so no SA is up and the
traffic falls through to the default route. Site-to-site IPsec pins a
peer IP and ESH no longer has a routable one.
So the IPv6 work keeps its justification, but on the IPsec link
specifically rather than on the tunnels generally. Operator caught the
over-generalisation.
Adds lesson 8 -- a result proven for one protocol does not transfer to
another -- and corrects the superseded-claims row rather than replacing
it, since the original claim was half right and the halves are the
point. Also records my own over-broad claim as its own superseded row.
Seeded by the Site Magic / CGNAT premise, which justified a body of IPv6
work and turned out to be false the first time anything actually tested
it. The mechanism was discoverable in advance: Site Magic is WireGuard
and the far side has a public endpoint, so the NAT'd side dials out and
never needs inbound reachability. NAT breaks inbound; it does not break
outbound-initiated tunnels with keepalives.
Also fills the first row of the superseded-claims table, which is what
that table exists for -- the claim is corrected with a date rather than
quietly deleted, so older references to it resolve instead of misleading.
Sibling to model-quantization-playbook.md, and it exists for the same
reason that one does: hard-won lessons were dying inside per-host
runbooks where nobody finds them until after repeating the mistake.
Six entries seeded from the esh-pve-nas migration, all of which would
bite identically on any other host:
1. mount --rbind into a chroot needs --make-rslave, and losing cgroup2
impersonates failing root-disk I/O closely enough that it was
misdiagnosed as exactly that.
2. A reboot is not confirmed until the host is observed DOWN; "never
rebooted" and "rebooted fast" are indistinguishable otherwise.
3. Assert the effective value, not the presence of a substring. Grep
proves presence; only evaluation proves effect.
4. Ask the server who its clients are -- documented dependent lists rot.
Plus the corollary that an idle hard NFS mount blocks and resumes, so
quiescing means stopping consumers, not always unmounting.
5. The scoped-looking command can be the dangerous one; setting a ZFS
cachefile on one pool of three would have stopped the other two from
importing at boot.
6. Long uptime hides breakage, and a forced look is worth more than it
appears -- one migration surfaced an 82-day-dead pvestatd, a 126-day
hung vzdump, a VM in prelaunch for four months, and an undocumented
cluster, none of them caused by the work.
Carries a superseded-claims table so corrections are dated rather than
silently edited, same discipline as the quantization playbook. The ESH
runbook now links here so the general rules are reachable from the
specific story and vice versa.
Root is now nvme/ROOT/pve-1. The USB DOM keeps the ESP and /boot but is
out of the runtime I/O path, so a bus reset can no longer drop root from
under a running hypervisor. All five guests healthy, three pools ONLINE,
system running, ext4 pve-root intact and unmounted as the rollback with
its own kernel and initrd. zfs-import-cache is now the active import
path -- the all-three-pools cachefile fix doing its job.
The window cost an unplanned outage, and the cause was this repo's own
tooling rather than the migration.
The staging chroot ran `mount --rbind /dev` and /sys with no
--make-rslave. On systemd `/` has shared propagation, so the cutover's
`umount -R` propagated back into the live host and removed the real
/sys/fs/cgroup, /dev/pts and /dev/shm. With cgroup2 gone systemd-logind
could not create a session: ping fine, TCP fine, SSH authentication
succeeded, resident daemons kept serving -- and every new exec hung,
including /sbin/reboot, so the reboot never ran at all.
It impersonates failing root-disk I/O almost perfectly, and I called it
as the DOM dying. That was wrong. dmesg had the answer throughout: the
DOM attached cleanly with no errors, and the last log timestamp was
12114881s -- 140 days -- meaning this was still the original boot. A
down-detector had also never reported the host down, which I read as a
fast reboot rather than as no reboot.
Fixes and guards:
- --make-rslave after every rbind, plus a guard that refuses to proceed
while any chroot bind still reports shared propagation.
- Confirm a reboot by observing the host DOWN, not by watching for it to
come back. Those two states are indistinguishable otherwise.
- Blast radius now measured from the server: `ss` inside CT 103 found
five NFS clients, not the two documented. The new one that mattered is
esh-vm-db, hard-mounted and unreachable by ssh. Left mounted on
purpose and it came through read-write.
- grub-reboot's one-shot does NOT work here: grubenv sits on an LVM LV
which GRUB reads but cannot write, so next_entry survived the boot
that consumed it. Steady state is saved_entry=pve-zfs-root with no
next_entry. There is no auto-fallback on this host and no IPMI.
Recovery needed no console: an idempotent cgroup2/devpts/shm remount
landed in the brief windows where exec succeeded. No data was lost, and
neither the DOM nor any pool was ever at risk.
Everything but the reboot. Two rerunnable elway playbooks; the host is
still running from the ext4 root and its boot path is byte-identical to
the last 140 days, because grub-install is deliberately held back to the
cutover window.
Phase 1 (esh-pve-nas-stage-zfs-root.yaml): carve a 512 MB /boot LV out
of the 768 MB swap LV, populate it, rsync the 4.3 GB ext4 root into
nvme/ROOT/pve-1, write the copy's fstab.
Phase 2 (esh-pve-nas-stage-bootloader.yaml): ZFS initramfs, grub.cfg,
explicit pve-zfs-root and pve-ext4-rollback entries with stable ids,
grubenv pinned to the rollback so cutover's grub-reboot is a one-shot.
Three landmines the plan did not predict, all caught by verify steps
asserting effective state rather than by reading the plan:
- The /boot LV had nowhere to live. VG pve had 4 MB free and mounted
ext4 cannot shrink; freeing space from root needs a rescue boot, which
costs the one-reboot property. Space came from swap (768M -> 256M).
- The runbook's `zpool set cachefile=... nvme` would have broken the
NAS. Populating a cachefile flips the host from import-by-scan to
import-by-cache, so a one-pool cache leaves ssd and tank unimported --
and CT 103 esh-nas has twelve bind mounts spanning all three pools.
Set on all three instead, verified in the resulting cache.
- update-grub silently emitted a pool-less root=ZFS=/ROOT/pve-1, which
boots to an initramfs prompt. Debian's 10_linux builds ${rpool}${bootfs}
and rpool comes from grub-probe --target=fs_label, which returns empty
because GRUB's ZFS reader cannot open a pool with encryption,
large_dnode and zstd_compress -- the same feature set that forced /boot
to stay ext4. The probe failure is swallowed by `2>/dev/null || true`.
Fixed with a /etc/default/grub.d drop-in plus explicit menu entries.
The transferable lesson: the original verify grepped for the correct
root= string appearing somewhere in grub.cfg, which passes while every
menu entry is still broken. Assert the effective value, not the presence
of a substring.
The operator-visible symptom is that PVE cannot be updated on this box for lack
of room. Measured: 225 packages pending, 161 carrying deb12uN/Debian-Security
bumps including ssh, against esh-pve's 8.4.14 versus this host's 8.4.11 and 20
weeks of uptime.
Records the ordering explicitly -- migrate first, upgrade after. The pending set
includes proxmox-kernel-6.8.12-42-pve-signed, roughly 250 MB of kernel plus
initramfs landing in /boot which is on root with 1.3 GB free. Unpacking 225
packages including dpkg and perl into that headroom risks filling the disk
mid-transaction and wedging dpkg on a hypervisor running five guests.
Notes the apt archive-dir redirect as a partial escape hatch if patching cannot
wait, and that zfs-initramfs 2.2.8 is fully capable of root-on-ZFS so there is
no need to upgrade ZFS before migrating.
Operator's proposal, and it is strictly better than the reinstall plan.
Boot and root do not have to share a device. Keep the ESP and /boot on the DOM
as ext4 -- so GRUB never has to read ZFS, which matters because the nvme pool
has encryption, large_dnode and zstd_compress enabled and GRUB cannot read
those -- and move root to nvme/ROOT/pve-1. The initramfs imports the pool and
pivots.
What this buys over the reinstall: the nvme pool survives, so no guest
migration, no export/import of ssd and tank, no reinstall. Downtime is one
reboot rather than half a day. Rollback is a GRUB menu entry, because the ext4
root stays on the DOM untouched. And it retires the actual top risk -- with
root on NVMe, a USB bus reset mid-run no longer takes the running system down;
the DOM becomes read-mostly, written only on kernel updates.
Preconditions verified and already met: UEFI with grub-efi, zfs-initramfs
2.2.8-pve1 installed with 76 ZFS files already in the running initrd, root only
4.3 GB to copy, swap negligible against 125 GB RAM.
Two traps recorded: canmount=noauto on the root dataset or ZFS tries to mount
over the running root; and cachefile is currently none with a 0-byte
zpool.cache, so the pool imports by scan today and must be given a cachefile
before the initramfs is rebuilt.
The reinstall plan is retained as the fallback.
PVE root on esh-pve-nas is a USB Disk-on-Module: 6 GB ext4 with the host's only
ESP. A DOM is SLC/pSLC so wear is not the driver -- the problems are that it is
on the USB bus (a reset drops root under a running hypervisor), has no headroom,
and is unmirrored while 928 GB of mirrored NVMe sits 96% empty.
Runbook targets a fresh PVE install to ZFS RAID1 across both NVMes. In-place
conversion is unsupported, and adding an ESP to the existing NVMes is impossible
-- both are whole-disk ZFS members with 1.7 MiB free and proxmox-boot-tool
manages nothing today.
The headline risk is not on the host being rebuilt: CT 103 esh-nas IS the NAS
at 10.0.50.50, and both esh-docker-vm and esh-pve mount it hard. Taking this box
down stalls esh-pve's storage layer and wedges esh-docker-vm into the D-state
whose only remedy is a host reboot -- the incident shape already on record.
Quiescing those clients is step one of the window, and the README now warns
against casual reboots.
Config snapshot captured off-box to nh3-dev (0600) with /etc/pve, network and
fstab config plus zpool/zfs/disk-by-id/guest state; the newest on-disk copy
before this was June 2024.
Operator correction to the prior 3.8 framing (d28a371), which over-blamed
AEON and dismissed the vLLM bug as a mere amplifier. Both were real and
compounded:
- Cause 1 (real, upstream): the qwen3_5_mtp x GDN partial-accept bug
(#51113), architectural across vLLM/SGLang/llama.cpp, genuinely improved
by the nightly fix -- not just an amplifier.
- Cause 2 (real, quant): AEON is FULL W4A4 (A4 activations on attention),
the bottom of the KNOWN activation-precision gradient already in 1
(W4A4 < W4+FP8 < W4+bf16) -- mildly subpar, not 'defective'. On top of
Cause 1 it degenerated ~15-20% of real multi-turn generations.
The mixed FP8-attention build sits a rung up that gradient and is coherent;
a W4+bf16 build would be higher still at a prefill cost. Process lessons
retained (two causes mask each other; stochastic degeneration is invisible
to n=1 probes; isolate weights in parallel with serving flags -- but the
weight swap alone would NOT have found the real vLLM bug).
Root cause of the multi-day degeneration hunt, operator-confirmed: the AEON
NVFP4 W4A4 quant (sakamakismile/Qwen3.8-27B-AEON-ULTIMATE-UNCENSORED-NVFP4,
full W4A4 incl. attention) went degenerate ~15-20% of generations in real
multi-turn use and forced regenerates. MTP, prefix-caching, and the gateway
all merely AMPLIFIED it, which is why MTP-off, APC-off, and the vLLM #51113
fix each 'helped' a synthetic probe without fixing it -- three plausible
false root-causes, each passing one clean run then failing in real use.
The fix was the WEIGHTS: the in-house JonathanColetti/Heretic mixed
NVFP4+FP8 build (qwen38-27b-uncensored-nvfp4-mixed, FP8 attention not W4A4,
same base, same MTP) is coherent through long multi-turn with MTP ON. W4A4
*attention* was the defect; FP8 attention is not.
This commit:
- GEN_MODEL -> the mixed FP8-attn build (primary gen until DavidAU 3.8 lands)
- GEN_IMAGE pinned to vllm/vllm-openai:nightly-311b3513... (v0.27.2rc1.dev150,
carries #51113; pinned by sha so it does not drift on the next pull)
- AEON weights PURGED from /tank (no-good), safety-checked not-in-use first
- playbook 3.8: the stochastic-W4A4-degeneration lesson + isolate-weights-early
+ do-not-declare-a-fix-from-one-probe (it validated three non-fixes)
AEON is re-pullable from HF if ever needed, but the operator ruled it no-good.
The qwen3_5_mtp corruption (playbook 3.7) is gated on MTP x prefix-caching
TOGETHER (vllm#43559 / #47194), per both cross-frontier peers. Disabling
prefix caching (--no-enable-prefix-caching; vLLM V1 defaults it ON, so the
explicit --no- form is required) forces the GDN cache into a mode where the
partial-accept align-path bug is inert, so MTP can stay on.
Verified on our stack (AEON W4A4): MTP on + prefix-caching off -> the 7-turn
varied series stays coherent through 3.9k tokens, zero cross-turn bleed, at
104.6 tok/s / 53.6% acceptance -- the FULL MTP speedup restored (vs ~half
with MTP off), losing only prefix-cache reuse. All 7 aliases route.
Ruled out on the way: num_speculative_tokens=1 (corruption is
depth-independent, n=1 and n=2 both corrupt); switching to SGLang (vLLM /
SGLang / llama.cpp mainline all share the architectural GDN-rollback bug).
Proper upstream fix (#51113) is in main / v0.27.2rc0 only, not stable, so we
hold at APC-off rather than jump the fleet gateway to an RC.
Supersedes the MTP-off config from 7bd38b3.
The single hardest bug of the night, and invisible to the existing
acceptance gate: a LOADED, healthy-accepting MTP head still corrupts
Qwen3.8-27B multi-turn output past ~2k cumulative tokens (length collapse +
cross-turn content bleed), while single-turn is perfect. Model-independent
across all three of our Qwen3.8 quants; Qwen3.6 on the same qwen3_5_mtp
method is clean; disabling MTP fixes it. New rule: gate MTP on a multi-turn
coherence probe, not just single-shot acceptance.
Quants are hard-fought and we keep re-paying for the same lessons. A survey
found quant knowledge scattered across 18 files in four trees, with three
documents having independently discovered and recorded overlapping
"landmines" sections — and one of them now actively misleading.
Adds docs/pfi/model-quantization-playbook.md as the single home for the
TRANSFERABLE lessons, with per-model artifacts demoted to worked examples
that link up to it. Contents:
- scheme decision table, incl. that a literal "W4A8" NVFP4 checkpoint is
unservable on vLLM (two legal activation settings, FP8 is not one)
- the reference mixed-precision recipe and the three parts of it that are
load-bearing and easy to drop
- the recurring landmines, ordered by cost: the loader-class trap
(rediscovered THREE times), the three separate ways to lose the MTP head,
toolchain deadlocks, vision configs, memory/device placement
- pipeline shape: prove targets before spending GPU time; mandatory
post-steps that verify rather than assume
- the acceptance gate, and the three ways measurement has lied to us —
prefix caching faking both speed metrics, prompt_logprobs going uniform
under speculative decoding, and a 0600 .env making compose silently no-op
- hardware/co-residency, including that a SMALLER model can starve its
neighbour because gpu-memory-utilization is a fraction of the whole card
- a superseded-claims table, and measured negatives not to re-chase
The superseded table earns its place immediately: the heretic2 runbook tells
readers to use modelopt because "compressed-tensors can't load the BF16 MTP
head, 0% acceptance". That symptom was real but the cause was not the format
-- it was the missing re:^mtp.* ignore entry. compressed-tensors gives
47.7-83.2% acceptance in production. A fresh session following that doc would
be sent down the modelopt path that current memory calls dependency hell, so
the runbook now carries a stale-warning header pointing here.
Wires discovery: an orientation.md "Where to look for what" row, pointers
from the gen-seat / heretic2 / mistral artifacts, and a CLAUDE.md maintenance
rule so the playbook gets fed instead of going stale -- model-agnostic
lessons land in the playbook, model-specific ones stay put, and a wrong
claim earns a dated superseded row rather than a silent edit.
Motivated by Qwen3.8 having just released: the next model swap will need a
requant, and this is what that session should read first.
56KB deep-research report on thinking-capable eRP finetunes 15-30B, weighted for
spatial/state coherence, targeting RTX PRO 6000 Blackwell (sm_120) NVFP4. Preserved
from an ephemeral Booth (gecko-65) into durable reference for the quant decision.
Brokkr independent verify clean (maxdiff 0.000000, no split). R42 v13
acceptance gate PASSES first time in its history: main+kb 56/90->90/90,
evictions 33->0. A2 control torn down. A3 throughput characterized at
~34 req/s (graceful queueing), with A4/util-bump/replica as levers.
A3 now backs the prod reranker alias but was launched --restart no;
docker update to unless-stopped so an ana-ml2 reboot can't silently
break the alias. Full compose-service promotion tracked as a follow-up
in the selection ledger.
The incumbent Qwen3-Reranker-0.6B was measured actively harming 80/90
fleet queries on main+knowledge_base (and inverting the bare-name region
behind Worldtree #389) — no-reranker beat it 89/90 vs 56/90. Brokkr's R43
bake-off selected BAAI/bge-reranker-v2-m3 (A3): 90/90 top-10, mean rank
0.19, multilingual (XLM-R), ~1.2 GB lighter than the incumbent.
Control arm (A2 = same Qwen weights, seq-cls head) scored identical to the
incumbent, proving the fault is a training prior, not the serving head —
which cancelled the expensive Qwen3-4B arm before it cost a GPU seat.
Cutover boundary 2026-08-06T17:37:48Z. The qwen3-reranker alias and the
:8002 backend are retained for one-edit rollback. Adds the process audit
trail at docs/pfi/reranker-selection-ledger.md.
The fast char-rp-reasoning seat works: ~77 tok/s (vs GGUF ~59.5, base NVFP4 ~53),
MTP draft-acceptance 32-40%, mean acceptance length 2.19. Same Heretic2/NEO-CODE
model, NVFP4 + native qwen3_5_mtp spec-decode.
Full end-to-end recipe + the four landmines in docs/runbooks/heretic2-nvfp4-mtp-seat.md:
(1) load as AutoModelForImageTextToText not AutoModelForCausalLM (namespace/gibberish);
(2) modelopt format not compressed-tensors (compressed-tensors MTP = 0% accept);
(3) modelopt 0.45 <-> transformers 5.12.1 FusedMoE crash (guarded in quant_modelopt.py);
(4) vLLM 0.24.0 does NOT propagate modelopt exclude_modules to the spec-decode draft
model -> BF16 mtp head gets quantized -> shape crash; no checkpoint config fixes it
(is_layer_skipped is exact-membership not glob) -> fix is a mounted sitecustomize that
force-skips mtp.* in is_layer_skipped (upstream vLLM bug to report).
Scripts: quant_modelopt.py (FusedMoE guard + single-shard export + multimodal load),
finalize_modelopt_mtp.py (splice bf16 mtp), serve_modelopt_mtp.sh, run_quant_modelopt.sh,
sitecustomize-mtp-workaround.py.
The ufw fix (prior commit) was necessary but insufficient. The DECISIVE blocker
was gitea webhook.ALLOWED_HOST_LIST = 'external, 10.100.0.0/16' (NH3 only) —
corviduo-dev is 10.250.50.152 (Anaheim), so gitea refused to deliver ('deny
10.250.50.152') and never opened the TCP connection. Fixed to 'external,
10.0.0.0/8' (whole fleet, matches the ufw choice) + gitea restart.
Listener now logs every delivery (source-IP/hmac_ok/ref/action) — the old
log_message=pass silence hid the whole failure. Proven end-to-end: real gitea
delivery -> hmac_ok=True, ref=main, 202 deploying -> green deploy.
The auto-deploy silently never worked: corviduo-dev's ufw is default-deny and
port 9010 was never allowed, so gitea's webhook deliveries timed out (DROP).
v0.3.6 was a manual deploy; v0.3.7-v0.3.13 never auto-deployed. The setup-time
'test-delivery 204' was gitea queuing, not the listener receiving. Fixed by
'ufw allow from 10.0.0.0/8' (operator-directed). Confirmed end-to-end.
soong-dev found the studio serving a stale web/ (52015 vs 55025 bytes — missing the
01-Role section, favicon, thinking-status): the deploy rsynced backend/ but never web/,
so SOONG_LAB_WEB_DIR stayed pinned to the initial manual copy while the backend updated.
Deploy now rsyncs BOTH backend/->studio AND web/->SOONG_LAB_WEB_DIR (read from the env)
on every green run. Verified: served frontend now 55025 bytes, current.
Per operator call (no gitea write token on the Worldtree-team VM): a 2-min systemd
--user timer on nh3-dev polls corviduo's last-deploy.json and pings soong-dev via
althing on a NEW red deploy (green stays silent). Delivers soong-dev's red-run
visibility without a credential on corviduo. Tested (red detect+format DRY, green quiet).
Adds the rsync --link-dest hourly snapshot job (nh3-dev:~/development ->
nh3-nas, 48-snapshot retention, secrets/build-dirs excluded) that closes the
no-off-box-backup gap exposed by the 2026-07-12 working-dir clobber. Script
mirrors the live ~/.config/dev-backup/dev-backup.sh; runbook covers restore.
New TTS service entry + reproducibility_audit row for the zonos-gateway
wrapper (irv-ml1:8890) — the ext-tts-aliased OpenAI facade over Zonos.
23 fields across Text&voice / Expression / Prosody / Quality / Sampling /
Output section groups; live voice dropdown from /v1/voices; response
format pcm|wav (audition UI forces wav). Distinct from the older down
zonos :8203 entry. jsonschema-validated.
The live gateway config has served char-rp-reasoning as deckard-pkd-27b (:8018)
since the 2026-07-08 A/B; the standalone doc had frozen on QwQ-RpR-v4. Corrects
seat 4 (backend + samplers + server-side DRY/reasoning-budget notes).
Also snapshots session state in persistent-memory.md: phantom-qwen verified
already-clean, ana-docker docker log-cap (logrotate copytruncate, no bounce),
and the granite→gen memory_extractor bind live on demo+personal.
Add §9 "PFI LiteLLM Gateway — Deployed Sampling Defaults": the live fleet
sampling table (granite/qwen/judges/GLM) with provenance, overrideable-default
semantics, the GLM API-accepted-subset caveat, and the research-confirmed temp-0
rationale for granite + image-judge. Accepts the dvalin-smithy-dev recommendations
as deployed. §§1-8 vendor reference left intact.
Wrapper /v1/audio/speech now accepts OmniVoice's whole surface:
- voice (clone, now OPTIONAL) and/or instruct (voice DESIGN). instruct is a CONTROLLED
vocabulary (gender/age/pitch/accent/whisper tags, comma-separated), not free prose —
discoverable at the new /v1/audio/instruct-items endpoint (23 items).
- language (Auto + 647, new /v1/audio/languages endpoint), speed, duration.
- diffusion controls: num_step, guidance_scale, denoise, preprocess_prompt,
postprocess_output; plus a generation_overrides JSON passthrough for expert
GenerationConfig knobs (t_shift, layer_penalty_factor, position/class temperature,
audio_chunk_*).
- at least one of voice/instruct required (else 400).
Catalog (services.yaml): omnivoice v1 -> v2, 13 schema-valid fields; instruct as a
controlled-vocab text field sourced from the items endpoint.
Verified live on irv-ml1: clone, voice-design (instruct-only), and tuned-param synths
all -> 24 kHz PCM_16 WAV; 647 languages; 23 instruct items.
- app.py: thin FastAPI wrapper exposing OpenAI /v1/audio/speech (+ /v1/audio/voices,
/healthz) around OmniVoice's Python API; precomputes a voice-clone prompt per voice
at startup (loaded Whisper auto-transcribes each reference). Replaces the Gradio demo.
- Dockerfile/compose: run the uvicorn wrapper, /healthz healthcheck, project name pinned
to "omnivoice" so the asset-engine liveness probe matches.
- deploy-omnivoice.yaml: stage chatterbox /refs/*.wav as clone voices (skip _* artifacts)
+ verify the API surface.
- services.yaml: catalog entry (id omnivoice, :8199/v1/audio/speech, voice list sourced
live from /v1/audio/voices) + reproducibility_audit row.
Verified live on irv-ml1: /healthz ok, 33 voices loaded, test synth -> 24kHz PCM_16 WAV.
Fleet/colo hosts must reach gitea over the internal route (ana-docker
container git-SSH at 10.250.50.70:222), not the public gitea.phasefinal.com:22
which fail2bans the host's egress IP and silently wedges webhook auto-deploys.
Bit irv-ml1's arbo deploy 2026-06-13.
storetank archive fully resolved (919 G -> 0): ~739 G killed (superseded/niche),
177 G migrated into arbo, rest dupes. Rewrite the archive doc as a decommission
record; refresh the arbo catalog to its post-migration 502 G state (+ SDXL/Pony
stack + 9 gen-agnostic utility categories).
Swept the orphaned llava_llama3 (HunyuanVideo text encoder, 23.5 G) after the
Hunyuan kill left it unreferenced. Update the curation table + remaining total.
Capture the 2026-06-13 archive curation pass (919->238 G, 681 G reclaimed:
Hunyuan + WAN2.1 + FLUX.1 + umt5 orphan, all superseded by arbo's current-gen
stack) and a detailed catalog of the remaining 238 G (SDXL/Pony stack, SD3.5/
Chroma, gen-agnostic utilities, shared encoders) for comfy-dev's migration
decisions into the active arbo set.
ComfyUI's ~325G model tree moved off the near-full worktank NVMe (97%->26%,
342G free) to /storetank/arbo (roomy SATA SSD on irv-ml1), overlay-mounted
back at /basedir/models so ComfyUI behaviour is unchanged. rsync byte-verified
(src==dst), one comfyui restart, worktank original removed. Inventory of the
set in docs/arbo-comfyui-model-catalog.md for the retain decision. The older
919G /storetank/image-models/comfy archive is untouched (separate reclaim).
The asset-engine catalog source of truth. Removes the chatterbox
exaggeration/cfg_weight sliders (proven Turbo no-ops) — reconciling the
canonical with the fix previously applied only to asset-engine's vendored
copy — and adds the csm-expressiva whisper TTS entry (irv-ml1:8198).
LiteLLM proxy fronting the vLLM services on ana-ml2 so every request +
response is captured and inspectable in a browser Logs UI — the
visibility vLLM itself lacks (Dozzle shows only connection metadata).
- compose: litellm (proxy + /ui Logs) + litellm-db (Postgres store)
- conf/config.yaml: routes phi4-mini (chat, :8004), qwen3-embedding
(:8001), qwen3-reranker (:8002); store_prompts_in_spend_logs persists
full prompt/completion text. reward classifier (:8003) stays direct
(no first-class LiteLLM route).
- Langfuse-ready: lean first cut intentionally skips Langfuse's heavy v3
stack; graduating is one env-var + callback step, no re-architecture.
- roadmap: mark the vLLM-observability item's first cut as shipped.
Lean first cut of docs/roadmap.md "Observability for the vLLM stack".
Auto-deploy on push to main: gitea webhook → HMAC-validated listener on irv-ml1:9008
→ git fetch/reset + docker compose up -d --build. Documents the gitea-server
ALLOWED_HOST_LIST anti-SSRF relaxation (scoped to the WG net), the irv-ml1 components
(deploy key, git-clone deploy dir preserving the proxy override/secrets, listener +
user service), and verify/debug steps.
YouTube (and a growing set of services) hard-flag datacenter IPs, bot-gating
even public content regardless of cookies/PO-tokens. Origin case: yt-voice-clipper
on irv-ml1 (Irvine colo) — every yt-dlp fetch returned LOGIN_REQUIRED. Confirmed
pure IP reputation: the same public video fetches cleanly (no cookies) once routed
through nh3-dev's residential egress (70.230.226.88).
- scripts/setup-nh3-egress-proxy.sh: idempotent dante (SOCKS5) install + config.
Internal-only ACL (10.100.0.0/16), bound to the WG interface, systemd-managed.
- docs/runbooks/nh3-egress-proxy.md: purpose, usage, security model, caveats.
Reusable fleet egress, not yt-voice-clipper-specific.
- chatterbox-fast experimental -> ready: browser audition verified end-to-end
(operator confirmed progressive playback "excellent" 2026-06-02).
- vibevoice ready -> down: no container running on irv-ml1 (connection refused);
catalog status was stale.
- voxtral: NOT a stale typo — its stack genuinely claimed :8197, the port now held
by the live chatterbox-fast. voxtral is down, so moved IT to :8201 (catalog
endpoint + source_url, stacks/voxtral/.env.example + README, host .env) rather
than disturb the live service. No live clash existed (voxtral down) but it was a
latent deploy-time collision I introduced by placing chatterbox-fast on 8197.
No catalog_version bump (status changes + endpoint correction, additive). Validates
against the schema.
Land chatterbox-fast in the asset-engine catalog as an additive service, per
asset-engine-dev's shipped streaming-audition path (asset-engine v0.1.17-19):
- streamable:true -> UI routes Generate to an ephemeral progressive-<audio>
audition (no Job/Asset); re-run on `chatterbox` to keep output.
- New service-level `streamable` bool added to services.schema.json (additive,
default false; mirrors the Pydantic model asset-engine-dev regenerates).
- Fields: text, voice (select via /voices), temperature/top_p/top_k/
repetition_penalty/seed, format (pcm default; UI forces wav). exaggeration/
cfg_weight omitted — Turbo ignores them.
- status experimental until the first real browser audition verifies progressive
playback (the one thing asset-engine-dev couldn't machine-verify).
- reproducibility + audit entries added. No catalog_version bump (additive).
Validates against the updated schema.
Build the streaming TTS server MVP per docs/design/chatterbox-fast-plan.md §4.
- scheduler.py: adaptive buffer-ratchet chunker (the meat) — GPU-free pure
logic. First sentence emitted alone for low TTFA, then chunks ratchet ~3x by
packing whole sentences to margin x buffered-audio; drives off measured RTF +
sec/char (EMA). relieve_leader() clause-splits a too-big mid-stream sentence
to avoid starvation (joins land on commas); a long comma-less sentence is the
one honored-but-flagged limitation.
- test_scheduler.py: GPU-free simulation, 13 tests — asserts no-starvation
(incl. overestimated RTF) and the ratchet.
- app.py: FastAPI model holder + POST /tts StreamingResponse (raw PCM s16le
default, wav optional, stream/oneshot) + GET /health.
- bench.py: client — ground-truth TTFB + real 1x-consumer starvation check.
Live test on irv-ml1 (turbo, A6000, GLaDOS voice): streaming TTFB 499ms vs
oneshot 5230ms (~10x), stayed ahead of a 1x player (no starvation), ratchet
1.64->4.08->8.60->8.60s audio, measured RTF self-corrected 3.38->4.01.
Kill the superseded docs/design/chatterbox-fast.md — its §5 windowed-token
streaming was the abandoned native-frame-streaming arc; the adaptive-chunk plan
supersedes it. Repoint persistent-memory + README at the canonical plan.
Self-contained build plan for the chatterbox-fast streaming engine: the
adaptive buffer-ratchet chunking design, validated turbo API + facts, the
GPU-1 dev/test container pattern, 4 build phases, the base-fork A/B, and
watch-outs (incl. native-turbo-streaming is abandoned). Intended for a
fresh-context session to execute at full strength.