Changelog archive — 2026-09-08 through 2026-09-21#
Entries moved out of the root CHANGELOG.md on 2026-10-17 so the live file stays
readable (its own rule: archive past ~700 lines, never delete). Newest-first, running
back to where changelog-archive-2026-09-07.md picks up. One mechanical edit, no
rewording: relative links are re-based on this directory.
2026-09-21 - The arm64 CUDA/cuDNN lane builds natively on SBSA#
The GPU lane had never been built on arm64. Built on a Jetson AGX Orin, for
arm64 only, base through media with NVIDIA’s SBSA CUDA 13.3, cuDNN 9.26 and
NCCL. ORT’s CUDA provider, 11 OpenCV CUDA modules and TVM’s CUDA runtime carry
sm_80 sm_86 sm_87 sm_89 sm_90; ffmpeg links NVENC/NVDEC.
Two defects blocked the GPU layer on EVERY arch: NVIDIA’s repository path used
the Ubuntu codename (a 404) and both keyring SHA pins were stale. The rest was
arm64 enablement: sm_87 in CUDA_ARCHITECTURES, CUDA_ARCH_BIN for OpenCV,
ENABLE_TENSORRT=false and CUDA_INSTALL_COMPAT=0 for a Jetson, the image’s
GCC 16 kept with NVCC_PREPEND_FLAGS=-allow-unsupported-compiler, a
CUDA_MB_PER_CICC job budget against cicc peaks, /tmp restored after
COPY --link, and ffmpeg’s nonfree --enable-cuda-nvcc dropped. The chain
still has no NVIDIA stage; the layer is inserted by hand
(docs/linux-accelerator-images.md § NVIDIA on arm64 (SBSA)).
2026-09-21 - HailoRT on Windows (Phase 3): library + CLI, both arches#
The Windows lane now builds HailoRT like the Linux lane does - from the
SHA-pinned v5.4.0 tarball, with the same ten externals staged offline at the
same commits - as a media-core-built-hailo branch between opencv and the core
merge, installed to C:\runtime\hailo. Probe-proven on amd64
(out/build-logs/probe-hailo-amd64-*: libhailort.dll PE 0x8664 +
hailortcli.exe); the arm64 cross build rides the same branch.
Three upstream Windows/clang-cl gaps had to be patched, each found live and
documented in docs/hailo-support.md § Phase 3:
quantization.hpp’sbankers_roundkeys on_MSC_VER- which clang-cl defines on every arch - so the x86 intrinsics fail on ARM64 and on a bare x64 clang-cl without-msse4.1.driver_os_specific.cppwrites explicit-specialization members withouttemplate<>, which clang-cl enforces.os/windows/filesystem.cppomitsLockedFile::~LockedFile()while the header declares it, sohailortclicannot link.
Not yet: TAPPAS (Linux-only), the pyhailort wheel, the GStreamer hailonet
element on Windows, and device execution.
2026-09-20 - Windows-on-ARM64 CUDA/cuDNN: the cross lane is wired (#176)#
The arm64 lane built its CUDA stack for the first time, on the x64 host, with no
arm64 device involved. Final run bk-20260920-203631 (2:45:53): smoke 120/0/15,
arch gate 1047/0, and every CUDA artefact 0xAA64.
Payload:
Install-Cuda.ps1 -TargetArch arm64keeps the x64 toolkit (headersnvcc are the host tools) and stages the arm64 redist components (
cuda_cudart,libcublas,libcufft,libcurand,libnvjitlink,libnpp,libcusolver,libcusparse) into the same root aslib\arm64/bin\arm64, plus the arm64 cuDNN archive. NVIDIA names the arm64 cuDNN archive_cuda13.4where the x64 one is_cuda13— the URL builder is arch-aware now. Every archive SHA-pinned inversions.env(redistrib_13.4.2.json/redistrib_9.26.0.json).
Build:
Get-NvccHostCompilerPathis the one owner of the nvcc host compiler (native x64 cl, crossHostx64\arm64cl) andGet-NvccCudaCmakeArgsaddsnvcc --use-local-envon cross — the documented x64→ARM64 flow. ORT, GenAI, OpenCV and TVM each enable CUDA on the cross lane only whenTest-CudaWindowsArm64Payloadfinds the payload — a positive signal, never a host GPU probe. Classic TensorRT stays OFF on cross (x64-only).OpenCV: NPP was the missing link (
CUDA::nppial/nppif), and cudafilters’wavelet_matrix_2d.cuhused the x86-only_mm_popcnt_u64; the guard change alone was not enough because nvcc’s device pass rejects__builtin_popcountlltoo, so the ARM64 branch is a software popcount (patch probe-proven inout/probe-arm64-popcount). TVM’s legacy FindCUDA hardcodeslib\x64, so the cross branch names the arm64CUDA_CUDART/CUBLAS/CUDA_LIBRARY, host compiler and--use-local-envexplicitly.Gates:
-Gpu -TargetArch arm64is a supported driver combination; the cross smoke getsEXPECT_GPU(a lost CUDA env reds instead of skipping) and its cuDNN run-probe became a link + PE-machine assert (Assert-NativeLinkRun -CrossLinkOnly), because an aarch64 DLL cannot execute on the x64 host.
2026-09-20 - the GPU lane’s smoke stops demanding an EULA payload nobody staged#
The -Gpu amd64 chain built green through final – CUDA EP, cuDNN, OpenCV
WITH_CUDA, GenAI-CUDA, the torch-app ORT+CUDA venv and the IREE CUDA-target
compile all passed – but the smoke gate returned 225/228: all three reds were
the TensorRT asserts, on a host with no TensorRT zip, which is the documented
NORMAL state. windows-builds.md § TensorRT setup already ruled that the
zip-less lane must pass (“do NOT re-harden this into a fail-fast”), so the
asserts were the defect:
Test-TensorRtTreeStaged(WindowsSmokeTest.Common) applies the same presence rule as the build’sResolve-TensorRtRoot: root set, exists, non-empty. The guaranteed-emptyC:\tensorrtis NOT staged.§8/§20 branch on it: staged ->
cuda=1 trt=1+ the provider DLL + the python TRT EP; zip-less ->cuda=1 trt=0+ no provider DLL + python CUDA-EP only. CUDA stays hard in both branches, nothing is skipped, and a staged tree with a silently-disabled EP still reds.Pester coverage for the decision (unset, missing, empty, one entry); the Windows suite is green at 864/864.
2026-09-20 — the torchvision pair fix, and the Hailo variant#
The Linux rebuild’s version-pin assertion caught a mismatched
torch/torchvision pair. The 2026-09-18 wave bumped TORCHVISION_VERSION to
v0.29.0 while PYTORCH_VERSION stayed v2.13.0; pytorch/vision’s compatibility
table pairs torch 2.13 with torchvision 0.28 (0.29 needs torch 2.14), and
both the wheelhouse’s built-in fallback and OrchestrANT’s lock install 0.28.0 —
so the runtime image shipped 0.28.0 and assert_pinned_versions failed the
amd64 smoke with installed 0.28.0+cpu NOT in expected ['0.29.0']. Reverted
the pin to v0.28.0 with a bump:hold note that re-derives from the table at
every torch bump. The wrappers were already built and pushed; the manifest was
repaired with build-runtime-manifest.sh --repair --push-manifest (no image
rebuilds) and the published index now carries this run’s three wrappers.
A second gap the incident exposed, deliberately not fixed here.
Dockerfile.media never declares PYTORCH_VERSION/TORCHVISION_VERSION as
ARGs, so the app-wheelhouse RUN cannot see them and falls back to the script’s
built-in defaults — which also means the layer cache does not re-key on a
version bump. It stayed invisible only while the pin equalled the fallback.
Wiring it re-keys the wheelhouse for all three arches (hours under QEMU); it
needs a planned window, and it is tracked in
docs/refactoring-backlog.md.
Hailo support landed as the opt-in :hailo variant
(docs/hailo-support.md). linux/Dockerfile.hailo
builds HailoRT 4.24.0 (the hailo8 line — Hailo-8/8R/8L) plus hailortcli and
the hailonet GStreamer element against the media image’s GStreamer, offline
against verified protobuf 21.12 and gRPC 1.54.0 sources, then copies the
payload into the published runtime image. Run it with --device=/dev/hailo0 on
a host that loaded the GPL-2.0 hailo_pci driver. The standard :latest-cross
is untouched — the variant builds after the runtime lane, like :nvidia/:amd.
pyhailort is deliberately not built (the public package ships only in Hailo’s
.deb). Pins live in versions.env, licence rows in docs/deps/deps.json
(MIT, and LGPL-2.1-or-later with a source pointer), and
sync_versions.py --check is green. Both arches built, verified and
published the same day as :hailo-amd64 / :hailo-arm64, joined into the
multi-arch :hailo manifest — hailortcli --version reports 4.24.0 and
gst-inspect-1.0 hailonet resolves the element in both shipped images. Three
build attempts shaped the final design: the offline externals must sit at the
literal <src>/hailort/external/<name>-src paths (16 of them, commit-verified);
the base runtime runs as uid 1001, so the payload copy needs an explicit
USER root; and the builder is the runtime image itself, not
cross-android-<arch> — those are amd64-hosted cross toolchains, and HailoRT’s
nested FetchContent cmake cannot be reached by a cross CMAKE_C_COMPILER
(as: unrecognized option '-EL', then a stale cross cache producing a
Ninja/RPATH “not ELF-based” error). Native is slower on arm64 under QEMU and
correct. riscv64 has no HailoRT support at any version; the script refuses it.
Torch 2.14 + torchvision 0.29 land in :latest-cross, and Hailo becomes
standard. The pins move to the valid pair (PYTORCH_VERSION=v2.14.0,
TORCHVISION_VERSION=v0.29.0); riscv64 has no upstream wheels, so it
source-builds the same pair (an interim <KEY>_RISCV64 override keeping it at
v2.13.0/v0.28.0 was removed when WH1 closed — see the entries below). assemble-torch-app.sh gained
enforce_torch_version_pins: on the arches with cp314 wheels (amd64, arm64) the
runtime force-installs the pinned pair from the CPU index after the lock-driven
sync, so the shipped venv matches the build pins the smoke asserts — the app
lock (OrchestrANT) lags them. And HailoRT + hailortcli + hailonet are now built
in Dockerfile.torch for amd64 and arm64 (riscv64 skips), so every
:latest-cross wrapper carries Hailo by default; the :hailo variant stays as
a convenience tag.
Hailo switches to Hailo-10H, Hailo-8 is dropped, pyhailort is built, TAPPAS
is declined, the Dataflow Compiler is staged. HAILORT_VERSION moves to
5.4.0 (master, the Hailo-10/15 line) and the externals are re-derived for
that tree — no gRPC (master has no grpc.cmake), with minja, tl-expected and
the newer cli11/json pins; libusb/tokenizers/slint/montserrat stay unstaged
because their features (USB, servers) are off. build-hailort.sh now also
builds the pyhailort wheel from bindings/python/platform/
(scikit-build-core) and installs it into /opt/venv with upstream’s
requires-python <3.14 relaxed and an import test — the image runs 3.14, and a
failed import is reported, not hidden. TAPPAS stays out: v5.4.0 supports
GStreamer 1.16–1.20 while this image ships 1.29.2; the hailonet element is
the pipeline integration the repo needs. The Dataflow Compiler is
login-gated and x86_64-only, so it gets the QNN-style staged drop point
linux/hailo-sdk/ (the amd64 wrapper installs a staged wheel; the Model Zoo
stays a host-side tool).
WH1 is closed: the app wheelhouse’s refs are wired, and all three arches now
carry torch 2.14 / torchvision 0.29. Dockerfile.media’s app-wheelhouse
stage gained ARG PYTORCH_VERSION / TORCHVISION_VERSION / IREE_VERSION and
exports them to build-app-wheelhouse.sh, which previously fell back to its
built-in v2.13.0/v0.28.0/v3.11.0 defaults and did not even re-key on a
pin bump. riscv64 has no upstream torch wheels, so its media stage SOURCE-BUILDS
the same refs — the per-arch PYTORCH_VERSION_RISCV64 overrides are gone, and
with them the 2.13/0.28 split the first rebuild exposed.
TAPPAS is built against GStreamer 1.29.2 — with build-args, not patches.
The plan had it as blocked (its README names 1.16-1.20 as the supported
matrix), but that is the TESTED matrix: the meson constraint is >= 1.0 and a
spike proved a clean build. What actually had to change: libargs is a Meson
ARRAY and its elements must be comma-separated (a space-joined value silently
keeps only the last element, and every HailoRT header then goes missing);
libxtensor/libcxxopts/librapidjson must point at core/open_source/;
that tree’s header-only dependencies (xtensor, xtl, cxxopts, pybind11,
rapidjson, Catch2) are staged at pinned commits because upstream clones them
from BRANCHES (rapidjson: master); and libzmq is built in (MPL-2.0) for
the hailoexportzmq/hailoimportzmq elements, since the runtime image has no
usable apt. gst-inspect-1.0 hailotools passes in the built image, and the
plugin is staged into the same plugin dir as hailonet. Also fixed on the way:
HAILO_BUILD_TOOLS=OFF (the v5.4.0 tarball ships no tools/ dir, so ON makes
CMake die), the missing mkdir -p that made the HailoRT download fail with
curl error 23, and pyhailort’s requires-python <3.14 metadata (relaxed before
the wheel build, import-tested after).
2026-09-19 - the Windows dual-lane rebuild: two build-killers fixed, CUDA on the network installer#
The first rebuild of the 2026-09-17/18 wave ran green on BOTH Windows lanes — amd64 6:29 (smoke 198/0/1), arm64 2:43 (smoke 102/0/16) — after the chain found two build-killers, both fixed and probe-verified:
The VS stable channel stopped REGISTERING
Microsoft.VisualStudio.Component.VC.Tools.x86.x64while its files stayed on disk, so CPython’sfind_msbuild.bat(vswhere) died withFailed to find MSBuild~80 min in.Install-Vs.ps1now--adds the component explicitly and the base asserts the vswhere query — a file check passes on the broken shape, which is exactly how it escaped.The 3.9 GB CUDA 13.4.2 full installer dies in-container with
0xE0E00064(self-extraction on the wcifs layer; silent, no logs; the network installer succeeds with identical flags).Install-Cuda.ps1uses the network installer with a pinned 37-subpackage list instead: 10 MB download, ~2 min install, CCCL/Thrust included, and the SHA pin moved with it.
Also landed the same day: the ghcr credential helper (credsStore: wincred)
was replaced by a direct auth entry so -PushRef can publish; CUDA 13.4.2’s
renamed installer and the sccache 0.18 zip were proven by the runs; the sccache
CUDA canary is still owed before cuda_llm is re-wrapped.
2026-09-18 - the dependency wave, and sccache 0.18 retires its source build#
Reported by Renovate (local CLI, 35 updates), applied through the two tools that own the writes; nothing was proven by a build. The Windows base and the Linux chain must prove it in the next window.
Renovate’s allowed set (12 lines): actions/checkout v6.0.2,
docker/login-action v4.0.0, github/codeql-action, actions/upload-artifact,
astral-sh/setup-uv pins, and SYFT_VERSION=v1.52.0 (a self-contained key its
file-scoped packageRule clears). The other 23 reported updates are
dependencyDashboardApproval-gated by design and were applied by hand, which is
the documented ritual for them.
Safe + report tiers (bump_versions.py --write-all, checksums refreshed with
each version): uv 0.12.16, node 26.9.0, ollama 0.34.2, flutter 3.47.4,
LiteRT-LM 0.17.1, the ubuntu base digest, syft v1.52.0.
Hand bumps (Renovate-visible, no programmatic checksum source):
LLVM 23.1.1 on BOTH lanes - LLVM_RELEASE + LLVM_COMMIT=e7ce3600 +
LLVM_WINDOWS_VERSION + the 23.1.1 source-tarball SHA + the aarch64
compiler-rt SHA (now pinned instead of warn-unverified); onnxruntime v1.30.0;
FFmpeg n9.0.1; openvino 2026.4.0; ComputeLibrary v53.3.0; torchvision v0.29.0;
openh264 2.6.0; ruff 0.16.8; sccache 0.18.0 on both lanes.
The one patch this wave retires: the Windows sccache source build. 0.18.0
ships mozilla/sccache#2722 + #2811 + #2816 - the three fixes SCCACHE_GIT_REV
was pinning - so Install-RustToolchain.ps1 now installs the released
x86_64-pc-windows-msvc zip against a new SCCACHE_WINDOWS_ZIP_SHA256, the
scoop sccache install is gone, and SCCACHE_GIT_REV is deleted from
versions.env, the base ARG, the driver, and bump_versions.py. The CUDA
canary bar stands: cuda_llm stays on bare nvcc until a candidate sccache
passes verify-cuda-cache + the ONNX fused_moe canary; history and the bar are
in windows-build-resources.md.
The patch audit found nothing else to retire. Every other local patch still
fixes something the pinned version lacks - including on LLVM 23.1.1, which does
NOT carry llvm#219275 (merged 2026-09-16, after the tag) and whose #219276 is
still open. The full per-patch verdicts are in
upstream-windows-patches.md and
upstreamable-patches.md, both refreshed.
Not bumped, deliberately: protobuf 36.2 - PROTOC_VERSION is slaved to
LiteRT-LM’s vendored pin (bump:hold). The renovate row reported a
sha512-... value, which is a datasource artifact, not a version; the approval
gate keeps it from being written. CUDA 13.4.2 landed later the same day
once the 404 was explained: 13.4 renamed the Windows installer to
cuda_<v>_windows_x86_64.exe, Install-Cuda.ps1 now picks the name by
version, spec_cuda follows, and CUDA_INSTALLER_SHA256 was refreshed from the
downloaded installer.
CON4 is measured and closed: winamd64 was Dart 3.12.2 / Flutter 3.44.8 against
:latest-cross Dart 3.13.3 / Flutter 3.47.3. Both pins are now 3.47.4, so the
next Windows rebuild is what aligns them; the backlog entry is gone.
F1’s last outside-the-closure row closed. docs/scripts/bump_versions.py main (160 lines) is 32: linux/scripts/tests/test-bump-versions.sh drives the
real main() in-process over fake tiers (40 assertions: the --only refusal,
the lookup-failure sweep contract, --check/--write/--write-all, bump:hold,
the UNCLASSIFIED self-audit and the offline half of --audit-sha-pairs), and the
split into _parse_args / _lookup / _sweep / _safe_row / _report_row /
_write_phase plus the reporting helpers kept rc, stdout, stderr and the
written bytes identical to HEAD’s for the same inputs (checked side by side).
The function-size.allow and code-complexity.allow rows are deleted, and the
file-size.allow row is re-trued to 1116.
The Linux backlog’s two owner questions are answered; the list is now two
registers. 06-packaging/package_archive.sh HAS a consumer: OxidANT’s
.github/workflows/rust_ubuntu26_04.yml:204 runs the hub path directly, so the
--appdata-file/--app-id/--appimage-extract-and-run CLI contract is live and
the script stays — the shellcheck-warnings.allow row’s “Needs the OWNER” is
replaced by that evidence, and docs/code-quality-tooling.md names it. The QNN
SDK pin stays at v2.49.0.260730: only a newer SDK needs a re-pin, none is staged
and the fetch is login-gated. The consumer inventory did not catch the consumer
because its linux-script class covers linux/scripts/*.sh only and treats the
stage trees as reached THROUGH the top level; OxidANT proves a direct call
exists, and the class note now says so.
The two 2026-09-03 audit questions that section also carried were already
answered on 2026-09-17 — Get-Pin strips the surrounding quote pair (proved by
the passing CUDA_ARCHITECTURES assertion over the quoted value), and
sync_versions.py’s check_script_defaults glob matches the renamed
windows/scripts/**/Build-*FromSource.ps1 scripts with the TVM_COMMIT→TVM_REF
exception the PinParity suite documents.
The first rebuild caught the LLVM pin — and the catch is the pin doing its
job. LLVM_COMMIT had been set to e7ce3600, which is the ANNOTATED TAG
OBJECT of llvmorg-23.1.1, not the commit it peels to. The compiler stage’s
clone-time assertion (llvm_assert_commit_pin compares git rev-parse HEAD
against the pin) died with resolved to 6dfe1677…, but LLVM_COMMIT pins e7ce3600…. Fixed to the peeled commit
6dfe1677ab8dffbc6ec13d53a1e0215d75147689 in versions.env and in
test-native-build-host.sh, which had locked the wrong hash. The 22.1.8 and
23.1.0 rows above it were correct peeled commits, so only the 23.1.1 row was
mis-copied from git ls-remote’s first (tag-object) line.
2026-09-17 — the F4 extraction wave, CON1-CON6, and the Windows defect batch#
Nothing in this entry was proven by an image rebuild. The Linux chain was not
rebuilt and no Windows image was rebuilt — the container-side files are cache
inputs of the next window — so every verdict below is a static gate, a bash suite
or a Pester assertion on an idle tree. The 2026-09-05 lesson is the standing
warning: a full green battery preceded two build-killing bugs found in minutes.
The full narratives live in
refactoring-backlog-archive-2026-09-17.md
and
windows-backlog-archive-2026-09-17.md;
the two open backlogs keep only pointers and open work.
F4: all 27 PowerShell extractions applied. Every row the 2026-09-09 review
identified and left unapplied is done: Get-CommandParameterArgumentMap +
Get-AstDefaultValue in the pin-parity suite (233 → 154), WriteArMemberHeader
(85 → 74), the hoisted $newFakeSccache (28 → 26), $newStageTree,
Assert-ManualTestOutcome, Get-ProjectSourceFiles (118 → 43), the uv-delegate
repoint to New-UvBuildDelegates (a row retired), Enable-SccacheCompilerWrapper
(30 → 20), the LSM module’s five-function surface, the docker/buildctl candidate
owners (Get-PreferredToolPath, Resolve-BuildCtlPath), Show-Exclusions,
Assert-FileSha256 at four call sites, Install-AArch64CompilerRt (with
Install-ScoopTools keeping a forced copy the base-stage closure cannot call),
Assert-NoCacheStageMatched, and the coordinator’s Start-HostServices.
docs/scripts/code-dupes.allow records 39 stale rows removed, 22 budgets
re-trued, 2 added; the finding arm is clean and the allowlist stands at 591
pairs. Honest residue, both in the archive: twelve rows survive because the
overlap shrank without going under the threshold (eleven still say “NOT YET
APPLIED” in their frozen reason, which only the allowlist writer may re-word),
and one budget row measured 12 against a recorded 13 at this tip — a suppression
flip the gate reports as a stale freeze, not a finding.
AS1 residual closed. The compiler-stage host stanza derives from
build_arch_oci instead of a literal amd64; cross-apt.sh gained
cross_apt_sources_file_for_arch / cross_apt_mirror_url_for_arch, so an
amd64/i386 cross target gets a real archive file and not just an added
architecture; and FAST_UBUNTU_REWRITE_SECURITY flipped to default-true, so the
host -security comes from the same archive as the target pocket (the explicit
false opt-out stays, and so do the tests on both arms).
CON1, CON2, CON3, CON5, CON6 closed; CON4 blocked. The web lane installs
RUST_NIGHTLY_TOOLCHAIN (nightly-2026-06-28) — a dated pin is a no-op on a
warm image, where the floating channel is updated and dies on EXDEV out of a
read-only layer. The flatpak refs are probed in both scopes through one owner
(app_packaging_flatpak_ensure_refs), deleting the ~1.9 GB per-user duplicate
pull. GSTREAMER_ROOT_ANDROID was verified already exported (Dockerfile.android
and Dockerfile.package) — no edit. API 37
(ANDROID_EXTRA_COMPILE_SDK=37.0, build-tools 37.0.0) ships beside 36, so the
consumer’s permission_handler_android pin can be dropped once the image ships.
KATAGLYPHIS_FLATPAK_FINISH_ARGS appends to the generated finish-args through
app_packaging_flatpak_finish_args_block, registered in
lint-env-knobs.allow. CON4 stays open, blocked: the Linux half measured
Dart 3.13.3 / Flutter 3.47.3, and the Windows half needs the exact command now
recorded in the backlog (the winamd64 image is not local to this host).
F1’s two seams cut, F2’s register gained one row. _agentic_planner_phase
(reporting planner_ran through a nameref, five new suite cases) and
_cross_build_drop_registry_cache_after_flake (nameref-taken build_cmd and
_regcache_fails); both run_agentic_loop allow rows deleted, and
_cross_stage_build_impl fell under 80 with the same work. F2 has no split
target; lib/app-packaging.sh crossed 800 with CON2 + CON6 and took a
NOT-a-split row.
Windows: #158’s thirteen audited defects landed in one closure window.
Build-Buildkit.ps1 forwards -TargetArch to -ConcurrentAux children,
registers child-forwarded -NoCacheStage entries as matched, exempts
final-tar/final-push from cache-busting, refuses -ConcurrentAux -NoSccache,
and owns the halving formula once. The DEPS_MIN_* ARG/ENV block moved above the
RUN that reads it. The toolchain lane now calls Disable-ContainerWindowsUpdate,
with the module mounts its built stage needs. Invoke-GitClone captures the
submodule-update exit code. Install-NewHost.ps1 builds the Kataglyphis hcsshim
fork at a pinned commit and asserts the env-configurable teardown knob instead of
patching constants; -ServiceEnvironment …=5m deploys it, and the buildkitd
restart is guarded. Update-HostVhdx.ps1’s rollback paths and Optimize-HostVhdx
/ Publish-ShimPatch start services through Start-HostServices (reverse order,
failures red), and the copy verify skips what robocopy skips. The agentic loop’s
captured output became a ConcurrentQueue, and -ExecutorOnly’s failure cap now
reports exit 1, pinned by two source-level Pester assertions (the cap is
unreachable in dry-run; mutations.json carries no entry for it). A genai
post-copy floor closes the last unverified major. The 36-minor opportunistic
sweep was NOT done and is recorded as such in the archive.
Windows: the rest of the batch. Eight settled sccache/CUDA probes deleted
(714 lines) and Dockerfile.probe re-pointed to Test-OnnxTuReplay.ps1; the
compiler-rt verify + System32-tar block ported into the base and merge copies
with Assert-FileSha256; patched LLVM compiles through sccache (remote-only
gate, session wrapper, stats on stderr, cache mounts in the Dockerfile);
smoke section 23 covers the baked C:\temp\scripts surface, moving the arm64
floor 66 → 69; the #168-#174 comment wave moved every essay to its owning page
(plus a docs home for Build-OpencvGstreamerPlugin.ps1); and #175’s remaining
checks landed. The 2026-09-03 doc drift is fixed — paths, variable names instead
of submodule version restatements, 23 categories, the Dockerfile.toolchain-builder
name, the QNN contradiction, and deps.json’s Linux-sccache misattribution with
every generator regenerated. The PascalCase doc anchors resolve; doc-links is
green.
Blocked or left open, deliberately. Windows #153 (the corpus is absent from
this checkout), #157 and #163 (both need a chain run and/or measurement), #162
(own re-key window), #155 (standing do-not-integrate) and #158’s minors sweep;
Linux CON4 (the Windows-side measurement above); and one host quirk found and
recorded rather than fixed: verify_code_size.py, verify_code_complexity.py
and verify_comment_size.py mis-resolve backslash-spelled allow paths under
Windows Python and report every frozen row twice, while the same runs are green
under WSL/Linux. No page owns that trap yet; the 2026-09-17 archive carries the
measurement.
2026-09-16 — four hub defects the family pass isolated#
Each was measured in a consumer and fixed here, where the code lives.
The clang runtime triple: amd64 was never normalized. LLVM builds
lib/clang/<v>/lib/<triple>/ from the string it is CONFIGURED with; the driver
searches it under the triple it NORMALIZES to. llvm-cross.sh had a per-arch
case list that covered arm64 and riscv64 and never mentioned amd64, so x86_64
alone was configured x86_64-linux-gnu and searched
x86_64-unknown-linux-gnu. Six clang jobs in BeschleunigerBallett died on
cannot find .../libclang_rt.profile.a while every gcc job passed, and
clang -print-runtime-dir said (runtime dir is not present). The mapping is
DERIVED now (llvm_cross_clang_triple) and refuses a string that is not a
Debian multiarch triplet, because falling through to the input is what shipped
the wrong value. Measured after the fix:
target |
deb triplet |
LLVM triple |
|---|---|---|
amd64 |
|
|
arm64 |
|
|
riscv64 |
|
|
The affected lane stays red until the image is rebuilt: :latest-cross moved on
2026-09-12 and carries the defect, so this corrects the NEXT image, not today’s
runs.
The shared .clang-format has never parsed. Standard: c++23 is not a
member of that enum (c++03 through c++20, plus Latest and Auto), so
clang-format exits 1 on the config itself and formats nothing. Measured in the
family image: before, .clang-format:98:11: error: unknown enumerated scalar
and the file untouched; after Standard: Latest, exit 0 and the file rewritten.
Every consumer carries the same line through the shared-assets manifest and none
could fix it locally, because editing a vendored copy is what the drift gate
exists to catch. Consequence, not damage: the formatter has never rewritten
anything, so the first working run in each consumer produces a real diff —
AccelerANTgine measured 13 of its 20 files under Src/. That is theirs to take.
PUB_CACHE lands inside the repo, and tree-walking gates found it.
flutter_lane_prepare_env defaults PUB_CACHE to <repo>/.pub-cache for a
real reason, which stands. But code_quality_find_cmake_files shipped NO
default excludes, so a consumer that ran the prologue and then the cmake-format
gate graded its dependencies’ example/ CMake files — 28 in OmniAccelerANT.
The hub creates the directory, so the hub excludes it:
CODE_QUALITY_CMAKE_DEFAULT_EXCLUDES is ADDED to whatever a consumer sets,
never replaces it. Any OTHER tree-walking gate still needs its own row, and
docs/shared-script-libraries.md now says so where the prologue is described.
uv_run leaked the image’s virtualenv. uv_sync_project clears UV_PYTHON
and VIRTUAL_ENV for its call; uv_run was uv run --active with both still
in scope, and uv honours UV_PYTHON OVER an activated venv. Measured in the
family image as uid 1001: the old form resolves to /opt/venv/bin/python3, the
image’s root-owned system venv; the fixed form resolves to the run’s own
.venv/bin/python3. That is both reported failures — analysers dying on
Permission denied under /opt/venv, and a per-version venv silently rebuilt
without the extra pytest lives in. This unblocks the deletion of WebDavClient’s
two local wrappers, which carry notes saying exactly that.
2026-09-16 (later) — ten mutation entries the batch rotted, re-pointed#
The Ubuntu lane’s preflight was red on the mutation gate for a SECOND reason,
found only after the first was fixed: ten recorded mutations no longer applied
to their targets. verify_mutations.py --stale-check named every one, and the
pre-batch run at 4f6f516a (34722813887) named none — so the 27-commit batch
rotted them, the same way it rotted the consumer inventory.
Two mechanical causes, and the manifest simply did not follow the code:
Seven entries pin a
python3 ...call site inlint-python.shand the two versioned hooks. The batch replaced the literal interpreter with${_PY}/${_LINT_PY}, so every one of thosefindstrings stopped matching.Two pin the LINE SPANS that
docs/cross-build-verification.mdquotes for the pre-commit hook’s_FAST_SLUGSblock and its staged-shell block. The hook moved, the prose was updated to:87-90and:101-118, the manifest was not.
The tenth, doc-numbers.total-re-quoted, targeted a sentence in AGENTS.md that
acc61567 moved out when it cut that file from 1863 lines to 900.
test-doc-numbers.sh still scans AGENTS.md and cross-build-verification.md as
the two MIRRORS of code-quality-tooling.md, so the mirror arm keeps its
mutation — on the mirror that still carries the sentence.
A stale entry is not a cosmetic failure: it is a guarantee nobody is testing, and the gate says so by name rather than skipping it. All ten were re-pointed at the code they are meant to neuter and all ten bite again; the staleness pass is clean over the whole 841-entry manifest.
2026-09-15 (later) — the four red lanes: one this batch caused, three it did not#
Separating cause from coincidence first, because the batch above is 27 commits and three of these lanes were already red before it.
Consumer Inventory — NEW, this batch. Green at the pre-batch tip
(run 34816752742 on 4f6f516a), red at 604294e2 (run 35015536117). The batch
taught ref_re the backslash so a Windows-spelled hub path is visible at all,
and that widened capture then swallowed things that are not path separators:
The hub’s own
run-lint-gates.sh:173prints a hub path from a format string endingmanifest\n. That trailing C escape read as one more path segment, so the gate reportedshared-assets.manifest/n— a file that is right there — as a dangling reference. A reference SPELLED with slashes now ends at the first backslash; one spelled with backslashes is unchanged, and still caught.BeschleunigerBallett’s
scripts/windows/tests/Resolve-BuildModule.Tests.ps1asserts that resolvingNoSuchModulenames both locations it searched. That is a fixture path that must NOT exist, whichFIXTURE_PREFIXESalready excused — forlinux/scripts/tests/andwindows/scripts/tests/only. A consumer spells the same directoryscripts/windows/tests/. The rule is now a path SEGMENT, so it holds in every repo’s layout.
Both are false positives with a test each; a dangling backslash path outside a test tree still fails the run, which is the arm the batch added.
Composite actions self-test — PRE-EXISTING. Red on 4667b00c
(run 34683896775) and 8203965d (run 34681555187) before the batch, with the
same message: the step asserted ACTION_DIRS=11 and the container reported 12.
deploy-over-ftp made it twelve on 2026-09-10. The literal was a claim about
the repository wearing the clothes of a claim about the mount; the step now
counts the host checkout and compares, which cannot rot and also catches a
miscount on the host side.
Ubuntu 26.04 — PRE-EXISTING. Red at the pre-batch tip on 4f6f516a
(run 34722813887): preflight’s mutation gate, one survivor,
doc-links.tracked-output-floor. The mutation deletes the static floor from
under a HAPPY git, and the test that should have caught it measured the floor
against what is on disk — and inside the mutation mirror no output path is on
disk, so floor was empty and every comparison was set() == set(). The
git-free arm beside it had already been moved to the synthetic probe list for
exactly this reason; the tracked arm now uses it too, and the mutation bites.
SBOM — PRE-EXISTING, and not a test problem. Red on every scheduled run:
4f6f516a (34808919489, 2026-09-14), 2a4e8a9b (34086038781, 2026-09-07),
6a9fd5fb (33359833450, 2026-08-31). All three arches die the same way, mid
layer: unable to populate layer cache ... disk quota exceeded. registry:
streams rather than pulling into a daemon, but syft still caches layers under
TMPDIR, and a cross image does not fit on the runner’s root volume. The cache
moves to /mnt. Nothing about what is scanned changes.
2026-09-15 — the audit’s second hub batch: the owner’s decisions, executed#
The 2026-09-14 family audit left 33 open items against this repository and twelve questions for the owner. The answers came back; this is the hub’s side of them. Cross-repo halves (the consumer wrappers, the OrchestrANT copy of the NAS census, the dartdoc fork deletion, the Flutter lane rewrite) are a separate pass — every hub-side helper they need exists now.
The owner’s decisions#
D1. The home-lab stacks stay, and the README says so. linux/homeassistant/
and linux/nextcloud-aio/ are the owner’s personal operations stacks, carried
here deliberately. README.md states that under its own heading, with the
consequence spelled out: anything claiming this repository is build
infrastructure ONLY is false. The h4 that claimed exactly that is rewritten to
what the tree actually holds.
D9. linux/webserver/dist/ stops being tracked. 82 MB of minified Flutter
output, built in another repository, rewritten in full on every site build, and
unrebuildable by anything here. The image takes it from a named BUILD CONTEXT
now (COPY --from=site, --build-context site=<jot>/build/web), and a build
that names no site context fails at that line rather than serving nothing.
git rm -r --cached only — the files stay on disk. license-assets/ stays
TRACKED, against the item text: it is 48 KB, this repo GENERATES it, and a live
gate checks it is current.
D8. The NAS document-AI thread leaves for OrchestrANT. nas_census.py, its
suite and nas-document-ai.md are removed here, with their mutations, their
allow row and their family, and every reference repointed. The consumer side is
a later pass; the files are recoverable from this commit’s parent.
D4. The repository size is documented, not rewritten. project-info.md now
says what is large (nothing, after D9), why the pack is still ~154 MB (dist’s
history) and why no filter-repo runs: it would change every commit id, break
the gitlink in six consumers and every SHA in every changelog entry, to save a
one-time clone cost measured in seconds.
D10. AGENTS.md is 900 lines, down from 1863. Nothing was deleted. Every
paragraph either stayed because it is a RULE an agent must not break, or landed
in the docs page that owns its topic with a link back — the Validation
measurements to linux-host-setup.md § B7/B8, the caching mechanism to
build-cache-tiers.md and windows-build-resources.md, the command reference to
linux-cross-builds.md, the Repo Map’s per-file detail to
shared-script-libraries.md, and so on. § Contents states the split, so the next
reader knows which side a new paragraph belongs on.
A089, decided by Claude because the item recommended it.
renovate-fleet.sh --vendored is an opt-in mode for the two cases that are not
the accident the default protects against: a repo of the owner’s with no own
checkout anywhere, and a container that mounts a single superproject.
The scan-root contract reaches the docs gates#
doc-links, doc-dupes and code-dupes resolved their root from __file__,
so in a consumer’s third_party/ANTfrastructure checkout they graded THIS
repository and reported that as the consumer’s verdict. All three take --root
now, keep their current behaviour exactly when the root is the hub, and read
their budget from <root>/<gate>.allow otherwise. doc-links joins the
--ratchets step — it is the only one with no budget to seed — which is what
finally puts a consumer’s own README.md into the cross-reference graph.
Ten gates gained a --root test case and a
<gate>.root-argument-is-the-tree mutation; nine are verified to bite (the
tenth needs shellcheck, which this image has not). That needed a fixture the
suites did not have: every existing one plants a tree AROUND the gate, so the
gate cannot tell it from its own repo. gate-tree.sh grows gate_tree_git plus
two assertion helpers, because ten hand-copied blocks are what the code-dupes
gate exists to catch.
The upstream halves the consumer forks were waiting on#
A Flutter lane prologue and an HTTP-readiness helper (three consumers had each
written the poll loop; this one RETURNS rather than exits, so the nginx caller
can still dump its logs). run-in-ci-image.sh, which is the hand-typed
docker run every README carried, once — with the Git Bash path-mangling escape
exported rather than documented, because a note nobody reads is how that keeps
being rediscovered. A cmake-format venv DEFAULT instead of an error. MSIX
orchestration (Invoke-MsixPackage, Get-PackageVersion) with the assertion
that makeappx actually produced a file. export_clang_gcc_toolchain_env
restored, with a prefix resolver that probes for crtbeginS.o instead of
trusting a composed path. The WebDAV downloader moved to 01-core/ with its
client PINNED, so both lanes install the same one. STATIC_ANALYSIS_EXTRA_PATHS
and CARGO_CLIPPY_ARGS, the two knobs whose absence made consumers hand-roll
the drivers.
The second pass: what the first one CLAIMED and did not ship#
A consumer measured the pinned tree and found six of the helpers above absent — they had been listed from a plan rather than from the tree, which is the exact mistake the audit keeps finding. They exist now, each shaped by the consumer code it replaces rather than by what looked tidy here.
The bandit bug, first, because it made an existing knob unusable.
STATIC_ANALYSIS_EXTRA_PATHS reached bandit as one -r per path. bandit’s -r
is store_true against a SINGLE nargs='*' positional, so bandit -r a -r b
is “unrecognized arguments” and exit 2 — measured against bandit 1.9.4 in the
family image. The knob therefore took the bandit gate down on every lane that
set it, which is why OrchestrANT documented it as broken instead of adopting it.
One -r and the whole target list now, on BOTH lanes: the Windows twin
Invoke-CiStaticAnalysis.ps1 had built the same shape. The tests COUNT the
flags, and Python.StaticAnalysisArgv.Tests.ps1 builds the Windows argv out of
the script’s AST and asserts its default exclude list equals the Linux one.
BANDIT_EXCLUDES / -BanditExcludes (A107): the -x list was a literal on
the gate line, so a consumer with one more directory to skip had to hard-code
the whole string in its own driver. The default is that literal, character for
character; setting the knob REPLACES the list, because a consumer that names an
exclude set means that set.
cmake-build.sh --configure-arg, repeatable, forwarded to the configure
step only. AccelerANTgine’s ci-release.sh needed -DCMAKE_LINK_WHAT_YOU_USE
and -DCPACK_ENABLE_APPIMAGE, and with no way to add a -D it ran
--skip-configure true plus its own cmake -B … --preset … — three library
entry points where cmake_build_main now does. A value containing a space stays
one argument, an empty value is fatal rather than dropped (cmake "" fails with
a message about the source directory), and the list is reset per parse.
app_packaging_ensure_flatpak_runtime and
app_packaging_package_cmake_install_flatpak. The existing flatpak packager
takes a Flutter BUNDLE tree; a CMake project has no bundle, so the cmake-install
variant is its own function — but it obeys this file’s three conventions, and
two of them the consumer’s fork did not: the staging tree is container-native
(the out dir is routinely the build directory on a mounted workspace), and
flatpak-builder’s exit code is not the verdict — ostree is asked whether the app
is committed. The runtime installer is deliberately not the container one: it
installs nothing with apt, asks before installing, and falls back user→system.
fix_bind_mount_ownership in 01-core/bind-mount-ownership.sh, with the
split that is the whole point of it intact: only the paths that actually differ
are chowned, a failure as a non-root uid is explained and tolerated (handing a
file to another uid needs CAP_CHOWN — a red nobody can act on is what the old
|| true was hiding from), and the same failure as root is fatal.
WindowsMediaRuntime.Common with Copy-MediaRuntimeBundle and
Get-MediaRuntimeDirectory, collapsing the three sites — ClangCL debug, profile
and release — that each staged the same GStreamer + ONNX Runtime DLL closure.
One difference from the fork it replaces: the recursive NuGet probe is pinned to
the TARGET runtime identifier instead of win-x64, so a foreign-rid payload
cannot be staged next to the exe.
python-ci-windows.yml gains lint-powershell and lint-path. OrchestrANT
and OxidANT each hand-wrote the same PowerShell-lint job within a week; they
agreed on everything that matters and differed only in the directory they
pointed the gate at. It is a second job on the same runner, defaulted OFF, and
deliberately does not needs: the build — a syntax error in the build scripts
is exactly when the lint is worth having.
The third pass: two gaps the consumers measured in what shipped yesterday#
Both were found by a consumer running the new code, and both are the same failure: a hub helper that is almost the consumer’s, so the consumer keeps its own copy and the duplication the upstreaming was for survives.
app_packaging_require_flatpak_tools requires ostree. It checked flatpak
and flatpak-builder only, while the verdict both flatpak packagers ask is
app_packaging_assert_flatpak_committed’s ostree refs. Debian’s and Ubuntu’s
flatpak package depends on libostree and not on the ostree CLI, so a dev
box with both declared tools present passed the check and then failed at the
verdict — is not in <repo> over an export that had succeeded. AccelerANTgine
measured exactly that and kept its local ensure_flatpak_tools with ostree
added rather than adopt the hub’s; that local check can go now (its apt /
AUTO_INSTALL_FLATPAK half is a separate, deliberate difference and stays).
Each missing tool is reported with the reason the packaging step needs it, and
all of them at once, because one apt-get installs the set.
python-ci-windows.yml gains build-python-package. The lint-powershell
input added yesterday was unreachable for the consumer it was written for:
build-test-python-package-on-windows carried no if:, so turning the lint on
also bought a winamd64 image pull, a Python package build, a ./dist/ upload
and a GHCR_PAT. OxidANT is a Rust crate with no Python package — it measured
the hub job as byte-for-byte its own and still had to keep it. The build job is
gated on the new input, which defaults true so every existing caller is
unchanged, and GHCR_PAT is required: false, because a required secret is
refused at call time and would have kept the lint unreachable for exactly the
callers the gate is for. The build job asserts the token in its own first step,
so a caller that wanted the build and forgot the secret is told which input it
missed instead of failing inside a docker login. OxidANT’s powershell-lint
job can become a uses: now — the retirement condition its comment records.
Ten assertions over four cases join test-app-packaging-flatpak.sh and twelve
over five join test-reusable-windows-lane.sh, with six mutations (three per
gap) proven to bite. No budget moved: the only derived number that changed is the mutation
manifest, 835 -> 841 entries over the same 91 distinct test commands, written by
test-doc-numbers.sh --update.
Gates and hooks#
One Python-probe owner for lint-python.sh and the versioned hooks: eight bare
python3 call sites, one of which was silently turning the embedded-Python
extractor into a no-op on a Windows host. The consumer inventory can see the
half of the fleet it was blind to — backslash-spelled hub paths, and the three
shapes that reach a PowerShell module by NAME. A weekly failure now files an
issue instead of reporting to nobody. The delete guard stops listing two
repository checkouts that have not existed since the 2026-09-12 rename.
Budgets moved with the code and every one is recorded at the measurement, with
the cause: six code-dupes budgets re-measured after the corpus grew, seven rows
retired as stale, seven added, two doc-dupes rule/mechanism pairs budgeted,
renovate-fleet.sh frozen at 814 lines with the not-a-split argument, six
comment-size headers frozen, and three new operator knobs registered.
2026-09-14 — family audit: the hub’s side of the fixes#
A cross-repo audit of all nine consumers against this hub (reuse, duplication, docs freshness, naming, quality-gate reuse, topic separation) landed the hub half of its mechanical fixes here; the consumer halves are one commit per repo.
Three files the 2026-09-08 “no consumer” sweep (2eaed40e) deleted are back:
windows/scripts/modules/WindowsOnnx.Common.psm1(AccelerANTgine’sBuild-Windows.ps1imports it by NAME; its Windows lane was red from the pin bump that carried the deletion),windows/scripts/modules/WindowsContainerLog.Common.psm1andwindows/scripts/rust/New-Archive.ps1(OxidANT reaches both by path). The consumer inventory gains awindows-lang-scriptclass forwindows/scripts/rust/*.ps1(thewindows/scripts/*.ps1glob is non-recursive, so nothing graded them), and AGENTS.md § Contributing Reusable Work Here now carries the rule: a hub file is deleted only when the inventory reports it named by nobody..github/consumers.json: WebDavClient is a confirmed consumer (verified against a clone; it still calls the python-ci lanes under the pre-renameKataglyphis-ContainerHubname, which is its own migration item); theunconfirmedarray is empty (AccelerANTgine and ANThology were already confirmed rows); the OxidANT and AccelerANTgine notes say what those repos actually do.Sync-UvProjectDependenciesexcludes the extras that[tool.uv] conflictsforbid (Get-UvConflictGroups,Get-UvExtrasToExclude,UV_SYNC_EXTRASoverride), the port ofpython_uv.sh’s_uv_extras_to_exclude— OrchestrANT’s Windows lane had failed onuv sync --all-extrassince 2026-09-12 while the Linux twin routed around it. Pinned byUv.ConflictExtras.Tests.ps1.run-lint-gates.sh --ratchets(opt-in): the eight--rootmeasurement gates over the consumer tree, freeze files at<root>/<gate>.allow; no consumer had run any of them. The consumer-pins gate and the new step run underPREFLIGHT_PYTHONinstead of a barepython3, through the probelint-workflows.shhad carried inline — now01-core/python-probe.sh(preflight_python_require), the one owner both call, and it accepts a command-line value such asuv run --no-project python, which is the hint the failure message itself gives.gate_scope.tracked()pinsencoding="utf-8"(a non-ASCII tracked path aborted every*-scoped gate on a cp1252 host).Two reusable lanes:
.github/workflows/lint-gates.yml(workflow_call; inputsexclude,submodules,hub-checkout,ratchets) replaces the seven consumer copies of the lint job, andsubmodule-pins.ymlgainedworkflow_call(inputssuite-path,pester-version,runner) so the three consumer copies collapse to oneuses:line.deploy-over-ftphas callers:build-docs.ymlandpython-ci-linux.ymlpublish through it (their ownchmod -R 755steps are gone with it), andactions-selftest.ymlnames every input in a guarded step so actionlint holds the contract.docs/ftp-deploys.mdand.github/actions/README.mdsay so..gitattributespins the extensionless bash files (linux/host-config/git-hooks/*,linux/nextcloud-aio/custom-bin/*), the Linux build context’s text files (linux/Dockerfile*,*.txt,*.allow) and the Python gates (linux/**/*.py,docs/**/*.py,.claude/**/*.py) to LF: acore.autocrlf=truecheckout materialised them CRLF, and preflight’s crlf-guard, shellcheck, android-parity andtest-advertised-keys.sh(its fixture mutates a gate with a$-anchored sed) went red on every Windows host bind-mount while Linux CI stayed green.Docs:
adopting-in-a-new-project.mdgains § 9 Quality gates (manifest, aggregator, pin suite, python-ci lanes) and four checklist rows, and stops claiming a layout uniform “across all seven consumers” (the two Linux-only Flutter repos keep a flatscripts/);shared/templates/README.mddescribes the six-section AGENTS template it ships;docs/INDEX.mdcites a BeschleunigerBallett passage that still exists;dependency-updates.mdnamesGITHUB_COM_TOKENfor--platform=local(three sites saidRENOVATE_TOKEN, which is why four consumers had measured and retyped the correction);shared-script-libraries.mddocuments six lint gates, not three; stale counts in AGENTS.md (12 actions; the pre-commit mutation cap), README.md (no hard-coded slug count),.github/actions/README.mdandci-build-triggers.md(llm-stack-serving.yml, the pins/selftest/inventory workflows) corrected; the consumer-inventory examples say/c/GitHub; a broken link inshared/linux/templates/README.mdfixed.
2026-09-13 — Home Assistant energy dashboard + recorder pass#
Solar forecast fixed on the dashboard: both Forecast.Solar planes are now attached to
config_entry_solar_forecast, not just the 2.5 kWp one — the 10 kWp plane was missing from the forecast graph. The planes themselves (10 kWp / 2.5 kWp) were already correct after the v3 subentry migration; both still carry the 25°/180° orientation defaults.Six individual devices on the energy dashboard — four NOUS A1T (Wohnzimmer links, Büro links, Silas, Waschmaschine) and the two P110s that expose a lifetime counter (Anrichte, Wohnzimmer rechts), each with its live
stat_rate. The “Stromzähler von Jones” P110 has no lifetime entity, so it is absent.Recorder slimmed:
configuration.yamlexcludes the instantaneous Tasmota plug telemetry (apparent/reactive power, current, voltage, factor) — measured ~320k of 552k state rows over 10 days.powerand the kWh totals stay, because the dashboard uses them.README: the backups section claimed “unencrypted”; the backup password was set 2026-09-13 05:41, so new automatic backups are encrypted while every archive already in
config/backups/is not.
2026-09-12 (night) — Home Assistant hardening pass#
Energy dashboard repaired: solar is back on the Growatt inverter (
sensor.hannemann_total_energy_today) and the Tasmota smartmeter counters are the grid import/export source, afterhomeassistant.customizegave themkWh/total_increasing/energymetadata.Security/runtime: login ban threshold 5 (was −1 = off), recorder
commit_interval: 30,stop_grace_period: 60s,privilegedand/run/dbusdropped — the latter silences the BlueZ D-Bus spam, since a rootless container cannot authenticate to the host bus anyway.Cleanup: 5 stale
mobile_appregistrations and the 2 Bluetooth adapter entries deleted; deadsleep_for_90sec, the missingthemes/include,automationsBackup.yamland.storage/tmp*removed. The twoforecast_solarentries are two arrays (10 kWp + 2.5 kWp), not duplicates, and both were kept.
2026-09-12 (evening) — the Home Assistant stack moves in#
linux/homeassistant/now tracks the compose stack that lived in~/Documents/homeassistant— compose plus the hand-written YAML and blueprints, with the volume rewritten to the relative./config. Live state (recorder DB, logs,backups/,secrets.yaml,.storage/) is gitignored and also excluded from every Docker build context, and the stale 455 MBcoredump was deleted.secrets.yaml.exampleis the tracked template; the WoL MAC, the F@H SSH commands and the alert address live in the gitignoredsecrets.yamlas whole-value!secretnodes, and the three stale-device automations plus the unwanted high-power F@H stop were removed.All remaining automations were dropped at the owner’s request —
automations.yamlis[]— leaving the WoL switch and theshell_commandservices callable from the UI.Two pre-existing reds cleared on the way, because the newly installed pre-commit hook runs whole-tree gates: the three
shared/*/templates/README.mdpages lost their duplicated table boilerplate (code-dupes), andgate-proofs.allowshed the 19 mutation families the benchmark lab took to OrchestrANT, withdocs/code-quality-gates.mdregenerated (gate-registry).
2026-09-12 (later still) — the benchmark lab leaves for OrchestrANT#
The measurement suite, the viewer and the tracked results moved out of
linux/llm-stack/to OrchestrANT (benchmarks/, with the runner in theorchestrant.benchmarkpackage). This repo keeps the serving stack,backends.json— the registry both host tooling and the benchmarks consume — andnas_census.py.llm-stack-tests.ymlbecomesllm-stack-serving.yml: the compose files parse, the registry keeps its default and GenieX lanes, and the NAS census test still runs.The roadmap and panel-review pages, ~200 mutation entries, the size allowlists, the doc-link test fixtures and every prose pointer moved with the lab; the two gate tests that pinned
linux/llm-stack’s scan membership now pin the NAS file that stayed.
2026-09-12 (latest) — the pre-existing CI reds, fixed#
The composite-actions self-test could not find its own local actions. Three jobs called
./.github/actions/...with no checkout ahead of them, so GitHub searched an empty workspace. Each now bootstraps the repository first; the actions under test still do their own checkout.The consumer inventory failed on its own test fixtures.
dangling_refsnow skipslinux/scripts/tests/andwindows/scripts/tests/: those suites build fake consumer trees full of paths that must not exist, and a real call in a test fails the suite itself.And on vendored submodule paths. A fresh clone leaves
third_party/DocumANTation/empty, so a reference into it read as dangling; paths declared in.gitmodulesare now outside the check.The version snapshot failed on the DocumANTation Dockerfile. The pin was behind, so its
ARGdefaults had drifted from versions.env; bumped to the rename commit.The SIGPIPE case in test-renovate-exit.sh was a race, not a defect. A closed pipe ends the run through the PIPE trap (141) or through bash’s EPIPE-on-builtin path (1); the test accepts both and still asserts the tree is intact, which is the part that must not vary. New helper:
t_assert_contains_any.
2026-09-12 (later) — the hub is now ANTfrastructure#
Kataglyphis/ContainerHubis renamed toKataglyphis/ANTfrastructure. GitHub redirects the old URLs so nothing breaks in flight, but the whole family is swept in the same change: every URL,uses:ref and Renovategithub>preset; the submodule path (third_party/ANTfrastructure); the bash bootstrap (shared/linux/templates/antfrastructure.sh,antfrastructure_path/antfrastructure_source/antfrastructure_exec); theCONTAINERHUB_*environment variables; and the per-consumer.antfrastructure-shared.manifest. The rename starts here because this repo owns the template every consumer copies.
2026-09-12 — merge cleanup: the CI reds the Renovate merge left behind#
Eight preflight checks were red on
origin/mainafter the Renovate merge; all fixed here: code-dupes (2 budgets tightened, 4 stale rows removed, the env-suite clone given one owner), SBOM regenerated, comment-size (6 new blocks frozen), code-size re-baselined after thebump_versions.pyshrink, shellcheck SC2088 reworded, workflow-lint’s spelled-out CI image ref replaced with the helper’s name, and the six failing unit suites.The Windows job’s failures were LiteRT-LM pin parity:
0.16.1 -> 0.17.0and PROTOC31.1 -> 35.1inBuild-LitertLmFromSource.ps1, plus the same LiteRT-LM default inBuild-LitertLmBazel.ps1. The PinParity scanner also excludedtests/with Windows path separators only, so on Linux it scanned the test fixtures themselves.verify_mutations.py --jobs > 1raced: the first shard mutates the repo root in place while the other shards were still copying their mirrors, so a mirror could capture a mutated file and its baseline read as a vacuous bite. Mirrors are now materialized before any shard starts.The fleet suite’s per-repo budget was 1s, which flaked on a loaded host (the repo before the slow one timed out too); it is 5s now, and the slow fixture still sleeps 20s so the verdict is unchanged.
The hook itself carried a bug the new renovate suites exposed: git exports
GIT_DIRto pre-commit, so fixtures that shell out to git operated on the superproject and the mutation sample failed undergit commitwhile passing standalone. The hook clearsGIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE/GIT_PREFIXbefore it runs anything.
2026-09-11 — android stage unblocked: every installed foreign arch gets a source#
Symptom. The android stage failed on all three arches within seconds:
libc6:i386=2.43-2ubuntu2.4(fresh from archive) Breaks the foreignlibc6:arm64/riscv64=2.43-2ubuntu2.3(frozen on ports) and apt had no source to upgrade them from.Root cause. The compiler base installs
libc6for both foreign arches, butDockerfile.media’s apt reset leaves sources for the build host and the current target only. A transient archive/ports sync gap (i386 2.4 vs arm64/riscv64 2.3, back in sync minutes later) then makes the i386 install unsatisfiable.Fix.
cross_ensure_installed_foreign_arch_sources(01-core/cross-apt.sh) writes a per-arch ports source for every installed foreign arch that lacks one;android-sdk.shcalls it beforeapt-get update. Reproduced and validated against the real media-parent state in a container before coding.Test.
test-cross-apt.shcovers the helper (ports arches only, existing file untouched, missing wiring = no-op) plus a whole-line wiring check onandroid-sdk.sh. Both mutations proven red — the first attempt at the wiring check passed with the call deleted because the helper’s name appeared in a comment.Two gates the fix flushed out.
test-mirror-consistency’s NOSITES fixture went blind becausecommand -v ubuntu_write_deb822_sourceparsed as a call site; the scanner now skipscommand -v/type -t/declare -Fprobes, which could previously make a writer-less tree read green.test-script-copy-coverageneededubuntu-mirror.shinKNOWN_BASE_PROVIDEDfor Dockerfile.android (inherited from the compiler base).Symptom entry:
docs/failure-modes.md.
2026-09-11 (night) - ASan runtime staging follows the link policy#
The build stages the runtime the link selected.
Get-SanitizerRuntimeDlls(WindowsCMake.Common) walkedclang-cl-on-PATH first and returned LLVM’sclang_rt.*san*.dll, whilecmake/Sanitizers.cmakelinks Microsoft’s import lib from the VS toolset; inside the Windows image every ASAN-instrumented build tool then died at load withSTATUS_ENTRYPOINT_NOT_FOUND(0xC0000139). It now delegates toGet-AsanRuntimeDirs(WindowsTesting.Common), the one owner of the Msvc-first policy - no second root ordering to drift.Regression covered:
WindowsCMake.Common.Tests.ps1pins the delegation and the empty-result array (full suite: 826/828; the 2 LiteRT-LM pin parity failures predate this change).
2026-09-11 (late) — bump_versions.py shrinks to the complement#
The detection half is gone, the finishing half stays. The script’s tiers drop every key Renovate now owns and reports (23 entries out of SAFE/REPORT), about 80 lines of upstream-querying helpers with them, and the coverage audit learns
renovate_owned()— a key is classified when it is in a tier, carries a# renovate:annotation, or is a non-version.What it still does, on purpose: paired
*_SHA256/*_COMMITrefresh (--write/--write-all), the keys with no feed, the two registry digests, the artifact-gated TENSORFLOW_C check, and the slaved PROTOC derivation.Measured after the shrink:
--checkcompletes with 0 lookup failures and 0 unclassified keys, and still reports the real outstanding bumps (pwsh, uv, node, ollama, pandoc, flutter, CUDA/cuDNN, both digests).Pre-existing, not introduced:
--audit-sha-pairsfails on 11 scattered*_SHA256keys that predate this change (verified on HEAD); recorded here so the next sweep can classify them rather than rediscover them.
2026-09-11 (evening) — 68 -> 89: vendor JSON, PyPI twins and digests#
Twenty-one more keys. Three custom datasources (
custom.cudawith an HTML-href fetch and a JSONata strip,custom.vulkan’s WINDOWS value,custom.nuget’stools.json), a second customManager using the regex manager’scurrentDigestcapture forUBUNTU_DIGEST/WINDOWS_BASE_DIGEST,versioning=regex:...for the 4-part PyPI twins (nvidia-cudnn-cu13,tensorrt) and forprotocolbuffers/protobuf’s major.minor-only31.1, plus ROCm viaROCm/TheRocktags.Two live iterations, both measured.
format: plainmaps each LINE to a version, so CUDA needed the HTML fetcher; andskipReason: invalid-valuenamed strict semver as the reason cuDNN, TensorRT and protoc were silently updateless.Live report: 29 pending updates and zero lookup warnings — including the two digest moves and the setuptools
<82cap holding (no 84 proposal).20 tracked keys remain annotation-free, in documented classes: a slaved pin, feeds no datasource can serve (MIGraphX, flatpak branch, JRE selector, the libffi wrap), platform matrices with no feed, checksums/raw SHAs, artifact-gated and dated pins —
docs/dependency-updates.md.
2026-09-11 (later) — the local apply half can write versions.env#
custom.regexbecomes a writable manager for the self-contained keys.renovate_locator.find_annotated_envanchors on the hint’sdepNameand returns the KEY= line under it;renovate_audit._parse_envreads the file back independently. A KEY, not a dep name, identifies a leaf — the live dry-run immediately caught thatNODE_VERSIONandRENOVATE_NODE_VERSIONsharedepName=node, which a dep-keyed parser had refused wholesale.Which keys may be written is a file-scoped policy in
.github/renovate.json: approval by default, cleared for the Rust security-tool and Python build-executor installs, the npm web runtimes,rust-lang/rust,cargo-c,APP_REFandsyft. On the live ANTfrastructure report that is 7 applicable rows and 13 refusals, exactly as intended.bump_versions.pyis demoted, not deleted: it remains the lock tool for every coupled*_SHA256/*_COMMITpin and the detector for the 41 unannotated keys. New suitetest-renovate-env.shcovers the write, the refusal and the unreadable hint; all six Renovate suites stay green (201/87/105/98/15/11 assertions).
2026-09-11 — versions.env is 68 keys visible to Renovate, not 18#
The annotation pass, verified against the real report.
bump_versions.pytracks 99 keys; 68 now carry a# renovate:annotation (up from 18). A liverenovate-local.sh --managers custom.regex .resolved every one of them with zero lookup warnings and reported 20 pending updates —uv 0.12.13, LLVM23.1.1, LiteRT-LM0.17.0, ComputeLibraryv53.3.0, openh2642.6.0, flutter3.47.3, syftv1.51.1, among others. The remaining 41 tracked keys are documented exclusions indocs/dependency-updates.md: coupledbump:holdpairs, vendor indexes with no datasource, base/platform pins, untransformable tag shapes, deliberate same-major pins and checksums.Every datasource was tag-shape checked first (
git ls-remote), then added:github-tags/github-releases,pypi,npm,node-version,python-version,flutter-version,crate. The customManager grew aversioning=capture with the standardversioningTemplate, needed by the two leading-zero tags (ARM-software/armnnv26.07,microsoft/vcpkg2026.07.29);test-renovate-annotations.shnow asserts the capture and the template cannot drift apart.Two would-be wrong bumps are gated to match the writer’s tiers:
NODE_VERSIONwithin its major (allowedVersions <27) andPYTHON_VERSIONwithin its minor (<3.15) — whatbump_versions.pyclasses same-major/same-minor in SAFE.
2026-09-10 — the foreign Vulkan prefixes are two files from amd64#
VK6 and VK7 closed, measured on both pushed digests.
lib/is 118 on amd64 and 123 on arm64 (@sha256:f77f97fa) and riscv64 (@sha256:028ce048);bin/52 everywhere; layers 9 / 10 / 10. The gap VK6 opened at 72 files is now 2, identically on both arches.The entry’s own fix would have made it worse.
BUILD_SHARED_LIBSis exclusive, not additive: ON alone gains 9 files and LOSES 6, because glslang guards three static installs behindif(NOT BUILD_SHARED_LIBS). The vendor configures glslang twice into one prefix and the STATIC pass must land LAST, because the second install ownslib/cmake/glslangand therefore whatfind_package(glslang)describes. A test asserts the order, not just the presence.Two files stay, both explained.
VulkanLoaderis a layout difference the consumers already assume;libshaderc_util.ahas no install rule and ships with no headers even on amd64, so it is unlinkable there too.VK7 mirrors the vendor’s own prune and is guarded on
include/dxc/dxcapi.h— without that marker the helper wouldrm -rf include/llvmout of whatever directory it is handed, and/opt/llvm-targetholds 41 MB of real LLVM 23 headers.VK5 closed with them: arm64’s earlier number was measured against a tree that no longer existed. Both foreign arches now report
24/24from the current one, with zerounavailable on <arch>lines.EX1 closed too, and the residual AS1 neighbours are latent (every cross stage builds on
linux/amd64).
2026-09-09 (later) — all three arches ship 52 Vulkan binaries#
VK4/VK5 closed: 52 = 52 = 52, and the foreign pair leads on layers. Measured on the pushed digests with both directories listed in a container, not derived from a log:
bin/52 on amd64, arm64 (@sha256:6eefc3c3) and riscv64 (@sha256:1bdfbb3a);explicit_layer.d9 on amd64 and 10 on both foreign arches. Zero entries in either direction — the sets are identical, not just equal in size.dxc,vkconfig,vkconfig-guiandllvm-tblgenreport ELF AArch64 / RISC-V, so none is a copied host binary.The gap was never a build failure. LunarG’s
./vulkansdkbuilds 24 components underall; the HOST list named 18. A component not named there is never checked out, and the install helper returns before incrementing_vk_attempted— so the three missing ones were never counted as attempted and the verdict read a cleanN/N. Four rows and three dynamic-arg arms later the table is 21 + 3 hardwired = 24, exactly the vendor’s ownbuild_allcount.-Werroron a warning this file already knew about.dxcfailed the first riscv64 run atexternal/SPIRV-Tools/source/util/timer.hon GCC 16’s-Warray-bounds. Configure had completed, so LLVM 3.7 does know the riscv64 host triple._vulkan_target_build_spirv_toolshad carried-DSPIRV_WERROR=OFFfor that exact warning for ages; DXC vendors its own copy of SPIRV-Tools. The rebuild logged 164 such warnings on riscv64 and 93 on aarch64 — the fix saved both lanes, not one.A vacuous success caught before it shipped. VulkanTools does
find_package(Qt6 ... QUIET)and, without Qt6, drops the whole configurator with amessage()and exits 0 — the row would have counted as BUILT with no vkconfig in the image.CMAKE_REQUIRE_FIND_PACKAGE_Qt6=TRUEmakes it honest.A gate that was coin-flipping.
verify-artifact-copy-parity.shfailed ~1 run in 10 on an unchanged tree, naming a different artifact each time. Both sets were byte-identical on a red run: the bug wasprintf | grep -qxF, whose status is not reliably 0 when-qexits early on a pipe. Replaced with a shellcase; 200 runs green and both directions still redden.Still open, both small: VK6 (13 shared libraries the foreign arches do not get, caused by our own
ENABLE_OPT=OFF/SPIRV_CROSS_SHAREDflags) and VK7 (11 DXC files they ship that the vendor prunes).
2026-09-09 — riscv64 reaches 20/20, and the apt pockets have to agree#
VK2 is closed: 20/20 Vulkan cross-components on both foreign arches.
Vulkan cross-targets riscv64: 20/20(sdk-rv64-20260908-211949, pushed@sha256:09a4d255) andaarch64: 20/20(sdk-20260908-132426). Verified on the shipped bytes, not the log:riscv64/binholds 37 entries and is a strict subset ofx86_64/bin’s 52, andvulkanCapsViewer,slangc,gfxrecon-infoandvulkaninfoall report ELF machine RISC-V. The 15-entry delta is entirely LunarG’s prebuilt tarball (the DXC family,llvm-tblgen,vkconfig) — which no arch builds from source, and which is now VK4 rather than a regression.The last blocker was not Qt and not riscv64: the host and target apt sources disagreed on a POCKET. The compiler stage wrote
ubuntu.sourcesfor amd64 without-securityandubuntu-ports.sourceswith it. Sincelibcurl3t64-gnutlsisMulti-Arch: same, amd64 topped out at8.18.0-1ubuntu2.4while riscv64’s candidate was2.5, no common version existed, and apt reported the DEPENDENT (libappstream5:riscv64) as unsatisfiable. EveryMulti-Arch: samelibrary with a security-only upload was affected; Qt6 was just the first to matter. Proven by A/B on the same base image, one line different.Fixed in three places, because two of them are not enough.
build_python.shandDockerfile.medianow write both halves with-security;cross_align_host_apt_pockets(cross-apt.sh) repairs an inherited skew at the point of use, so a stage benefits without its parent being rebuilt — which is why the riscv64 sdk stage could be fixed with no compiler rebuild.archive.ubuntu.comcarries<codename>-securityfor amd64 (HTTP 200), so the old0bought nothing.The
mirror-consistencygate now asserts the pair, not the literals. It runs the realubuntu_write_deb822_sourcefor a host arch and a ports arch and requires the two suite sets to match, then parses every shipped call site withshlexand fails if they disagree on the flag. Neither file is invalid on its own, so nothing that reads one file at a time could ever have caught this._apt_sources_rewritenow owns the atomic sources rewrite. The pocket repair had drifted into an eight-line identical run withapt_sources_set_architectures; the dupes gate caught it, and the shared owner keeps the three properties each of which was paid for by a real failure (temp beside the target, explicit cleanup instead of a trap in a SOURCED file, and nomvafter a failing awk).Also this window: the retry classifier stopped reading BuildKit’s elapsed prefix as an HTTP 429,
smoke-toolchain.shasserts LLVM by major.minor instead of the full pin,lint-secrets.sh’s gitleaks invocation was repaired, andbench_coding.py’sRLIMIT_NPROCcounts tasks rather than processes.
2026-09-08 — the container stack installs rootless, with no sudo#
install-nerdctl-full.shgrew a rootless prefix mode. It always installed into/usr/localwith unconditionalsudo tar/sudo cp -a, which made it unrunnable unattended on a host whose sudo prompts for a password. It now installs into$HOME/.localwith no sudo anywhere. The mode is AUTO-DETECTED from the livesystemd --userunits’ExecStart— those units are the only authority on what a host actually runs — so neither host needs a knob;NERDCTL_ROOTLESS=1|0forces it. Every safety property is unchanged: busy-build refusal, SHA256 verification, backup +--rollback, the cache-mount census, the buildkitd worker assertion and the QEMU-binfmt warning.Extracting the bundle was only half a prefix change. The units keep the absolute
ExecStartthey were generated with, so an install into a new prefix moved no daemon at all. The script now repoints~/.config/systemd/user/{containerd,buildkit}.serviceand prepends${PREFIX}/binto theirEnvironment=PATH, stashing the pre-image in${NERDCTL_BACKUP_DIR}/systemd-userso--rollbackrestores units as well as binaries. A relocation is same-version by definition, so the “daemon version MOVED” proof is replaced there by the one that actually applies: every daemon’s/proc/<pid>/exemust resolve under the new prefix.A drop-in
ExecStartbeats the unit file’s.buildkit.service-override.confhardcoded/usr/local, so applying host config after a rootless install silently revertedbuildkitdto the other prefix — invisible until a build failed. It now carries@NERDCTL_PREFIX@, substituted byapply-host-config.shandverify-host-config.shbefore they install or diff.CNI plugins: 0 → 18. Rootless nerdctl resolves plugins under its own
$HOME/.local/libexec/cni, not under/usr/local, so summy-server had been running with none at all —/usr/local/libexec/cniheld only a LICENSE. The install now counts them where nerdctl actually looks and fails the run at zero.Measured on summy-server (aarch64, Snapdragon X, WSL2). The relocation was byte-identical: sha256 matched across
nerdctl,containerd,buildkitd,rootlesskit,runcandcontainerd-rootless.sh, because/usr/localalready held the same nerdctl-full 2.3.5 bundle. Not a version change — a relocation. Documented aslinux-host-setup.md§ B3c.