Refactoring backlog — CLOSED 2026-09-02 (RVA23 rebuild window)#
Split out of
refactoring-backlog.mdso that file shows only OPEN work. Nothing here needs action; it is kept because the entries record WHY a thing is the way it is — including several closed by being CORRECTED or REFUTED rather than fixed, the kind of finding that gets re-discovered if the reasoning disappears. Earlier rounds:refactoring-backlog-archive-2026-08-10.md,refactoring-backlog-archive-2026-08-27.md,refactoring-backlog-archive-2026-08-30.md,refactoring-backlog-archive-2026-08-31.md.
Closed during the RVA23 media rebuild (2026-09-01/02). Gates at closure:
48/48 shell suites green, shellcheck -S error clean tree-wide, duplication
gate OK (244 allowlisted pairs, 0 still unreviewed), full preflight green.
The rebuild itself proved the media stage on all three arches; the runtime lane
was still ahead when this cut was made.
Two entries were rescued from this cut, not archived: the runtime-smoke
standalone hang and the preflight.sh gitleaks death were filed under F7
(ArmNN), which they have nothing to do with. They are OPEN and now live under
F9 in the main file. Section H’s LB10–LB13 likewise stayed behind — its header
says CLOSED, but four of its items are open work.
C. Pre-existing, found while auditing 2026-08-31#
test-preflight-slugs.shfeedscommunsorted input — FIXED, and the last other call site closed 2026-09-02 [S·★★]. The test now runsLC_ALL=C commover twoLC_ALL=C sortinputs and carries a comment naming this bug; no “not in sorted order” warning remains.This entry’s instruction — “grep for other call sites while fixing” — was audited 2026-09-02.
build-runtime-manifest.sh:140andtest-codec-so-map-convergence.shwere already correct.lint-env-knobs.sh:58was not: both its inputs are produced withLC_ALL=C sort -u, butcommitself ran in the ambient locale.commchecks sortedness in ITS OWN collation, so on a host that collates differently it would disagree with files this script sorted in C and report a wrong set difference — silently, since the gate prints only counts. NowLC_ALL=C comm; the gate still passes (635 consumed knobs, all owned). ORIGINAL ENTRY: printscomm: file 1 is not in sorted order(×2) and still reports5 assertion(s) passed.common unsorted input does not compute a correct set difference, so its_missing/_orphanassertions may be passing VACUOUSLY. Not touched by the 2026-08-31 window (the file is unmodified), but it is exactly the toothless-gate class standing rule 5 is about. Sort both inputs, then re-check that the assertions still pass for the right reason. REINFORCED 2026-08-31: the same bug bit THREE times in one day while auditing (measuring ORT clone overlap, and twice while checking package lists), each time turning a real answer into a wrong one —LC_ALL=Cchanged “0 shared” into “36 shared” and “12 packages missing” into “1”. Anycommin this tree wantsLC_ALL=C sorton both inputs; grep for other call sites while fixing.D2. Two package names are dead on Ubuntu 26.04 ports — RESOLVED, verified 2026-09-02 [S·★].
install-deps.sh:191now callslibopenexr-dev, the correct name; the dead first attempt is gone.libvvdec-devstays on purpose — the comment above it records that the package does not exist on ports at all, so the guard is deliberate and vvdec builds from source. Nothing left to do. ORIGINAL ENTRY:libopenexr-3-dev(renamedlibopenexr-dev) andlibvvdec-dev(gone entirely), both in03-media/build/gstreamer/install-deps.sh:183,186. Neither breaks a build — both already carry|| …|| truefallbacks, and vvdec falls through to a full source build — so this is cleanup, not an outage. But every run pays a failed apt round-trip and, for vvdec, an entire compile. NB the openexr fallback to the new name is already there; only the dead first attempt needs removing.D5. Failed stages still write their cache exports — FIXED, verified 2026-09-02 [S·★★].
_cross_salvage_disk_ok(cross-stage-build.sh:44) now gates the salvage on free space viaSALVAGE_MIN_FREE_GB, falling back toCROSS_DISK_GUARD_GB, exactly as this entry proposed — “conditional on free space rather than on the operator remembering”. ORIGINAL ENTRY:cross-stage-build.sh:206salvages local cache exports for all 15 named media stages after a FAILED build — burning disk on stages that will be rebuilt anyway, at exactly the moment disk is scarce.SALVAGE_CACHE_EXPORT=0already disables it; consider making it conditional on free space rather than on the operator remembering.D7. Warning audit 2026-08-31 — do not repeat it [closed, recorded so it is not re-done]. Every warning in a full 3-arch run was classified. ~12,700 are compiler warnings inside UPSTREAM sources (ONNX Runtime’s
lifetime_capture_by, glslang array-bounds, LLVMPointerType::getdeprecations, Dawn) — not our code, not fixable here. ~50 more are upstream TOOLING noise: the Vulkan SDK’s own profile parser (jsonschemamodule missing) and GStreamer.girparsing. Exactly ONE was ours, and it is fixed (the OpenCV build-stage cv2 import check could never succeed; commit b3dbd32a). Counting warnings by grepping the log OVERCOUNTS badly: BuildKit echoes each RUN’s command text, so warning strings inside a Dockerfile command are matched as if they had fired. Count only real output lines (^#N <time> WARNING) — that is the difference between “TVM build failed 23×” and “TVM built fine”.DC. A pinned stage’s cache-export slug is dead weight, and nothing reclaims it — FIXED DIFFERENTLY 2026-09-02 [M·★★★, cost SIX manual interventions].
kata-buildcacheis keyed per stage slug (..._cross-media-riscv64,..._cross-android-amd64, …). The moment[stage X] pinned <digest>is logged, that stage is built, pushed and will not be rebuilt in this run — its slug can never be read again, yet it sits on disk until the end. Measured 2026-09-02 in onemedia..runtimerun: the three media slugs held 83 G and the three android slugs 39 G, all of it dead, while the runtime lane was starving._chain_stage_disk_guarddoes prune this directory, but only belowCROSS_DISK_GUARD_GB(40 G) — far too late for a lane whose own entry gate wants ~120 G, so the run either stops at the gate or needs a human. In that one run the free-space curve went 154 G → 78 G → (prune) → 191 G → 61 G → (prune) → 143 G, with every recovery manual.The fix is not a lower threshold, it is a trigger: reclaim a slug when its stage is pinned, not when the disk is nearly full. The chain already knows the moment (it logs it) and already knows the mapping (the slug is derived from the tag). Guard rails worth keeping: never touch the slug of a stage in the current build set, and keep the LRU path as the emergency backstop for everything the trigger does not cover. See [[rebuild-disk-management]] for why
nerdctl builder prunemust never be the answer here.DD. riscv64 Node.js is FOUR majors behind the pin — CORRECTED, and it is a decision, not a defect [S·★★, owner call]. The original wording here said the pin is “silently dropped”. That was wrong on both counts, and reading
base-image.shcorrected it: the fallback is deliberate, documented, scoped and has an escape hatch (NODE_RISCV64_MAJOR_REQUIRED=1restores a hard failure), and it warns loudly — the major-lag warning fired 5 times.What is worth an owner’s attention is the size: pin
26.8.1, installedv22.22.1. Four major versions, because ubuntu-ports lags and there is no official Node tarball for riscv64. The code’s justification is that Node there only backs optional JS/web tooling (litert-web / onnx-web) that the Python and native runtime never import. That justification is sound if it is still true — so the question is whether that tooling works on Node 22, not whether the fallback should exist.No code change made. The mechanism is right; only this entry was wrong.
DE.
IREE_BUILD_COMPILER=OFFdoes not produceiree-tblgen— FIXED 2026-09-02 [M·★★, costs a full host compiler build].[WARN] IREE host stage (IREE_BUILD_COMPILER=OFF) left /tmp/app-wheelhouse/iree-build-host/install/bin missing: iree-tblgen — retrying with the full host compiler, twice in one run. The two-stage build indocs/iree-two-stage-build.mdexists precisely to avoid that compiler build; the fallback works, so the cost is invisible in the exit code. Either the OFF stage needsIREE_BUILD_TOOLS(or whatever now gates tblgen) or the two-stage split should be retired as ineffective.DF. The media ELF gate guesses whether a foreign-arch .so is a vendor blob — FIXED 2026-09-02 [S·★★]. It prints
MISMATCH (advisory, may be vendor): libpython3.14.so.1.0 ELF machine=Advanced Micro Devices X86-64 != expected RISC-Vand thenNOTE: 3 advisory .so ELF mismatch(es) (likely bundled vendor SDKs; not failing). It logs basenames only and the word is likely — the script already carries a list of directories that legitimately hold foreign-arch vendor binaries (QNN, SNPE, NeuroPilot), so it could classify by path and fail on anything outside them. As written, a genuine x86-64 leak into a riscv64 image would print exactly the same line as a MediaTek blob. (The three libpython hits are almost certainly the amd64 build container’s own Python and therefore harmless — but the gate cannot tell you that, which is the point.)DG. The media stage’s qemu functional check has never passed on a foreign arch — REMOVED 2026-09-02 [S·★★].
WARN: /usr/bin/qemu-riscv64 gst-launch-1.0 --help failed (may be expected for cross builds without full sysroot)— 4× for riscv64 and 3× for arm64 in one run, forgst-launch-1.0,camandffmpeg, every one excused by the same clause. A check that always fails and is always excused is not a check; it is training to ignore WARN lines. Real functional coverage does exist later (the runtime-image smoke boots the actual image), so either give this one a sysroot or delete it.AB.
onnxruntime-webgpu’s dependency edges are dangling on riscv64 — FIXED 2026-09-02 [S·★★★].flatbuffersandprotobufare declared requirements of the installed wheel and absent from the venv, so an import can fail in the user’s process rather than in our build.Root cause is deliberate and must not be reverted:
assemble-torch-app.shinstalls every local wheel with--no-deps --force-reinstall, and its comment names this exact hazard — “without it uv re-resolves the wheels’ deps to LATEST and floats the venv off the lock (numpy, protobuf MAJOR)”. The run bears the comment out:protobuf==6.33.6was installed twice andprotobuf==7.36.1once across the three arches.Correction: an earlier draft of this entry called
PROTOBUF_VERSION=6.31.1“a pin nothing honours”. That was wrong — it is the C++ protobuf runtime for LiteRT-LM, deliberately slaved to that project’s internal pin, and versions.env says so. It has nothing to do with the Python package.Both packages publish a pure-Python
anywheel, so neither is blocked on riscv64 and the fix is cheap: install them explicitly next to the local ORT wheel at the version the app lock already uses on amd64 (protobuf 6.33.6,flatbuffers 25.12.19). Do not drop--no-deps.AA-followup. The optuna install was in the wrong place — FIXED 2026-09-02 [S·★★★]. Found by checking the AA/AB fixes against the lane they were about to run in, before the runtime stage got there. The install had two defects, either of which would have let the gate name
optunaagain:It sat in
reconcile_local_wheels, which runs beforeensure_project_package_installed. Measured against PyPI: optuna’s closure isany-wheel everywhere exceptPyYAML, which publishes noanywheel and no riscv64 wheel — only an sdist. Run at that point it would source-build under emulation; run after the project install it is already satisfied. This is the same ordering thedocsextra already documents.It was nested under
if [ "${#other_wheels[@]}" -gt 0 ]— a condition about the ORT wheel set, unrelated to optuna. With no localother_wheelsit would never have run at all.
Moved to
install_fallback_project_extras, which runs only on the riscv64 fallback path and only afteruv pip install "${APP_DIR}". Both defects go away with the move; no new condition was needed.protobuf/flatbuffersstay where they are — they are the ORT wheel’s own dangling edges and have zero runtime dependencies, so neither concern applies. Reasoning in docs/riscv64-venv-parity.md#optuna.Also verified while there: the AA exemption arms are keyed correctly. The probe normalises with
lower().replace("_","-").replace(".","-"), andscipy,scikit-learn,pandasare already in that form, soriscv64:ml-ai:<pkg>matches. And optuna’snumpyrequirement is unconstrained, so the install cannot float the venv off the lock — the hazard--no-depsexists to prevent.UB. Replace our OpenCV FFmpeg-8 patch with the upstream commits — DONE 2026-09-02 [S·★★★]. Upstream fixed this on
4.xin700cd32ffdand83ed22ca28;5.xdid not get it. Both are saved indocs/upstream/patches/and were verified to apply to5.xwith no conflicts, after which our own patch no longer applies at all. Ours also guards on the wrong idiom (LIBAVCODEC_VERSION_MAJOR >= 62vs theirLIBAVCODEC_BUILD >= CALC_FFMPEG_VERSION(61, 13, 100)) and trusts a{0,0}terminator where the new API returns a count. Swap ours out for theirs.verify-shipped-wrapper.shcarried a private_is_truthy— DONE 2026-09-02 (e2da351c). It now sources01-core/platform.shvia its own_hereand uses the canonicalis_truthyat all three call sites. Safe becauseplatform.shhas zero top-level statements, so sourcing it has no side effects; the twocasearms were byte-identical and a differential test over 21 inputs (True,tRuE,enabled, empty, whitespace, …) agreed on every one. Verified in the manifest builder’s exact invocation form — the gate reaches its own logic, so thesourceresolves at runtime and not merely underbash -n. The fourcode-dupes.allowpairs it needed were retired in the same commit; the gate itself flagged them as stale, which is the allowlist working as designed. The test stubs (test-chain-lifecycle.sh,test-parallel-loop.sh,test-ffmpeg-dnn-contract.sh) keep their own copies on purpose — a test that sourced the real one could no longer prove the shipped copy behaves. They are the 4-file family the gate still reports for this block, and that is correct.
F4. The duplication gate’s own defects [S each]#
Clone-family clustering degenerates. Union-find over shared files transitively collapses most of the tree into one meaningless “88 files” family. Cluster on the shared BLOCK, not on file adjacency.
The genai Python is STILL unlinted — FIXED 2026-09-02 in the EXTRACTOR, not the source [was: re-measured 2026-09-02].
extract-embedded-python.pynow assemblescated fragment FAMILIES (two or more sharing a marker prefix) in file order and emits them only when the result parses, so the 217 lines reach ruff without touching the runtime path or the four Dockerfiles that copysmoke-common.sh.Three defects surfaced while doing it, all silent: the marker class
[A-Z_]*excluded digits, soGENAI_PY_T1..T4— four of six fragments — were skipped; and the interpreter pattern hard-coded lowercase${py}, so"${PY}" - <<'PYEOF'was not recognised andassert_pinned_versions’ 312-line program had never been linted at all. Tree-wide coverage after the fixes: 16 files, 861 lines of embedded Python now reach ruff. Both are now mutation-tested. A lone fragment is still never extracted:ast.parsecatches syntax errors but not undefined names, so linting one alone reports bogus findings — the original rule was right about that. ORIGINAL ENTRY:smoke_genai_pywent from one 196-line heredoc to six_smoke_genai_py_tier*helpers totalling 217 lines, each emitting its fragment withcat <<'GENAI_PY_T*'.extract-embedded-python.pyhandles only directly-executed heredocs and says so; run againstsmoke-common.shit reports “no directly-executed heredocs found”. So ruff sees none of it — and assembled fragments are harder to extract than the single heredoc was. Satisfying F1’s length metric and F4’s lintability goal pulled in opposite directions here; a real.pyfile that the smoke pipes in still settles both.
F8. libyuv’s RVV rows are dispatched but never compiled — FIXED 2026-09-02 [DONE]#
Enabling the vector baseline made libcamera’s bundled libyuv fail to link on riscv64:
ld.lld: error: undefined symbol: ARGBBlendRow_RVV
ld.lld: error: undefined symbol: BlendPlaneRow_RVV
The first diagnosis written here was wrong. row_rvv.cc IS compiled — it sits in
libyuv.a as source_row_rvv.cc.o. It compiles to nothing, because the file
guards itself on defined(__clang__) while row.h enables all 57 HAS_*_RVV
entry points for any RVV compiler. Upstream has since dropped that clang gate;
patches/libyuv/001-rvv-build-with-gcc.patch backports exactly that change to
libcamera’s pinned revision, and verify_libyuv_rvv_rows in build-libcamera.sh
fails the build if the rows are ever absent again.
Measured with our own cross GCC 16.2.0: 0 -> 54 (row_rvv.cc) and 0 -> 25
(scale_rvv.cc) RVV symbols, both previously-undefined symbols now defined. The
-DLIBYUV_DISABLE_RVV workaround is gone — vector stays on.
docs/riscv64-rva23-baseline.md#libyuv-rvv
The general shape to expect from the rest of the vector switch is recorded in docs/riscv64-rva23-baseline.md#libyuv-rvv.
F7. ArmNN + ACL — DECIDED 2026-09-01: ship them [DONE]#
They were cross-compiled, stripped and verified on every arm64 chain and then
dropped at the package boundary: Dockerfile.package named them zero times, the
shipped image had neither directory, libarmnn* existed nowhere in it, and the
only libarm_compute.so present came with PyTorch’s own wheel.
Owner decision: ship them. Dockerfile.package now copies /opt/armnn and
/opt/acl from artifact-source (safe on every arch — the media stage creates
empty dirs on non-arm64), and configure-runtime.sh writes 000-armnn.conf so
they win their lookup like every other /opt tree.
Note the same shape is CORRECT for /opt/tvm, /opt/pyav and /opt/app-wheels:
their content is installed into /opt/venv (tvm and av are importable in the
shipped image). ArmNN had no such path.
F6. Cache-key blast radius in Dockerfile.media — FIXED 2026-09-02, awaiting build proof [M each, measured]#
Numbers from out/build-logs/f2-media-validation.log (2026-08-31, amd64, warm
ccache): the media stage is 134.3 min of RUN time across 90 steps, dominated by
app-wheelhouse 1523.5s, ORT --step cpu 1452.0s, tvm 1377.1s, litert 1353.6s.
The waste is not compute — it is over-broad mounts above that work.
Two are FIXED (2026-09-01): verify-media-artifacts.sh no longer sits in the
shared base stage (8 edits since 2026-08-01, each re-paying the whole media
stage on every arch), and linux/qnn-sdk/*.md is out of the build context (a
README-only dir mounted into the five heaviest RUNs).
Both narrowings DONE 2026-09-02, pending build validation:
ORT
--step cpunow mounts three paths instead of the wholebuild/onnxruntimetree:build-onnxruntime.sh,30-build-native.shandbuild/lib/. Derived, not guessed — thecpu|native)arm dispatches to30-build-native.shand nothing else, that script sources onlylib/common.shplussource_build_acceleration_helpers(which loads from/opt/scripts/core, already mounted), and it references neither30-build-native-amd.shnor-nvidia.sh.tvm now mounts its five
tvm*.shfiles instead of all of05-frameworks.tvm.shsources exactly four siblings, and none of them sources anything further in that directory — the rest of the tree istorch/andflutter/.
Still open here: the genai / wasm / js RUNs have the same over-broad shape and were left alone, because unlike the two above they are not on the critical path that the next build will exercise.
The next media build is the proof. A missed transitive source fails hours
in, so treat a green media stage as the close condition for this entry.
F5. The duplication baseline is frozen, not reviewed [L, measurable]#
verify_code_dupes.py reports OK, but that means no NEW or GROWING copy —
not “no duplication”. Re-measured 2026-09-02: docs/scripts/code-dupes.allow now
holds 260 pairs (was 251), of which 236 still say “baseline 2026-08-31, not
yet reviewed”. So nine pairs were added since the baseline and the unreviewed
tail has not moved at all — every pair reviewed so far was a NEW one.
PROGRESS 2026-09-02. The top pair is done: build-runtime-artifacts.sh ↔
build-runtime-manifest.sh went 199 → 28 shingles. The 17 option lines both
documented identically now live in runtime_shared_usage_options
(01-core/runtime-build-fns.sh), beside the runtime_shared_usage_env_overrides
that library already owned — those flags are handled BY the library, so the
duplication was a symptom of documentation living in the wrong place. --help
verified unchanged in content on both scripts.
That one change also retired three allowlist entries the gate then reported
as stale (build-cross-chain ↔ both runtime scripts, and manifest ↔
build-sdk-artifacts) — dedup at one site can dissolve overlaps at others, so
expect the list to shrink faster than the number of entries you touch.
Two entries were ADDED in the same pass, both honestly labelled: my own new test
cases had duplicated four lines of extract-and-count boilerplate (fixed with a
helper instead of an allowlist line), and runtime-build-fns ↔
build-cross-chain now share 14 shingles — recorded as unfinished, not
deliberate, because the chain orchestrator could call the same helper.
Second pair done: iree/android/build-android.sh ↔
litert/android/build-android.sh, 179 → 12. The duplicated
resolve_host_compiler (source-or-fallback, ~25 lines) moved into
android-build-preamble.sh, which both already source and every android stage
shares. The iree copy’s own comment said “aligned with the litert copy” — the
duplication was known and simply had no owner.
That consolidation exposed a four-site family, which is the more useful
finding: the same host-compiler-preference logic lives in
01-core/compiler-resolution.sh (canonical), the preamble’s fallback,
ffmpeg-probe-framework.sh and build-app-wheelhouse.sh. The preamble copy is
the bootstrap paradox in its purest form — a fallback must duplicate the
thing it stands in for, or it is not a fallback. The other two are not, and
that family wants one owner.
Note the accounting: this pair’s number fell by 167 while
preamble ↔ build-app-wheelhouse rose 29 → 40. Total duplication went DOWN (two
copies became one) even though one pair’s number went UP, because the surviving
copy now sits in a file that already overlapped there. Read the totals, not a
single row.
The unreviewed tail is CLOSED: 225 → 0 (2026-09-02). Be precise about what that means, because the distinction is the whole value of the entry:
how |
count |
what was actually done |
|---|---|---|
READ |
4 |
the pairs whose longest shared run exceeded 12 lines and were not already covered — each carries its own finding |
MEASURED |
221 |
longest shared run computed per pair; the reason records the number so anyone can re-derive it |
Measuring is a real review — it answers “is this a copied block worth extracting a helper for?” — but it is not reading each file, and the reasons say so (“NOT read line-by-line; revisit if it grows”). The distribution it produced:
longest shared run |
pairs |
reading |
|---|---|---|
≤6, structural only ( |
87 |
the shape of sibling functions, not a copy |
≤6, substantive |
88 |
a shared idiom, below any extraction threshold |
7–12 |
41 |
small; watched via a pinned budget |
>12 |
9 |
four already covered, four read here, one is the family below |
The recurring shape across the biggest offenders is source-or-fallback:
path-helpers, compiler-resolution and host-python each have a canonical file
plus an inline copy for when it is absent. That is the bootstrap paradox and the
copies are load-bearing — the useful question is not “dedup them” but “is the
fallback still reachable at all, now that every image ships the canonical file?”
Eight stale entries were retired along the way — three from the first pair, five from the second. Final count: 260 → 243 pairs, 0 unreviewed.
Work the tail from the top; each line deleted or shrunk is real progress and the gate enforces the new, lower budget automatically:
shingles |
pair |
|---|---|
199 |
|
179 |
|
95 |
|
90 |
|
88 |
|
Two pairs alone are 378 shingles — 6% of the whole baseline. Reviewing an entry means one of: shrink the twin and lower the budget, or replace “not yet reviewed” with the reason it is deliberate. Both are improvements; leaving it is not.
The advertised-version gate covered 6 of 31 keys [DONE 2026-09-01] —
verify-advertised-keys.py(preflight slugadvert-keys) now fails when any version-shapedENV/ARGinlinux/Dockerfile.*is neither checked by the smoke nor excused with a reason, and when an excuse goes stale. 16 keys are checked, 16 excused. Probes were measured against the shipped image, and each mutation was proven to go red (test-advertised-keys.sh).VULKAN_VERSIONcould not disagree with itself [DONE 2026-09-01] — its HAVE side parsed the version out of/opt/vulkan/active, a directory named by the ARG under test. Now measured from the loader (vulkaninfo), falling back toVK_HEADER_VERSION. The class matters more than the instance: any HAVE probe that re-reads the advertisement is a gate that cannot fail.An absent
PYTORCH_EXTRAsilently shrank the venv gate [DONE 2026-09-01] — the empty case was folded in with thenonesentinel, so an image that failed to advertise the extra had it dropped from the checked set. NowNOADVfails; only the literalnoneis a torch-less image.pkg-namestreated a partial index fetch as authoritative [DONE 2026-09-01] — one failed component (universe, say) truncated the name set, cached it, and would then report live packages as dead. Now all-or-nothing with an honest SKIP. Also: the vendor-repo exemption covered whole FILES, so the plain Ubuntu packagessetup-rocm-repo.shinstalls before adding the vendor repo (curl,gpg,ca-certificates) were unfailable; it is now name-scoped. Andapt_install_availablewas in no installer table at all — 6 cross-toolchain packages were extracted nowhere (118 -> 121 call sites, 522 -> 529 names).riscv64 now builds at RVA23 (RVV ON) [DONE 2026-09-01] — the earlier entry here had the risk INVERTED and is corrected: the shipped image’s own glibc and loader already require RVV 1.0 (997
vsetvliin apt’slibc.so.6), so a board without a vector unit could never run this image. Our binaries were the only sub-baseline objects in it. The cross GCC now defaults torva23u64_zifencei/lp64d— the exact string apt’s libc carries — with OpenCV, ORT, Rust and the gst-plugins-rs cargo wrapper wired separately because they gate vector paths on their own switches. A new smoke gate readsTag_RISCV_archoff the shipped objects. See docs/riscv64-rva23-baseline.md. OPEN: TVM and IREE emit code at RUNTIME, so they need a codegen-target change (-mattr=+v,+zvl128b/--iree-llvmcpu-target-cpu-features), not a compile flag. Also budget a COLD riscv64 build — this invalidates the warm cache.The destructive-delete guard now works on Linux [DONE 2026-09-01] — it was PowerShell-only and
pwshis absent on this build host, so the answer to the 2026-08-21 host wipe was INERT exactly where the incident happened, and the second registration it claimed in the user-level settings did not exist..claude/hooks/guard-destructive-deletes.pyis a Linux port with the same protocol, wired ahead of the PowerShell one. It denies the filesystem root, system directories,$HOME/~roots, credential dirs, the containerd and buildkit stores, block devices and package removals, whilelinux/host-config/prune-safe.sh,nerdctl rmiandrm -rf ~/.cache/kata-buildcache/*still pass. Pinned bytest-delete-guard.sh; emptying the rule table turns 7 assertions red.LB1 — correctness probe [DONE]
--correctness/--correctness-only(exit non-zero on a wrong answer);run_benchmarks.shgates the sweep on it (BENCH_SKIP_CORRECTNESS=1bypasses). Validated: Qwen3-4BQ4_06/6,IQ3_XXS0/6 BROKEN — the case every speed metric rated as a good run.LB2 — TTFT/prefill [DONE]
ttft_s,prefill_tok_per_sec,decode_tok_per_sec(the pre-existingtokens_per_secdivides by the whole request and so hides prefill inside what reads as a decode rate).LB3 — time to a finished answer [DONE]
wall_s_to_answer+thinking_char_share. A live run showed 77–86 % of output was<think>.LB4 — multi-endpoint + concurrent aggregate [DONE] new
bench_lanes.py --lanes. Reproduced the manual finding within 0.5 %: NPU +0 % when a CPU lane joins, CPU −18 %, aggregate 39.9 tok/s = 1.57x the best single lane.LB5 — batching probe [DONE]
bench_lanes.py --batching. VerdictSERIALISEDon GenieX: second request’s TTFT 74.27 s vs the first’s 74.10 s total.LB6 — GGUF introspection [DONE] new
inspect_gguf.py: header-only read, tensor-type histogram, OK / LIKELY OK / RISKY verdict, exit 1 on RISKY. Correctly separates the brokenIQ3_XXS(97.6 % sub-4-bit i-quants) from the working 27BUD-Q4_K_M(0.8 %).LB7 — de-Ollama’d [DONE]
LLM_BASE_URL(old name still honoured);detect_model_via_apiasks/v1/modelsfirst and takes abase_url. The hardcoded"gemma4:26b"probe is gone — it returned that name on any 200, mislabelling every non-gemma host.LB8 — worker vs listener [DONE]
top_cpu_processes()primes before and reads after each request, so the summary names the PID that actually burned CPU. Encodes the trap: GenieX’s port owner read 11 % of 800 % while its worker sat at 752 %.LB9 — cross-platform hardware info [DONE]
platform/psutilfallback after the/procreads, plus an explicitincompletelist so a gap is visible instead of silent.
Viewer + test debt closed 2026-08-31 (second pass). The first pass wired
the new metrics into the result JSON but not into the React viewer, although
LB2/LB3 explicitly said “and the viewer” — the data flowed in and surfaced
nowhere. Now: a correctness banner above every speed number, time-to-answer as
the leading column, TTFT/decode/prefill/thinking-share in the comparison and
drill-down tables, the busiest process per request, and legacy runs degrading
to - (charts drop them rather than drawing a 0 that would claim an instant
first token). bench_lanes.py and inspect_gguf.py gained 15 unit tests
(synthetic GGUFs, an in-process SSE server), taking the suite to 30 unit tests
that need no running stack. A server-side smoke render (npm run smoke)
renders every component against the real manifest, because vite build only
proves the JSX compiles — it caught a silently-failed edit that made the
comparison table render empty cells.
Probe calibration fix: truncation is now reported apart from wrongness (exit 2 = INCONCLUSIVE, exit 1 = genuinely wrong). A model cut off mid-thought was not wrong, it was unmeasured, and scoring it as a failure made a healthy model look degraded. The arithmetic probe also moved 847293 -> 2317: a healthy 4B could not finish the former inside 4000 thinking tokens, so the check cried wolf on good models.
Two pre-existing bugs found while doing this, both fixed:
the SSE parser matched
"data: "with the space (optional per spec). GenieX omits it → nothing parsed, 0 tok/s reported, no TTFT possible. The harness was effectively blind to every non-Ollama server.servers ignoring
stream_options.include_usageyielded 0 tokens; now falls back to a counted chunk total flaggedtokens_estimated.
Closed since the original harvest: context-length quality scaling
(bench_coding.py --context-tokens, § 1e of the GenieX page) and tool/function
calling correctness (bench_tools.py, § 1f) — the latter found the one result
that does not crown the coding winner.
Still open for a general LLM toolkit (listed so it is not forgotten):
LLM-BENCH: LB10–LB12, closed 2026-09-02#
Re-checked rather than trusted, after F4 and F5 had both turned out stale the same day. Two of the three were already done and the entries had simply not been updated.
✅ LB10 — embedding benchmarks. Already implemented:
bench_embeddings.py(194 lines) measures shape, speed and meaning — whether related texts land closer together than unrelated ones, the check a shape test cannot make. Its docstring quotes this entry’s own justification verbatim, so the entry was written and then satisfied without being closed. 13 tests green.✅ LB12 — energy per token. Resolved honestly rather than implemented:
bench_provenance.energy_proxy()reports CPU-seconds under that name and refuses to report joules, because the host exposes no power rail (no RAPL on aarch64, no battery counter through WSL2, Snapdragon sensors not surfaced). “Reporting joules would be inventing them.” Recorded as a PROXY so nobody later mistakes it for a measurement. That is the correct closure for this item; a real number needs hardware that exposes one.✅ LB11 — run-to-run regression comparison.
bench_compare.py(315 lines) existed, had its own test file, normalises BOTH report envelopes and exits 1 on a regression — and nothing ever called it.run_benchmarks.shdid not mention it; the “baseline” strings in that script are context-length labels. The tool was a tripwire nobody armed, the exact “gate that never runs” shape this repo keeps rediscovering.Closed by adding what was missing.
pair_directories()+ a--dirmode compare two run directories report-by-report, matching on file name, since the sweep writes one report per config and_manifest.jsonis the viewer’s index rather than a report. A config that appeared or vanished between runs is printed as a change, not silently skipped — otherwise a shrinking sweep would pass.run_benchmarks.shnow calls it whenBENCH_COMPARE_TOnames a previous output directory: advisory by default, failing the run underBENCH_COMPARE_STRICT=1, because a sweep is not a gate unless the operator says so.Six tests added, and all three mutations bite: returning 0 unconditionally fails the regression test, dropping the vanished-config line fails the swallowing test, and treating
_manifest.jsonas a report fails the pairing test. Verified end to end outside pytest as well — a 10/10 → 2/10 run prints*** REGRESSION ***with its Wilson intervals andBENCH_COMPARE_STRICT=1exits 1.
Full text of the three entries as they stood:
LB10 — embedding benchmarks [M·★★]
tests/test_v1_api.pyexercises the embedding endpoints but nothing measures them. Relevant the moment a RAG or code-search path is added.LB11 — run-to-run regression comparison [S·★★] Every tool writes a JSON report and nothing diffs two of them. Without it a model swap or a runtime bump can silently cost accuracy or speed. Probably the highest-value item left: it turns a pile of one-off measurements into a tripwire.
LB12 — energy per token [M·★] The interesting axis on a battery device, and the NPU’s real argument over the CPU lane (165 % vs 752 % of 800 % CPU says something about power but does not measure it).