Changelog archive — 2026-09-22 through 2026-09-30#
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-21.md picks up. One mechanical edit, no
rewording: relative links are re-based on this directory.
2026-09-30 - GStreamer: a meson inherited without its launcher is reinstalled#
What went wrong. The default
:winamd64merge stopped inBuild-GstreamerFromSource.ps1withmeson.exe not found after pip install. The FFmpeg stage, rebuilt that day, pip-installs meson into the source CPython. The merge copies media-core’sLib\site-packagesbut not itsScripts, sopip install mesonanswered “Requirement already satisfied” and wrote nomeson.exe. On 2026-09-27 the FFmpeg layer came from cache without meson, and the merge’s pip installed it fresh.The fix. When no
meson.exeis found after the install, the script runspip install --force-reinstall --no-deps meson, which writes the launcher again, and then looks once more. It still refuses if that fails.Proven with a scratch venv. With the launcher removed, pip reports “already satisfied” and writes nothing. The forced reinstall restores it:
meson.exe --versionprints 1.12.1.
2026-09-30 - OpenCV: gapi’s fluid SSE4.1 kernels build under clang-cl#
What went wrong. The first BuildKit run of the default (CPU + DirectML)
:winamd64stopped inmedia-core-built-opencv:gfluidcore_simd_sse41.hpp(308,35): error: use of undeclared identifier '_mm_setr_epi64x', five times. opencv_contrib 5.0.0 takes MSVC’s_mm_setr_epi64xunder#ifdef _MSC_VER. clang-cl defines_MSC_VER, and its intrinsic headers have no such function.The fix.
windows/scripts/patches/opencv_contrib/003-gapi-fluid-clangcl-setr-epi64x.patchtakes that branch only for real MSVC (&& !defined(__clang__)). clang-cl then uses the portable_mm_setr_epi64branch. It is the only file in contrib’s gapi that names the intrinsic.git apply --checkis clean against the 5.0.0 tag (755e5067).
2026-09-30 - Windows base: both OpenSSL installers come from the LAN preseed#
What went wrong. A
:winamd64base build sat 3.6 h inscoop install main/openssl, then failed. scoop purged its cache and started again. slproweb serves each connection at ~9–25 KB/s, and the x64 installer is 251 MB.Measured. One connection gets 1 MB in 41 s. Eight connections each get their 1 MB inside 45 s, so the throttle is per connection, not per host.
The fix, on the pattern of the Vulkan preseed.
Build-Buildkit.ps1reads scoop’sopenssl.json.Save-ParallelRangeDownload(WindowsBuildDriver.Common) fetches both installers host-side in 32 ranges, joins them and checks their SHA256. It then puts them on the WebDAV underpreseed/.Install-ScoopTools.ps1drops the x64 installer into scoop’s cache under scoop’s own name, throughCopy-PreseedToScoopCache, which the Vulkan preseed now uses too. The aarch64 step fetches from the preseed first. Both fall back to slproweb, and both hashes are still checked.
Measured after. 251.1 MB in 269 s and 219.6 MB in 234 s, both hash-verified.
BuildDriver.RangeDownload.Tests.ps1holds the range split to exact, gapless coverage; an off-by-one overlap fails it.
2026-09-30 - Windows base: aarch64 OpenSSL follows scoop’s manifest instead of a pin#
What went wrong.
Install-ScoopTools.ps1fetched a literalWin64ARMOpenSSL-4_0_2.exe. slproweb had replaced it with 4.0.3, so the URL answered 404. The step only warns, so that day’s:winamd64base build went on withoutC:\opt\openssl-arm64. The next:winarm64GStreamer cross build would have thrown on it. This is the second time: the 4.0.1 → 4.0.2 bump indocs/changelog-archive-2026-08-28.mdwas the same break.The fix. The URL and SHA256 come from
architecture.arm64inbuckets\main\bucket\openssl.json. The x64scoop install main/opensslreads the same manifest, so both architectures move together and the hash is still verified. Checked against the live bucket: 4.0.3,Win64ARMOpenSSL-4_0_3.exe,afc17720….Not proven in a container yet. It takes effect with the next base build.
docs/windows-cross-builds.md§ aarch64 OpenSSL is a base prerequisite too records the rule.
2026-09-30 - SBOM: the amd64 and arm64 scans get swap#
What went wrong. Every SBOM run since at least 2026-08-31 lost amd64 and arm64 with “The runner has received a shutdown signal”. riscv64, the smallest image, passed.
The cause, measured. syft 1.52 on
:latestamd64 indexes the image’s files into 11 GB before any cataloguer runs, then peaks at 24 GB unconstrained. The runner has 16 GB, so the kernel killed the runner. Under a 15 GB cap:GOMEMLIMIT=10GiBstill died;SYFT_PARALLELISM=4still died, because the index is live heap;16 GB of swap finished in 15.5 minutes with the same 4056 packages.
The fix.
sbom.ymladds a 24 GB swap file on/mntbefore the scan. The job timeout goes from 30 to 60 minutes, because paging doubles the scan time.docs/sbom.md§ Generating them records the budget.
2026-09-30 - python-ci-linux: a pull request no longer publishes the docs#
What went wrong. The Sync files to domain step ran on every event. On a Dependabot pull request the FTP secrets are empty, so WebDavClient’s PR builds failed with
Input required and not supplied: server. A PR opened from the repository itself has the secrets, and its docs would have replaced the live site.The fix. The step also needs
github.event_name == 'push', the gatebuild-docs.ymlalready had. A push to a caller’s branches publishes as before.
2026-09-30 - Windows: the source-built CPython reports win-amd64 in a venv again#
What went wrong.
Py_GetVersion()keeps 80 characters ofCOMPILER. For clang, CPython writes__clang_version__there, and Visual Studio’s clang puts its git URL and hash in it. So64 bit (AMD64)fell offsys.version.The effect.
sysconfig.get_platform()readwin32in every venv of the image’s CPython. uv, pip andbdist_wheelthen treated the x64 interpreter as 32-bit. OrchestrANT’s Windows lane has failed on this since it was created:onnxruntime-genai-cudahas no win32 wheel.The fix.
windows/scripts/patches/cpython/001-short-clang-compiler-id.patchnames clang by major.minor.patch ([Clang 22.1.3] 64 bit (AMD64) …).Build-ToolchainAll.ps1applies it and refuses asys.versionwithoutAMD64.Test-PatchesApplyClean.ps1now mapscpythontoPYTHON_VERSION.Proven by rebuilding CPython in the published
:winamd64, with the laterInclude\pyconfig.hcopy removed as a fresh build has it:the venv reports
win-amd64;uv resolves
onnxruntime-genai-cuda0.15.2.
Takes effect with the next image build.
2026-09-30 - GHCR prune: the keep-set gate counts digests, not tag names#
What went wrong.
ghcr-prune-package.shrefused on 2026-09-27 with “keep-set smaller than tag count”: 22 tags, 21 kept digests, because two tags named one image during the:latest-crossrename. The weeklyGHCR · cleanuprun has been red since then.The fix. The gate now requires every tag’s live digest to be readable and in the keep-set. An unreadable digest aborts; the old code dropped it silently.
Test:
linux/scripts/tests/test-ghcr-prune-keepset.sh. With the old gate put back, it fails with the 2026-09-27 message.Docs:
linux-host-setup.md.
2026-09-30 - One-line comments, only the why, in every language#
The rule.
AGENTS.md§ Comments: one line, only the why replaces the two-line ceiling and is the owner rule for every repo of the family; the consumerAGENTS.mdtemplate links it. One line, only the why; API docs stay, short; a trap that needs more gets a paragraph in itsdocs/page and a one-line pointer; no history, dates or commented-out code.The sweep. Every tracked source file is held to it (about 880 files, 38.9k lines out, 8.6k in). Traps moved to their pages first, among them
build-cache-tiers.md,linux-cross-builds.md,vulkan-foreign-arch-sdk.md,windows-build-resources.mdandslang-shader-compilation.md. Apart from the gate, its tests and the fixes below, code is unchanged: every file was compared token for token with its comments stripped. The empty CMake branches inProjectOptionsCommon.cmakekeep a one-line why.The comment-size gate reads every language.
verify_comment_size.pygrades shell, Python, PowerShell, Dockerfiles, YAML, TOML, CMake and the////* */languages at one line (COMMENT_SIZE_LIMIT). Heredocs, here-strings and multi-line strings are data; licence headers, tool directives and//////!//** */API docs are exempt. The 32 blocks it still finds (the guard hooks, CMake function docs, help texts, scripts inside strings) are frozen incomment-size.allow(code-quality-tooling.md).What leaned on comments.
test-cuda-cross-sbsa.shsliced the script at a comment and now slices atcuda_cross_packages(). The gate registry credits a mutation by the target naming its gate, solint-root.sh,lint-workflows.shandlinux-x64.ymlkeep one line naminglint-shell.sh,workflow-conventions.allowandverify_mutations.py.Budgets.
code-dupes.allow:Install-ScoopTools.ps1with itself 16 -> 26 (the notes that split its two fetch arms moved towindows-cross-builds.md), andtest-prevention-gates.shwith itself at 12 (one case per language).file-size.allow: seven shrinks, four stale rows dropped.Cost. Every edited Dockerfile and baked script re-keys its layer, so the next Linux and Windows image builds run cold from the first edited step.
2026-09-30 - CON38-CON40, and the code-dupes gate green again#
CON39: clang-tidy selects the image’s GCC. Clang reads
<triple>-<driver>.cfgfrom the directory it was reached through, and clang-tidy reaches it as the compile database’s/usr/bin/clang++, where no cfg was: it selected the distro GCC 16 on all three arches.write_clang_gcc_toolchain_cfgnow writes the pair into every directory of/usr/binand/usr/local/binthat links to the driver; the smoke grades clang-tidy too. Package stage.CON38: atheris finds libFuzzer. Its
find_libfuzzer.shprobes only the pre-per-target namelib/linux/libclang_rt.fuzzer_no_main-<arch>.a.link_compiler_rt_legacy_nameslinks the fuzzer, asan and ubsan archives there, and the smoke runs atheris’ probe. atheris 3.0.0 and upstream build from source on 3.14 in the published image with the links recreated. Package stage.CON40: a
3.14venv after a3.14tone is a GIL venv. uv 0.12 lets a plain3.14take a cached free-threaded build, anduv_ensure_python_availablestripped thet.uv_venv_createasks uv for3.14+gil, andpython3.14no longer satisfies3.14t(python-ci.md). Lane-time only.code-dupes: the torch and torchvision builders share one owner for their repeated steps (
Save-TorchRocmTree,Get-TorchRocmPythonTag,Install-TorchRocmBuiltWheel,Copy-TorchRocmEnv); the test harness owns the module-import closure two suites spelled (Get-ModuleImportClosure), andTorch.Rocm.Tests.ps1one refusal assertion. The rest are recorded with a measured reason, six shrunk budgets written down and two stale rows dropped. The builder edit re-keys the Windows rocmtorch-rocm-wheelsstage.
2026-09-30 - The arm64 and riscv64 Vulkan loaders carry X11, XCB and Wayland (CON41)#
Measured on the
:latestof 2026-09-29. amd64’s LunarG loader lists 26 instance extensions, includingVK_KHR_xcb_surface,_xlib_and_wayland_. The cross-built arm64 and riscv64 loaders list 22 and none of those three, so vkcube underxvfb-runwith lavapipe aborts there. That is what stops BeschleunigerBallett’s windowed GPU suites on arm64 (run 36615897603).Cause:
_vulkan_target_build_loaderpassedBUILD_WSI_{XCB,XLIB,WAYLAND}_SUPPORT=OFFon purpose, from when TVM’s compute-only use was the loader’s only consumer.Fix (sdk stage,
02-toolchain/vulkan.sh): the three options, plusBUILD_WSI_XLIB_XRANDR_SUPPORT, areON. Upstream makes each one a REQUIRED pkg-config lookup. A new export check (_vulkan_loader_wsi_missing, viareadelf) fails the REQUIREDvulkan-loadercomponent whenvkCreate{Xcb,Xlib,Wayland}SurfaceKHRis missing. The loader still needs onlylibc.Smoke:
check_vulkan_loaderalso lists the instance extensions (this needs no ICD and no display)._vk_wsi_verdictthen fails any arch, amd64 included, whose loader lacksVK_KHR_surfaceor one of the three platform surfaces.Proven without the chain: the loader was cross-built through the real
_vulkan_target_build_loaderfor both arches, with Ubuntu’s cross GCC. Both builds export all three entry points and list 26 extensions. Swapped into the published images, vkcube renders on llvmpipe over xcb on arm64 and riscv64. With the target xcb/x11 dev packages missing, the configure fails.Tests:
test-vulkan-target-decomposition.sh(the WSI-on argv, and a WSI-less or absent loader is fatal) andtest-runtime-image-gates.sh(the verdict’s cases). Seven new mutations, all of which bite.Docs:
vulkan-foreign-arch-sdk.md§ The loader carries the window systems. The change ships with the next:latest. The rebuild starts at the sdk stage.
2026-09-30 — Home Assistant configuration pass#
Grid statistics corruption found: the bitshake smartmeter emitted
-1.4e13 kWhtwice in 24 h;total_increasingturned each glitch into a multi-trillion-kWh jump in the grid import/exportsum. New trigger-based template sensorssensor.netzbezug/sensor.netzeinspeisungreject non-positive, decreasing or >1000 kWh-jump readings (guard simulated against the logged glitch values). The energy dashboard now uses them; the grid statistics (hourly and 5-minute, back to 2026-09-02) were recomputed from the per-hour meter states, skipping implausible values, and copied onto the new sensors — daily import now reads 10–22 kWh instead of ~10¹² kWh. Verified that post-restart 5-minute stats continue the repaired sum without a jump. DB backup:~/ha-pre-statsrepair-20260930/.Backups: deleted the two pre-password manual archives (Oct 2025, May 2026) that held
secrets.yamland.storageunencrypted; only encrypted automatic backups remain.Registry cleanup (HA stopped, files backed up to
~/ha-pre-registrycleanup-20260930/): removed the 15 orphanedautomation.*entity-registry entries left behind when the automations were dropped, their 4exposed_entitiesrows, and the 5 staleautomation.*repair records (already dismissed, non-persistent). The dismissedsundeprecated_sun_solar_risingrecord stays so it cannot resurface.Recorder: also excludes P110 voltage/current and day/month counters and two phone sensors (~49% of last week’s state rows); verified after restart that the excluded entities stop being written and energy-dashboard sensors are untouched.
Logger: silenced the
habluetooth.manager/aiodhcpwatcherpermission errors that a rootless container always hits.Dead shell commands removed:
turn_on_rechenkiste, the three F@H commands andsend_mailhad no caller since the automations were dropped, and could not run anyway (wakeonlan/mailare not in the image, no SSH private key).turn_off_rechenkistestays but needs its key restored.
2026-09-29 - Windows ROCm torch source build: first rocm build green, after three fixes#
-Variant rocm -Stages torch,final on the 2026-09-28 rocm parent is green: BUILD_RC=0, smoke gate 215
assertions (1 skipped), rocm-checks\Torch.ps1 reports torch 2.14.0+rocm10.0.0 (HIP 7.15.26333) and
torchvision 0.29.0+rocm10.0.0 compiled for gfx1200 and gfx1201. The first three runs each died on one of these:
The build venv reported
win32. The image’s clang-built CPython needs the basesitecustomize.pyshim, and a venv does not load it: uv resolved 32-bit wheels (pyyaml’s sdist build then died on a 32-bit Cython).Copy-TorchRocmVenvShimcopies the shim andpython3.dllin, asBuild-TorchApp.ps1does, and the builder stops unless the venv reportswin-amd64.No
libomp140.x86_64.dllin the VS 18 redist. MSVC 14.51 ships the release DLL only underdebug_nonredist, so the wheel no longer carries it.torch_cpu.dllresolves it from System32, where the image’s VS install puts it, andAssert-TorchRocmSystemLibompchecks that before the compile.torchvision’s import check had no PIL, and took a finished torch build with it. torch and torchvision are now two RUNs (new
Build-TorchvisionRocmFromSource.ps1, with pillow and on the torch RUN’s venv). The torchvision ARGs sit below the torch RUN, so a torchvision failure, edit or bump keeps the torch layer.
Measured: torch 4051 s cold, 1719 s with sccache warm; torchvision 228 s; the whole driver run 54 min.
Also: failure-modes.md gains the containerd snapshot sweep that stalled every new RUN for 50 minutes on the
same evening (about 440 GB deleted, nothing wrong), and a driver comment now points at windows-rocm.md.
2026-09-29 - compiler caches save on the default branch only#
compiler-cache-save’senabled: autonow saves on pushes to the default branch only, andcontainer-ci-windows.yml’s save step does the same. It used to save on every push, and the deletion of older entries works per branch: a release push tomainwould have stored a second generation of every key, about 7.3 GB more in BeschleunigerBallett.Nothing is lost on other branches: every other branch, pull request and dispatch restores the default branch’s entry, which GitHub lets any branch read.
Docs:
build-cache-tiers.mdand the actions README.
2026-09-29 - compiler caches keep one entry per key#
compiler-cache-savedeletes the key’s entries from older runs on the same ref before it saves. Each save adds a per-run entry, and the superseded ones stayed until GitHub’s quota eviction. That eviction takes the least recently used entry of the whole repository, not of the key. BeschleunigerBallett reached 11.2 GB and OxidANT 12.4 GB of the 10 GB quota, and Ballett’s TSan job restored nothing because its only entry went while stale x64 duplicates survived.What the saving job needs:
permissions: actions: write. Without it the step warns and saves anyway. An empty or missing directory deletes nothing.New inputs and outputs:
path(what to save) andtokeninputs, and aprunedoutput.New action
compiler-cache-prune, the same deletion (prune.sh, shared with the save). It is for lanes that save elsewhere.container-ci-windows.yml’s job grants onlycontents: read, and a reusable workflow cannot ask for more than every caller grants, so a caller settingcompiler-cache-keyprunes from a later job.Self-test: it now asserts that no older selftest entry survives a save, and that compiler-cache-prune then finds nothing.
compiler-cache-prunedeletes only once this run saved its own entry (require-saved, defaulttrue), so a build that died before its save keeps the older one.Docs:
build-cache-tiers.mdand the actions README.BeschleunigerBallett’s
permissionscensus row is deleted. Itsreusable-linux.ymlnow declarescontents: readat the top andactions: writeon the four cache writers, which takes the census from 11 to 0.
2026-09-29 - -DisableSccache beats the preset; the sccache-on-modules docs are corrected#
Invoke-CmakeConfigureAndBuild -DisableSccachenow disables sccache. It only cleared environment variables, and a preset withCOMPILER_CACHE=sccachemadeCache.cmakeset the launcher again. A reused build tree kept it inCMakeCache.txtas well. The configure line is built by the new, exportedGet-CmakeConfigureArgs, which puts-DCOMPILER_CACHE=and both emptyCMAKE_<LANG>_COMPILER_LAUNCHERafter the caller’s arguments. Tested inWindowsCMake.Common.Tests.ps1.The docs said sccache “caches nothing” on a C++23 modules build. That was wrong, and the “harmless to leave wired up” that followed was dangerous. The released 0.18.0 with clang-cl caches importers without hashing the BMIs they import, so after an interface edit a warm cache returns stale objects (reproduced on BeschleunigerBallett and AccelerANTgine). Rewritten from measurement:
windows-container-build-performance.md§ sccache on a C++23 modules build, with the one-liners inwindows-builds.mdandwindows-build-resources.md.
2026-09-29 - Windows ROCm: torch 2.14.0 / torchvision 0.29.0 built from source against ROCm 10.0#
Owner decision: the Windows rocm image builds PyTorch here instead of installing AMD’s wheels.
AMD’s ROCm 10.0 Windows wheels stop at torch 2.13.0, the app locks 2.14.0, and Assert-TorchRocmVenvMatch
refused the pair as soon as the torch stage tracked the app’s develop.
Dockerfile.torchgains atorch-rocm-wheelsstage (Build-TorchRocmFromSource.ps1). It builds on the rocmBASE_IMAGE, never onapp, so an app move cannot re-run the compile, and onlyrocm-1mounts it, so cpu and nvidia never build it.Sources are upstream
v2.14.0/v0.29.0atPYTORCH_VERSION/TORCHVISION_VERSION, fetched by the newTORCH_ROCM_WINDOWS_PYTORCH_COMMIT/_TORCHVISION_COMMIT. They are compiled against the image’s TheRock SDK for theROCM_WINDOWS_GFX_FAMILYtargets (gfx1200, gfx1201).The recipe is TheRock’s
external-builds/pytorch/build_prod_wheels.py: HIPIFY, TheRock’s clang-cl,torch/_rocm_init.pyandrocm[libraries]==<release>, so the wheels load ROCm like AMD’s.Off in this first build: AOTriton (flash / memory-efficient SDPA; its CMake fetches unpinned sources at build time),
torch.distributedand torchvision’s image IO.Install-TorchRocm.ps1installs the two built wheels (exactly one each,+rocm<release>, hashed on the spot) with AMD’s pinned runtime:rocm,rocm-sdk-core,rocm-sdk-librariesand a device wheel per GPU. The eight AMD torch, torchvision, device and bootstrap pin pairs are gone fromversions.env.rocm-checks\Torch.ps1now asserts torch was compiled for every GPU rocBLAS serves (torch._C._cuda_getArchFlags()), instead of AMD’s per-GPU torch device wheels.The driver forwards
PYTORCH_VERSION,TORCHVISION_VERSION,ROCM_WINDOWS_GFX_FAMILYand the sccache endpoint to the torch solve on rocm only.Records:
deps.json, the curated SBOM and the licence tables describe the source build;bump_versions.pyregisters the two commits.Tests:
Torch.Rocm.Tests.ps1rewritten for the new set,Torch.RocmSourceBuild.Tests.ps1for the builder’s pure parts.Rocm.Migraphx.Tests.ps1lets only that one rocm-only stage mountWindowsMigraphx.Common.Not yet proven: no rocm build has run this stage. Building against AMD’s SDK tarball rather than the
rocm-sdk-develwheel has no public precedent:windows-rocm.md§ PyTorch on the rocm lane.
2026-09-29 - The torch stage builds OrchestrANT’s develop, at the commit it points to#
Owner decision: the images take the app from its develop branch, and a fix the build
needs goes to develop directly instead of into a release tag. APP_REF in versions.env
is now develop and names what to track.
Resolved to a commit when the run starts, never used as a name. BuildKit keys the torch RUN on its build args, so a fixed
developwould reuse the first clone forever and ship a stale app behind a green build.runtime_resolve_app_ref(Linux, once per run before the per-arch loop) andResolve-TorchAppRef/Resolve-GitRefCommit(Windows) resolve the ref withgit ls-remote. The torch layer then rebuilds exactly when the app moved, every arch of one index builds the same commit, and the commit is the image’s version label. A 40-hexAPP_REFis built as given.Fetched as a commit.
assemble-torch-app.shandBuild-TorchApp.ps1fetch a 40-hex ref withgit init+fetch --depth 1+checkout FETCH_HEAD; a name still clones, with a warning, for a plaindocker build.The ROCm extra is
pytorch-rocm10(runtime_gpu_backend_pair,smoke-torch-venv.sh), as the app names it ondevelop.Cost: the
versions.envedit re-keys the Linux chain once; later app moves rebuild the torch stage only. An unreachable remote stops a real run;--dry-runonly warns.Known gap: the Windows ROCm variant installs AMD’s ROCm 10 wheels, which stop at torch 2.13.0, and refuses an app lock on 2.14 (
Assert-TorchRocmVenvMatch), so-Variant rocmon Windows fails its torch stage until torch 2.14 is built from source here.Removed:
bump_versions.py’s unregisteredspec_app_ref, which proposed the newest release tag forAPP_REF.Tests:
test-app-ref.sh(resolution, precedence, failure, the commit fetch, both entry points wired) andBuildDriver.AppRef.Tests.ps1.linux-cross-builds.md.
2026-09-29 - Linux lanes can keep their compiler cache across runs#
Two new actions, compiler-cache-restore and compiler-cache-save, carry a Linux container
lane’s /var/cache/{sccache,ccache} between CI runs through actions/cache. Until now every
run compiled cold, because each step’s --rm container took the cache with it. In
AccelerANTgine’s linux-x64 lane, measured against a same-time control, the clang job fell from
25.1 to 14.4 min. Saves happen on pushes only, one key per lane, sized for the 10 GB repository
quota: docs/build-cache-tiers.md § Keeping the compiler cache across CI runs. Windows lanes
are not covered yet.
actions-selftest.yml exercises both actions (a write through the mount, read back on the host)
and now compares the Windows deep lane’s action-directory count with the host checkout instead
of a literal 11, which had been wrong since the twelfth action.
container-ci-windows.yml takes a compiler-cache-key too. It restores the cache into the
workspace and hands the container CI_COMPILER_CACHE; the build script moves it into
container-local directories, because sccache cannot write to a mounted one on Windows. Not for
clang-cl module builds until mozilla/sccache#2876 ships.
2026-09-29 - The consumer-pins gate holds torch, torchvision and onnxruntime-genai#
consumer_pins.py compared only a consumer’s ruff pins with versions.env. The image build
force-installs PYTORCH_VERSION and TORCHVISION_VERSION over the app lock, so OrchestrANT’s
torch==2.13.0 meant 2.13.0 on a dev box and 2.14.0 in the image, and no gate said so.
Five new rows: the
"torch=="and"torchvision=="pins, their riscv64pytorch.git@v<tag>/vision.git@v<tag>source pins, and"onnxruntime-genai[-cuda]==". The unpinnedonnxruntime-genai-directmlis not a mention.A leading
vis dropped before comparing. These three keys carry the GitHub tag (v2.14.0); a consumer pins the bare number.Proven: new cases in
test-version-snapshot.sh, and the gate’s first two mutations (version-snapshot.consumer-pins-tag-prefix,version-snapshot.consumer-pins-torch-row).Rollout: a consumer’s
lint-gates.ymlruns this fromdevelop, so a lagging consumer goes red on its next push. OrchestrANT moved first (febd7ea: torch 2.14.0 / torchvision 0.29.0), so its lane stays green.code-quality-tooling.md.OrchestrANT’s ROCm extra is now
pytorch-rocm10(therocm7.14wheel line, asPYTORCH_ROCM_INDEX). The ROCm wrapper keeps passingpytorch-rocm71, which the pinnedAPP_REF=v0.0.28defines; the change that movesAPP_REFmust switchruntime_gpu_backend_pairandsmoke-torch-venv.shwith it:linux-accelerator-images.md.
2026-09-28 - Windows images: faster native libraries, KleidiAI on arm64#
An audit of the Windows build before the CON12 rebuild found four things that left runtime
speed on the table. All four ship with the next :winamd64/:winarm64 build:
GStreamer (both arches) passes
-Dbuildtype=release, as the Linux lane does. Without it the monorepo’sdefault_optionsgavedebugoptimized:/Ziobjects, and GLib’sglib_debugchecks switched on in every shipped GLib.OpenCV (amd64) sets
CPU_BASELINE=AVX2. Every TU was already compiled for AVX2+FMA, but OpenCV’s baseline universal intrinsics stayed at SSE3/128-bit. A configure gate fails if the baseline does not report AVX2.LiteRT (amd64) gets the image’s AVX2 baseline through
CMAKE_*_FLAGS_INIT. Its own kernels had built at SSE2.ONNX Runtime (arm64) builds MLAS with KleidiAI, runtime-dispatched. Only
build.pyturns it on, and this script configures through CMake. XNNPACK, OpenCV (KleidiCV), Arm NN and ACL have no usable Windows-on-Arm KleidiAI/ACL path at the pinned versions:docs/windows-cross-builds.md.
Then, the same day, the owner took the four follow-ups:
KleidiAI’s
.Skernels are assembled through a preprocess step. Upstream’sASM_MARMASMrule hands them raw to armasm64, which has no C preprocessor and rejects the/arch:armv8.2KleidiAI adds. Measured in:winamd64: A2029, then A2003/A2230/A2034, until the rule becameclang-cl /P /EP /TC /U__clang__then armasm64 (178/178 objects). ORT gets that patched tree throughFETCHCONTENT_SOURCE_DIR_KLEIDIAI; configure fails if its rule is not the wrapper.ONNX Runtime ThinLTO on the CPU and arm64 builds (not with CUDA, as ORT itself withholds
/LTCGthere). A probe of the image’s clang-cl + llvm-lib + lld-link produced bitcode objects and a linked DLL for amd64 and arm64.versions.env’sORT_ENABLE_LTOstays the Linux knob.GStreamer also passes
-Db_ndebug=if-release, so every subproject dropsassert().FFmpeg on every native amd64 lane gains AMD AMF (was rocm-only) and NVENC/NVDEC/CUVID (was nvidia-only). Both are header-only and load the vendor DLL at run time, so the CPU image imports nothing new. Vulkan stays rocm’s. The smoke gate lists
h264_amf/hevc_amfand the NVENC encoders on the CPU image.FFmpeg software codecs (amd64):
Build-FfmpegCodecs.ps1builds static dav1d (AV1 decode), x264 and x265 (H.264/HEVC encode) and links them into FFmpeg’s DLLs, so no codec DLL can meet GStreamer’s. They use the static CRT, as FFmpeg’s DLLs do (avcodecimports no CRT DLL). x265 4.1’s two OLD policies are set NEW, because CMake 4 refuses them. dav1d and x264 are the sources GStreamer’s own wraps pin; the six pins live inversions.envand travel as media-core build args. The arm64 cross lane has none yet: it needs GStreamer’s meson cross file and aarch64 asm shim reproduced first.
2026-09-27 - :winamd64 is CPU + DirectML; the CUDA build is :winamd64-nvidia#
The owner decided that Windows follows the variant rule in AGENTS.md § Image and tag
naming. Build-Buildkit.ps1 -Gpu (-Variant nvidia) now writes bk-*-nvidia stage tags
and a winamd64-nvidia final image, or winarm64-nvidia for the cross bundle, and
Resolve-BkVariant refuses to push it under any other tag. :winamd64 is the build without
-Gpu. Until now the nvidia build wrote the default tags, so the :winamd64 published on
2026-09-22 carries CUDA until that tag is republished. No consumer needs CUDA from the
Windows image, so the switch needs no consumer commit (BACKLOG CON12).
2026-09-26 - The image backlog, swept: fixed in source, decided, or the owner’s#
Every open item of BACKLOG.md was worked. Most are image fixes that ship with the next
publish (CON11 for :latest, CON12 for :winamd64), each proven against the published
image in a throwaway container or a buildkit probe; the rest are recorded decisions or wait
on the owner. Both chains rebuild from base at the next publish anyway (versions.env
changed since each published image), so none of this adds a re-key.
Hub scripts, effective now.
Copy-MediaRuntimeBundlefinds the family image’s GStreamer throughGSTREAMER_BIN(C:\runtime\bin); AccelerANTgine run 36044940426 had staged only ONNX Runtime (CON33). The Rust driver checks forcargo fmt/cargo clippyinstead ofrustup component add(CON32).Android API 37 (CON14). The
android-sdk-sharedcache id never named the two API 37 pins, so every build since 2bb0410f restored an SDK tree cached on 2026-08-22. The id names every pin (tests/test-android-sdk-cache-key.sh); a restored tree is checked against sdkmanager’spackage.xmlrecords, completed and refreshed; the smoke checks every platform.The Linux toolchain (CON15-CON17). A bare
clang/clang++selects${GCC_PREFIX}through<native-triple>-clang{,++}.cfg; clang’s ownclang-tidy,llvm-profdata,llvm-cov,llvm-symbolizer,ld.lldand 16 more are onPATH(clang-formatandllvm-configstay 21 on purpose); compiler-rt builds libFuzzer.validate-compilers.sh smokechecks all three.The Linux runtime (CON18, CON19, CON20, CON21, CON23).
VIRTUAL_ENV/UV_PYTHONno longer send uv at the root-owned/opt/venv; lavapipe gives a CPU Vulkan device;perf,jq,Xvfband gperftools ship; amd64 drops the unused distro GTK 4 and GStreamer 1.28 runtime;libcamera-env.shand the entrypoint let a caller’sLD_LIBRARY_PATHwin without shadowing GCC 16.2’slibstdc++.consumer-image-contract.md§ What changes with the image after CON11 is the consumer’s list.The Windows image (CON9, CON10, CON25, CON28, CON29). The patched LLVM builds
clang_rt.profileand a clang-tidy that reads its own BMIs, both proven by rebuilding the tree that ships in:winamd64, and its 7.1 GB source tree no longer stays in the layer; the final stage puts LunarG’svulkan-1.dllon PATH (never System32; OpenGL stays host-only, the owner’s decision); the app venv installsai-edge-litertfrom the lock; GStreamer’sgdkpixbufplugin builds on amd64,enabledso its next loss fails meson setup; no bakedC:\workspace. The first local rebuild with Blackwell in the arch set found that sccache 0.18 aborts ORT’s PTX-only LLM kernels (mozilla/sccache#2862), so ORT’s nvcc stays bare for such an arch list.Decided (§ Deliberate): arm64/riscv64’s distro GStreamer (CON21), their thin GCC (CON22), riscv64 TVM/IREE targets (CON24, with the flags in
riscv64-rva23-baseline.md), lane-installedcargo-audit/cargo-denyand no Linuxpwsh(CON20), five Windows absences (CON28), and OpenGL in the Windows image, host-only by the owner’s decision (CON25). CON27 waits on microsoft/STL#6298.Found on the way: amd64’s GCC carried 4.4 GB of unstripped cross compilers, because
build-gcc.shstripped with the target’sstripalone; the build machine’s runs too now (CON36). amd64’s TVM could not load its compiler library: a native build linked apt’s bootstrap LLVM, which the image lacks. It links the LLVM the image ships now, and the venv smoke fails a TVM that cannot compile (CON35).
2026-09-26 - The reusable Windows lane takes a caller’s secrets#
BeschleunigerBallett’s x64 lane could not become a thin caller of container-ci-windows.yml:
its build fetches the MSIX signing certificate over WebDAV, and the reusable lane accepted
no secret but GHCR_PAT.
container-ci-windows.ymltakes an optionalCONTAINER_SECRET_ENVsecret, oneKEY=valueper line, which the caller composes from its own secrets. The compose step masks each value and writes the pairs to an--env-fileinRUNNER_TEMP(LF, UTF-8 without BOM), so no value enters a command line, a step output or the job environment. A line that is notKEY=valueis refused without being echoed.windows-cross-builds.md§ Consumer cross lanes and the adoption guide describe it.
2026-09-25 — Home Assistant 2026.9.1 → 2026.9.3#
Upgraded the live container. glances was already current (4.5.6).
README § Updating:
up -dalone does NOT pick up a pulled image under nerdctl compose; the documented command now carries--force-recreate, and warns that the “orphaned” container reported for a single-serviceupis the other live service, not debris.
2026-09-25 - One Windows module stages and proves a consumer’s ONNX Runtime#
OxidANT, OmniAccelerANT and AccelerANTgine each carried the same glue around G6, as
local modules under three names: the ORT family’s names, “replace the ORT beside the
exe with the chain’s”, and the proof of a shipped tree. It is
WindowsOrtPayload.Common now, a consumer-side module.
Copy-ChainOrtBesidestages the core, the provider bridge andDirectML.dllfrom the chain install;-Allstages every DLL of itslib\andbin\, EP sidecars included. It goes throughGet-OnnxChainLayout, so a non-chain or wrong-archONNX_ROOTthrows before anything is copied. Two of the three copies only checked thatbin\onnxruntime.dllexisted.Assert-ChainOrtTreeis G6 over a tree, plus what G6 does not grade: MISSINGonnxruntime.dllwhere the exe looks, a STRAY family name the chain has not got, a CHANGEDDirectML.dll. STRAY is judged against the chain’s own file names, not a fixed list, so a chain with a CUDA or QNN provider does not trip it, and those names include the chain GenAI install’s whenONNX_GENAI_ROOTis set: AccelerANTgine’s stagedbin\carriesonnxruntime-genai.dll.-WaiveUnresolvedkeeps AccelerANTgine’s Python-package mode.New-OrtProvenPayloadbuilds and proves a package payload in a fresh directory;-IncludeDirectorycarries a tree beside the exe with it (OxidANT’slib\gstreamer-1.0), graded by G6 with the rest.OrtPayload.Common.Tests.ps1holds OxidANT’s cases, moved, plus-All, a chain provider that is not STRAY, and-WaiveUnresolved.onnxruntime-single-source.md§ The shared Windows glue documents it; the consumers drop their copies when their hub pins move.
2026-09-25 - The arm64/riscv64 native GCC has multiarch (CON8)#
BeschleunigerBallett’s arm64 GNU presets could not find X11, while its Clang preset
could. Measured in the published :latest (ec4bb68b) under qemu: the arm64 GCC
printed no -print-multiarch, its link line held no /usr/lib/aarch64-linux-gnu, so
CMake left CMAKE_LIBRARY_ARCHITECTURE empty and find_library never looked there.
The amd64 GCC prints x86_64-linux-gnu. The arm64 and riscv64 GCC is the Canadian
native, and build-gcc.sh configures every --target build with
--with-native-system-header-dir, which switches GCC’s multiarch auto-check off.
build-gcc.shpasses--enable-multiarchwhen--hostequals--target(_gcc_native_multiarch)._gcc_is_canadian_nativeis now the one owner of that test, shared with libsanitizer’s helper. Plain cross compilers are unchanged.swap-native-gcc.shstops the build when the relocated GCC prints another multiarch, where the build host can run it.test-native-gcc-multiarch.shand thenative-gcc-ma.*mutations prove the helper, its wiring and the gate.cross-build-verification.md§ The native GCC has multiarch has the measurements.In source only: it ships with libsanitizer in the next Linux
:latest(CON11).
2026-09-25 - MSIX -Sign takes the signing root from the caller#
Invoke-MsixPackage -Sign looked for the signing .pfx in the staging directory’s
parent. Every consumer stages inside a build directory, so none could use it.
AccelerANTgine and BeschleunigerBallett called Invoke-MsixSign themselves with their
repository root, and OxidANT never signed.
-Signnow needs-SigningRoot, the directory that holds the.pfx: the repository root, where the family keeps it gitignored. Without it the call throws before it stages anything. No caller in the family’s checkouts passed-Sign, so nothing that worked breaks.Invoke-MsixSign’s warning names the directory it searched. It used to say “repository root” whatever it had been handed.WindowsMsix.Common.Tests.ps1pins both halves, and each new case fails against the old code. Its packer cases now share one makeappx stub and one resolver mock.
2026-09-25 - x64 lanes move onto the reusable Windows lane: version-file, host-command#
The family’s next sharing step (owner request): the consumers’ windows-x64.yml
lanes become thin callers of container-ci-windows.yml, one repo at a time,
OxidANT first. Two things an x64 lane did by hand had no input.
version-file: the hub’sversion_util.shresolvesVERSIONandMSIX_VERSIONon the host, from the file, the ref name and the run number. The container gets both as environment.host-command: PowerShell on the runner host after the container build, in the workspace the container built into. It is for what Server Core cannot run (noopengl32.dll), such as OxidANT’s WebGPU renderer tests and a run of its x64 product.Every native command in
host-commandandrun-commandcounts ($PSNativeCommandUseErrorActionPreference). Before,run-commandfailed the lane only through the exit code of its last line.windows-cross-builds.md§ Consumer cross lanes and the workflow’s header describe both.
lint-workflows.sh is clean.
2026-09-25 - x64 packages carry the same DLL closure as arm64#
Owner decision: the consumers’ x64 packages ship what their binaries import, as the arm64 ones do. A clean x64 PC lacks the media stack and may lack the VC++ runtime too, and AccelerANTgine’s x64 MSIX carried neither.
Get-ProductDllSearchPath(WindowsCrossBundle.Common) gives one search order for every consumer’s closure:the chain ORT (
ONNX_ROOT\bin), so an ORT-family import resolves to it;the image’s
C:\runtime\bin;the target arch’s
VCToolsRedistDirCRT. Only existing directories are returned.
cmake/CPackCommon.cmake:kataglyphis_install_package_dllsinstalls the DLLs of a directory named at configure time (KATAGLYPHIS_PACKAGE_DLL_DIR) beside the executables. A build script fills it with the closure before packaging, so the CPack packages ship it too.windows-cross-builds.md§ Consumer cross lanes describes both.
Tests:
CrossBundle.Common.Tests.ps18 pass. Reordering the search, ignoring the arch or dropping the existence filter each fails it.test-cmake-windows-arch.sh8 pass. Two newcmake-arch.*mutations (the install rule, the*.dllfilter) bite.
2026-09-25 - The three consumer cross lanes are green; arm64 binaries ran#
OxidANT, AccelerANTgine and BeschleunigerBallett each gained
windows-arm64-cross.yml, a thin caller of container-ci-windows.yml. All
three went green on their first runs (36136967538, 36142875090, 36142882316).
Each cross-built in the arm64 bundle and passed the arch gate: 12, 22 and 8
arm64 binaries, no unresolved import. Each product then ran natively on
windows-11-arm. It is the first execution of any binary from the bundle on
arm64 hardware, though only as far as loading.
BACKLOG.mdCON30 says what that proves and what it does not.windows-cross-builds.md§ Consumer cross lanes records the three runs. It also records how BeschleunigerBallett’s run job borrows the Khronos Vulkan loader, pinned by hash, on the GPU-less runner.
2026-09-25 - A CMake consumer’s cross build names its target#
AccelerANTgine and BeschleunigerBallett build with CMake, and nothing they call told CMake, Corrosion, FindVulkan, the hardening flags or CPack that the target was arm64. Each of those follows the host by default.
Get-CrossConfigureArgs(WindowsCrossBundle.Common): empty on amd64. On arm64 it returnsGet-CMakeCrossArgs, plusRust_CARGO_TARGETunder-CorrosionandVulkan_LIBRARYfrom the SDK’sLib-ARM64under-Vulkan. A missing arm64 import library throws and names the optional SDK component.Get-WindowsPackageArch:x64orarm64, the one spelling an AppxManifest’sProcessorArchitecture,wix build -archand the VC++ redist directory share.cmake/Hardening.cmake:/CETCOMPATon x64 targets only. An ARM64 link refuses it, and the flag was unconditional underMSVC, which clang-cl sets.cmake/CPackCommon.cmake:CPACK_WIX_ARCHITECTUREisarm64when the package arch isaarch64. Left unset, CPack derived x64 from the pointer size. An arm64 package also installs the VC++ runtime withoutvcruntime140_1.dll(kataglyphis_arm64_system_runtime_libs). The arm64 redist folder carries that file as ARM64EC, an x64-machine PE, and AccelerANTgine’s first cross build shipped it and failed the arch gate.CMakeidentified the target correctly: the file really is in the arm64 folder.windows-cross-builds.md§ Consumer cross lanes: the configure side, and why a cross lane builds Release only.
Tests:
test-cmake-windows-arch.sh(new) configures a compiler-less project with MSVC faked: AMD64 keeps/CETCOMPAT, andARM64,arm64andaarch64drop it, and the arm64 runtime list keeps everything butvcruntime140_1.dll. 6 pass, and fourcmake-arch.*mutations (a new declared family) bite.CrossBundle.Common.Tests.ps17 pass. Dropping the host shortcut, reading the x64 VulkanLibor ignoring-Corrosioneach fails it.
2026-09-25 - A cross lane’s product carries its DLL closure#
The arm64 run job needs a folder that runs on a clean device. The CRT, ONNX Runtime and everything they import have to be in it, and nothing in the hub could collect them.
WindowsCrossBundle.Common(new):Copy-PeImportClosure -Path <binaries> -SearchDirectory <dirs> -Destination <dir> -Arch arm64. It walks static and delay-load imports (the set the import walk grades) transitively and copies what the search directories hold, the first directory winning. Names found nowhere are left to the device, and a closure DLL of the wrong machine throws throughAssert-PeTargetMachine, the media gates’ own check. It is a new module becauseWindowsTargetArch.Common, where the PE readers live, is mounted into every media stage.The synthetic PE builders (
New-OrtTestPe,New-OrtTestExportTable) moved from the census suite intoTestHarness.psm1.New-OrtTestPetakes a-Machineand writes a delay-load table for-DelayImport.windows-cross-builds.md§ Consumer cross lanes says what the folder must hold.WebDavClient’s Linux lane split into
linux-x64.yml+linux-arm64.yml; the rename table andpython-ci.mdrecord it. OrchestrANT’s split stays open.
Tests: CrossBundle.Common.Tests.ps1 3 pass. Dropping the transitive enqueue,
the delay-load table, the first-directory rule or the machine check each fails
it. The census suite still passes 25 with its builders moved. The dupes
allowlist moves the builders’ two rows to TestHarness.psm1 unchanged.
2026-09-25 - The Windows arm64 cross lanes get a hub lane; the gate knows the bundle#
Owner decisions of 2026-09-25: windows-arm64-cross.yml lanes for OxidANT,
AccelerANTgine and BeschleunigerBallett, built in the arm64 bundle on the amd64
runner, plus a job that runs the product natively on GitHub’s windows-11-arm.
Lanes share through hub reusable workflows with thin callers. Until now nothing in
the family could even name the bundle in CI: verify_ci_image_refs.py accepted
exactly two canonical tags and had no route for a third.
versions.env:CI_IMAGE_WINDOWS_ARM64_TAG=winarm64(noforward). Like any edit to that file it re-keys the next image build.prepare-windows-container-hostandrun-in-windows-container:target-arch: amd64|arm64(default amd64; anything else fails in the first step);an
image-arm64input whose default is the bundle;the prepare action outputs the
imageit pulled.
verify_ci_image_refs.py: the arm64 ref is canonical.Check A grades every image input of an action, so six defaults now instead of four.
Check C takes the ref of the input a literal is handed to.
The bundle spelled out anywhere is still a copy (check D).
ci-image-ref.sh --windows-arm64andGet-CiImageReference -Windows -TargetArch arm64ask for the same ref.container-ci-windows.yml(“Container CI · Windows (reusable)”): one Windows container lane fortarget-arch: amd64andarm64. The job:prepare;
run the caller’s build script in the image;
on arm64, run
Test-TargetArch.ps1 -Arch arm64 -ImportWalkover the product, in the same image, from the caller’s own hub submodule;upload the product. With a
run-command, awindows-11-armjob then downloads the product and runs it natively.
actions-selftest.ymlpassestarget-archto both actions and reads the newimageoutput.windows-cross-builds.md§ Consumer cross lanes, the actions README andINDEX.mddescribe all of it.
Tests: test-ci-image-ref.sh 53 pass, with new fixtures for a Windows action
carrying both inputs:
a wrong or missing
image-arm64default fails;the bundle handed to
image:, or the amd64 image handed toimage-arm64:, fails;target-arch: arm64with no image passes.
All 13 mutations on the gate and on ci-image-ref.sh bite, three of them new.
lint-workflows.sh is clean, 26 YAML files. No suite covered
Get-CiImageReference, although ci-image-ref.sh’s header says the two are
held equal. ContainerImage.CiRef.Tests.ps1 now does: all three refs against
versions.env, arm64 refused without -Windows, and a missing arm64 key throws.
Breaking the arm64 key lookup fails it.
2026-09-25 - An APISIX gateway in front of the GenieX lanes (P1: the lab only)#
linux/llm-stack gains a gateway: APISIX 3.18.0, pinned by digest, on
127.0.0.1:9080. Clients ask for an alias (chat, chat-long, agent) with
their own key (webui, lab, agent), and the gateway applies the GenieX rules
the lab measured. chat goes to the NPU. A request estimated too large for its
4k context goes to the GPU before anything is sent, and an NPU
400 context_length_exceeded is sent to the GPU once. With tools, the request
gets the tools prompt P8.1 measured, pinned by the sha256 of its raw bytes. T=0 is
made greedy on the GGUF lanes only. The NPU times out at 300 s and the GGUF lanes
at 1800 s. Nothing is retried once a reply has started, and nothing probes a lane.
raw-<lane> routes, lab key only, skip the shaping; they are the transparency
path for the acceptance runs.
Everything is rendered from a new top-level serving block in backends.json
by gateway/render_apisix.py, which refuses an unknown lane or key, a GGUF on
the QAIRT lane, a missing prompt, a prompt sha mismatch, a listener off
loopback, a route id emitted twice and a lane that is the gateway itself.
scripts/serve-stack.sh up|reload|down|status|keys|validate boots each
candidate in a throwaway container before it goes live, rewrites apisix.json in
place and waits until /gateway/info reports the new config sha. APISIX alone
cannot do four of the rules, so a custom plugin (geniex-shape) and a hook
(geniex_hook) that patches four APISIX internals carry them. That hook is why
an APISIX bump now needs the new e2e suite green (AGENTS.md § LLM Stack).
The registry: geniex-gpu now names the Instruct-2507 GGUF (the chat-long lane,
an owner decision; earlier geniex-gpu runs measured the base 4B GGUF). The 9B
agent lane is a NEW entry, geniex-cpu-9b, because re-pointing geniex-cpu
would change what the lab’s existing commands measure. Six lab-* entries reach
the lanes through the gateway with GW_KEY_LAB and probe: false.
Start-GeniexServers.ps1 takes -BindAddress (default 0.0.0.0, unchanged),
and warns when a lane it skips as already running listens somewhere else.
Tests: test_gateway_render.py (52 offline, with the overlay and serve-stack.sh keys), and tests/gateway_e2e/, 79 tests against the real image and three fake
lanes (GATEWAY_E2E=1; CI job gateway-e2e on x64 and arm64). All 79 passed
live under rootless nerdctl on the arm64 dev host. Removing each hook patch, the
worker envs or the prompt insert turned the suite red where it should. That
run also found a reload rollback that restored apisix.json but not
render.json; fixed. An adversarial review then found and fixed, each with a
test that was red first: live/runtime.env (every key in clear) left 0644 by
the install’s chmod -R a+rX; geniex-shape re-encoding a shaped body with
cjson’s 14 significant digits (a seed of 123456789012345 reached the lane as
123456789012340); route ids an alias could repeat, which APISIX resolves by
silently keeping the last; a lane pointing at the gateway itself; and an
acceptance step (raise budget_tokens past the NPU’s context) the renderer
refuses. The review also added the R5 cases (no request sent twice, no 429,
no cap, a client hang-up closes the lane’s connection), loopback-only
listeners and the hook surviving a hot reload. Details:
linux/llm-stack/README.md § Gateway.
Merging it into develop (6b1532a8) would have turned preflight’s
workflow-lint slug red. The two gateway-e2e matrix rows were one-line flow
mappings (- { arch: x64, runs_on: ubuntu-26.04 }), which the YAML subset that
verify_workflow_conventions.py reads cannot parse. That failed
lint-workflows.sh, the shipped-census assertion in test-workflow-lint.sh
and the actionlint + CI image refs gate of run-lint-gates.sh. The rows are
block mappings now, and the matrix GitHub reads is unchanged.
2026-09-25 - HIP compiles in the rocm image: TheRock’s clang loads the <cmath> overlay#
With torch through (entry below), the rocm chain built bk-winamd64-rocm and the
smoke gate passed 215 assertions. Test-RocmImage.ps1 then failed on one item:
[FAIL] hipcc could not compile a kernel for gfx1201. It was the same clash MIGraphX
hit: MSVC 14.51’s constexpr isgreater and its five siblings against clang’s HIP
math headers, 20 errors for a one-line kernel. MIGraphX only got past it with its own
-isystem overlay, so no HIP compile worked in the image as shipped.
A new diagnostic, Test-HipMsvcCmath.ps1, measured the choices in the image. The
overlay fixes the compile given as -isystem, through HIPCC_COMPILE_FLAGS_APPEND
(hipcc only), through CPATH, and through clang.cfg/clang++.cfg beside
TheRock’s clang.exe. TheRock ships no *.cfg, so the config files overwrite
nothing. They reach every driver name (clang, clang++, amdclang++, hipcc),
and CMake’s HIP language too, and no user environment can undo them.
windows/scripts/hip/ now holds the two headers and both config files. Two COPYs at
the end of Dockerfile.rocm-llama install them, as C:\runtime\opt\hip-msvc-cmath
and beside TheRock’s clang. That is the first rocm stage every rocm build has after
the media stages, and being last keeps its llama RUNs cached. The files belong in
Dockerfile.rocm, but that re-keys the whole chain, so they move at the next full
rocm rebuild (BACKLOG.md CON34). MIGraphX keeps its generated copy for now. A
parity test holds the shipped headers equal to Write-HipMsvcCmathOverlay’s output,
and a second test holds both configs to the directory the Dockerfile installs into.
The probe also compiles with --no-default-config, which reports when a toolset
stops needing the overlay. On the unfixed image it measured MSVC 14.51.36231 with
TheRock 10.0.0: all three drivers fail, and the overlay is still needed.
The rocm chain is through. The rebuild took 24:44 from cache, with
Stages: base, sdk, toolchain, media, migraphx, llama, torch, final (rocm). The smoke
gate passed 215 assertions (1 skipped), and ROCm image checks: all passed (root C:\TheRock\build, kernel compiled for gfx1201). The publish gate passed too. On the
rebuilt image the probe reads both configs back, and hipcc, clang -x hip and
amdclang++ -x hip all compile as the image ships them. --no-default-config still
fails with the same 20 errors, so the overlay stays.
2026-09-25 - The torch ROCm wheels download straight into the cache, with no rename#
With MIGraphX, the EP and llama.cpp through, the torch stage built and verified the
app venv. Its ROCm half then downloaded and SHA-verified three pinned wheels and
stopped: Move-Item: C:\bkmnt\Install-TorchRocm.ps1:324 ... Could not find a part of the path. Save-TorchRocmWheel downloaded each file to <file>.part in the
hash-keyed cache (C:\uvcache\torch-rocm\<sha256>\) and renamed it into place.
torch (51-character file name) and torchvision (57) moved. The first 68-character
name, amd_torch_device_gfx1201-..., did not. The download had just created the
.part there, five characters longer still. So the failing operation is the rename
on the BuildKit cache mount, the create-then-rename class in
windows-build-lanes.md § Run-side wcifs symptoms. The containerd store is
admin-only, so the host-side path length behind the mount was not measured.
Each file now downloads to its cache path directly. The rename bought nothing the
existing checks do not already give: a failed download deletes what it wrote, and
every reuse re-hashes the cached copy. The four cache tests still pass (layout,
offline reuse, tampered copy re-fetched, mismatch leaves nothing). A new one fails
if a rename comes back, and was mutation-checked by re-adding one.
Torch.Rocm.Tests.ps1: 57 pass.
2026-09-25 - The EP stage’s G2 gate no longer finds its own source’s .tar#
With the seeds extracting (entry below), migraphx-ep.dll configured, built in
284 s and staged, and then phase 5 failed. ORT gate (amdgpu-ep) FAIL: an ONNX Runtime archive: C:\temp\ort-amdgpu-ep-work\onnxruntime-ep-amdgpu-src\onnxruntime-ep-amdgpu.tar.
That file is the intermediate tar from the first 7-Zip pass over the EP’s own
.tar.gz. Expand-SourceTarball never deletes it, and G2’s archive rule matches
any onnxruntime* archive in the tree (only GenAI and extensions are excepted).
The finding was the only one.
Expand-PinnedArchive now deletes the intermediate .tar once its pass has
unpacked it. The gate is unchanged. The pinned EP tarball, checked on the host,
now unpacks with no .tar left and no file matching G2’s archive rule.
Rocm.Migraphx.Tests.ps1: 58 pass.
2026-09-25 - The ORT AMDGPU EP’s flatbuffers seed extracts despite 7-Zip’s link refusals#
With MIGraphX built (entry below), the EP stage stopped in phase 1:
7z extraction of 'C:\temp\ort-amdgpu-ep-work\flatbuffers.zip' failed (exit 2): Sub items Errors: 9. The archive matched its pin. 7-Zip 25+ refuses every
symlink whose target climbs with .., even one that stays inside the tree, and
flatbuffers 25.12.19 carries nine: six Java test dirs, ts/package.json,
ts/pnpm-lock.yaml and a test .npmrc. Reproduced on the host with 7-Zip 26.03.
The other eight EP seeds extract cleanly, and no flatbuffers CMake file reads those
paths.
Save-PinnedSource now extracts through Expand-PinnedArchive. It runs the same
two 7-Zip passes as Expand-SourceTarball, but hands each pass to
Get-SevenZipSkippedLink. An exit 2 made only of those refusals is skipped with a
warning that names each link. Any other ERROR line, another exit code, or an exit 2
that names nothing still throws, now quoting 7-Zip’s own ERROR lines. The links are
never written, so the refusal keeps its point. The fix lives in
WindowsMigraphx.Common because WindowsSourceBuild.Common is mounted into every
media layer; fold it in at the next deliberate media rebuild. Checked on the host
against the zip, a .tar.gz, a .tar.xz and a truncated zip (which still throws).
Rocm.Migraphx.Tests.ps1: 57 pass.
2026-09-25 - MIGraphX links with MLIR off: upstream’s stubs, backported#
With HIP compiling (entry below), the build reached migraphx_gpu.dll and stopped
at the link: is_module_fusible, adjust_param_shapes, dump_mlir_to_file and
dump_mlir_to_mxr were undefined. At rocm-10.0, mlir.cpp declares them for
every build but defines them only when MIGRAPHX_MLIR is set, and the rocm lane
builds MLIR off because TheRock ships no rocMLIR. Upstream fixed this on develop
in 5a80dc91ba (#5154, 2026-08-24), after the tag.
windows/scripts/patches/migraphx/001-mlir-off-stubs.patch carries that commit’s
four definitions verbatim. Their signatures were checked against the pinned
mlir.hpp. Phase 1 git-inits the tarball and applies the patch with
Invoke-SourcePatch, and Dockerfile.rocm-migraphx mounts the directory.
upstream-windows-patches.md lists it as fixed upstream, to retire on a bump.
Test-PatchesApplyClean.ps1 now maps migraphx to MIGRAPHX_WINDOWS_COMMIT.
That is a commit, and git clone --branch takes only names, so a 40-hex ref
clones through --revision (git 2.49 or later). A -PatchRoot holding a single
patch no longer dies on .Count. The whole catalogue passes, 20 of 20.
Rocm.Migraphx.Tests.ps1 covers placement, the patch and the mount: 54 pass.
2026-09-25 - MIGraphX’s HIP code compiles against MSVC 14.51’s <cmath>#
With the configure through, every HIP source in migraphx_device failed (114
errors): __device__ function 'isgreater' cannot overload __host__ __device__ function 'isgreater', with the previous declaration at MSVC 14.51’s
cmath:688, _CLANG_BUILTIN2(isgreater). Under clang that <cmath> defines the
six two-argument comparisons as constexpr builtin wrappers. HIP makes those
__host__ __device__, so clang’s HIP headers can no longer add their __device__
versions. Upstream MIGraphX’s Windows GPU job avoids this by building with VS 2022
17.13.
The owner chose to keep the image’s own clang/MSVC 14.51 toolchain. The first
try, -Xclang -fno-cuda-host-device-constexpr, cleared all 114 errors but broke
device code instead (reference to __host__ function 'operator unsigned int',
std::integral_constant at xtr1common:34). It was dropped before any push.
Write-HipMsvcCmathOverlay now writes an -isystem directory ahead of clang’s
resource dir. The wrapper includes both headers with <>, so the overlay reaches
them. Each overlay header renames those six names, #include_nexts the untouched
original and restores them, so device code calls MSVC’s builtin versions. No
TheRock file is copied or edited. The flag is inert for non-HIP sources, which
never include these headers. Rocm.Migraphx.Tests.ps1 covers the overlay;
51 cases pass.
2026-09-25 - MIGraphX takes TheRock’s nlohmann_json through a natvis shim#
With its own rocm-cmake (entry below), MIGraphX got past
rocm_add_version_resource, then stopped at src/CMakeLists.txt:389:
Cannot find source file: C:/TheRock/build/nlohmann_json.natvis. TheRock’s
nlohmann_json 3.12.0 was installed by an MSVC-style build. Its exported target
therefore lists <prefix>/nlohmann_json.natvis, a debugger visualizer, as an
interface source, and TheRock’s dist does not carry that root-level file. It was
the only missing source.
Write-NlohmannJsonConfigShim writes a two-file package into the deps prefix:
The config includes TheRock’s own config by absolute path, then clears
INTERFACE_SOURCESonnlohmann_json::nlohmann_json.The version file includes TheRock’s.
nlohmann_json_DIR points at the shim. Headers, version and the staged licence
notice all stay TheRock’s. Rocm.Migraphx.Tests.ps1 covers the shim’s content,
its refusal when TheRock lacks the package, and the new configure argument.
2026-09-25 - MIGraphX builds with its own rocm-cmake pin#
The rocm chain got through LiteRT (1:17:33), TVM (2:55:07) and the media merge
(42:20), then stopped in rocm-migraphx at the MIGraphX configure:
src/CMakeLists.txt:184: Unknown CMake command "rocm_add_version_resource".
MIGraphX rocm-10.0 calls that function in five CMakeLists, and rocm-cmake added
it in 33541cd51f (“Add versioning for Windows”, 2026-04-17). MIGraphX pins
ROCm/rocm-cmake@6a7c5b73 in its own requirements.txt (6 commits past it). The
build took rocm-cmake from TheRock instead, and every TheRock tag points at
10155d7272, 4 commits before it.
Phase 2 of Build-MigraphxFromSource.ps1 now does three things:
It reads the commit from the pinned MIGraphX tree’s
requirements.txt(Get-MigraphxRocmCmakeCommit, which accepts only a 40-hex id).It fetches exactly that commit (
Save-GitCommitSource). git verifies every object against the id and HEAD is re-read.It installs rocm-cmake into the deps prefix, which precedes TheRock on
CMAKE_PREFIX_PATH.
MIGraphX does find_package(ROCmCMakeBuildTools REQUIRED), so it now finds this
copy. The owner chose deriving it over a new versions.env pin: versions.env is
baked into Dockerfile.base, so a pin there would re-key the whole Windows chain,
while this change re-keys only the MIGraphX stage. It also makes a MIGraphX bump
carry its own rocm-cmake. Rocm.Migraphx.Tests.ps1 covers the parse, both
refusals, the parameter guard, and where phase 2 stages it.
docs/windows-rocm.md § Supply chain records the exception.
2026-09-25 - BACKLOG.md: every known image gap, measured#
The root BACKLOG.md, in the agentic loop’s - [ ] / - [b] format, now lists
every gap in :latest, :winamd64 and the :winarm64 bundle that a consumer
hits or works around. There are CON7–CON33, grouped by image, each with its
evidence and what closes it. CON7–CON10 moved there from
docs/refactoring-backlog.md, which now points at it.
Sources. Nine consumers and this hub’s own docs were swept for image notes and
workarounds. Each Linux candidate was then measured in :latest (ec4bb68b,
amd64, uid 1001, through the entrypoint). Windows and arm64 items cite runs and
hub documents.
Found:
CON14: a stale stage. API 37 was added to source on 2026-09-18, but the image built on 2026-09-22 has no
ANDROID_EXTRA_*ENV and onlyplatforms/android-36.CON17: the image’s clang has no libFuzzer runtime (
llvm-cross.sh:201), which breaks WebDavClient’s atheris build.CON15: LLVM 21
clang-tidy/llvm-profdata/llvm-cov/ld.lldsit first on PATH next to clang 23.CON12: the published
:winamd64builds OpenCV/GenAI against an ONNX Runtime that is not the chain’s.
Closed by the measurement. Eleven gaps that consumers still describe turned out
closed. Among them: CARGO_HOME ownership, rustup, wasm32,
GSTREAMER_ROOT_ANDROID, and a second ONNX Runtime in /opt/opencv5/lib that is
the chain build, byte for byte. BACKLOG.md lists all eleven so nobody files them
again. docs/riscv64-rva23-baseline.md’s pointer for the
TVM/IREE RVV codegen item now names CON24, and docs/INDEX.md lists the file.
2026-09-25 - The fleet calls this hub at @develop, not @main#
Owner directive. Work lands on develop. main is a release branch that has
lagged it since 2026-09-16, and its versions.env still sets
CI_IMAGE_LINUX_TAG=latest-cross. The composite actions read that file at the ref
they were called at. So every consumer lane pulled :latest-cross, which no
release moves any more, and a Linux image fix such as CON7 would never have
reached one. Both tags are one GHCR version today, so nothing changes in what CI
runs until the next publish.
In the hub:
prepare-windows-container-hostnow calls its three sub-actions at@develop, and the reusable workflows call their 15 action references at@develop.lint-gates.ymlchecks the tooling out atref: develop, and the consumer registry’s hub entry now readsdevelop.Docs and templates: AGENTS.md’s tag note,
.github/actions/README.md, six docs pages andshared/templates/AGENTS.md.templatenow say@develop.adopting-in-a-new-project.md§ 6 says why.In the consumers: 110 references across eight repositories move in their own commits.
What this means for
:latest-cross: it is deletable once it is a GHCR version of its own.python-ci.mdnotes that OrchestrANT and WebDavClient no longer wait formainbefore splitting theiramd64-arm64workflow.
2026-09-24 - Backlog: four image gaps the consumer lanes hit (CON7–CON10)#
docs/refactoring-backlog.md gains two arm64 GCC gaps and two Windows LLVM gaps. Each
cites the run that showed it and says what closes it. CON7 (no libsanitizer in the
arm64 GCC) is fixed in source by e2de5852 and waits only for a published image. CON8
(arm64 GCC configures cannot find libX11) comes with a hypothesis and the check that
decides it. CON9 (clang_rt.profile) and CON10 (clang-tidy) are the two components
the patched Windows LLVM does not build. No code changed.
2026-09-24 - ONNX Runtime GenAI is configured without its own tests (ENABLE_TESTS=OFF)#
With FFmpeg fixed, the rocm chain got through OpenCV (25:36) and Hailo (8:09), then failed in
media-core-built at ONNX Runtime GenAI v0.15.2: unit_tests.exe would not link
(undefined symbol: Generators::Log, Generators::g_log, SetLogBool, GetEnv). GenAI
gates test\ on its own ENABLE_TESTS option (default ON) and ignores the BUILD_TESTING=OFF
this script already passed. Its unit_tests links the shared onnxruntime-genai and uses
internals that DLL does not export. Only the CUDA variant, which every earlier chain run built,
compiles those sources into the test itself, so the rocm lane’s CPU build was the first to hit
it. Nothing in the chain runs GenAI’s tests, so Build-OnnxGenaiFromSource.ps1 now passes
-DENABLE_TESTS=OFF on every lane. That also drops the CUDA kernel tests from the CUDA
lanes’ build time. SourceBuild.GenaiOrt.Tests.ps1 pins the flag, and its case fails with
the flag removed.
2026-09-24 - lib/compiler-llvm-tools.sh: the LLVM tools of the compiler that built a tree#
BeschleunigerBallett’s coverage lane failed with error: no profile can be merged: its
build-coverage-llvm.sh calls this hub’s coverage.sh, which runs llvm-profdata and
llvm-cov by bare name, and PATH’s copies are an older LLVM than the image’s clang.
AccelerANTgine had hit and fixed the same thing locally (compiler_llvm_tool and
use_compiler_llvm_tools in its scripts/linux/ci-common.sh). With a second consumer
needing them, both functions move up unchanged into linux/scripts/lib/compiler-llvm-tools.sh.
test-compiler-llvm-tools.sh covers them with two fake LLVM installs (the tree’s compiler,
and a different clang++ first on PATH): 6 assertions. Three new mutations bite, family
compiler-llvm-tools. docs/shared-script-libraries.md lists the library and describes it.
The consumers switch to it with their next hub pin.
2026-09-24 - makedef hands llvm-nm its object list as @file: xargs died in the rocm lane’s environment#
The refusal below paid off on the first rerun. The rocm FFmpeg stage stopped at
makedef: 'C:/llvm-patched/bin/llvm-nm.exe' listed no global symbol in 116 object(s), and
the captured stderr was not llvm-nm’s at all:
assertion "bc_ctl.arg_max >= LINE_MAX" failed: file "xargs.c", line 512. Git for Windows’
xargs subtracts the whole environment block from the ~32 000-character command-line limit.
The rocm lane’s environment is large enough (hundreds of version variables, among them
the long TORCH_ROCM_* wheel URLs) that nothing was left, so xargs aborted before it ran
llvm-nm. The amd64 lane’s smaller environment stayed under the line, which is why the same
FFmpeg linked there four times. The previous 2>/dev/null had turned the abort into an
empty export list.
makedef no longer uses xargs. It writes the object list, one per line, to
<version script>.nm-objects and runs llvm-nm --defined-only -g @<that file>, so no
command line carries the list. LLVM tools expand response files, and the real llvm-nm 23.1.0
read it and exported exactly the av* symbols from clang’s COFF objects. Reproduced on this
host with Git for Windows’ shell: past ~33 KB of environment, the xargs version fails
(xargs: environment is too large for exec) at 33, 41 and 50 KB, and the @file version
passes at all three. test-ffmpeg-makedef.sh asserts one llvm-nm run with one @file
argument and no object on its command line (19/19). The new
ffmpeg-makedef.objects-by-response-file mutation, which puts xargs back, bites.
2026-09-24 - FFmpeg’s makedef refuses an empty export list and runs the compiler’s own llvm-nm#
The rocm chain (hub a943dd94) built base, the ROCm SDK layer, the patched toolchain and ONNX
Runtime, then failed FFmpeg n9.0.2’s make + install: swresample-7.dll and swscale-10.dll
could not resolve a single libavutil symbol (av_mallocz, av_log, av_channel_layout_*).
avutil-61.dll had linked, but with an empty export list. The replacement
windows/scripts/patches/ffmpeg/makedef writes each DLL’s .def from an llvm-nm dump.
It sent llvm-nm’s stderr to /dev/null and wrote EXPORTS with nothing under it
whenever the dump came back empty. Its own header records the same symptom from the
replacement before it. The same FFmpeg linked four times on the plain amd64 lane
(09-21, 09-22). The rocm lane adds AMF and Vulkan, and the FFmpeg script puts scoop’s shims
first on the PATH its Git-bash make sees, so a bare llvm-nm is not necessarily the
compiler’s.
makedefkeepsllvm-nm’s stderr in<version script>.nm-errors. It exits 1, naming the tool, the object count and that stderr, when the dump is empty or when no symbol matches the version script’s globs. Nothing reaches the.defon either refusal.It runs
$LLVM_NM, with barellvm-nmonly as the fallback.Build-FfmpegFromSource.ps1sets it on the clang-cl toolchain to thellvm-nm.exebeside theclang-clthat this PATH resolves (Get-FfmpegLlvmNm), and throws if there is none.
linux/scripts/tests/test-ffmpeg-makedef.sh drives makedef with a stub llvm-nm whose
“objects” are nm-format text, so it runs on any host. It passes 15/15 here, and 11/15 fail
against the old script, which ignored LLVM_NM and on a host with no llvm-nm wrote an empty
EXPORTS list: the rocm signature, reproduced. The four new mutations bite
(ffmpeg-makedef.*, a family declared in gate-proofs.allow). SourceBuild.FfmpegRocm.Tests.ps1
covers Get-FfmpegLlvmNm; its throw case fails when the throw is removed. Against real COFF
objects from clang 23.1.0 and its llvm-nm, in the Linux image, makedef exported exactly the
av* symbols and refused an nm that could not read them. What emptied the rocm dump is not
pinned yet: the next rocm run either links with the compiler’s own llvm-nm, or stops at
makedef with that tool’s own error.
2026-09-24 - The WebDAV client installs from its commit archive, not through git#
BeschleunigerBallett’s Windows lane (run 36020442781) died before its build, inside the
early WebDAV download. uv pip install git+https://github.com/Kataglyphis/WebDavClient@<pin>
makes uv run git submodule update --recursive --init. The pinned commit still carries
ExternalLib/Kataglyphis-ContainerHub, whose nested DocumANTation and LaTeX submodules
overflow Git for Windows’ gitdir limit inside uv’s cache: fatal: '$GIT_DIR' too big. A
shorter cache root cannot help, because the limit is on the gitdir chain.
Both halves now install
kataglyphis_webdavclient @ https://github.com/Kataglyphis/WebDavClient/archive/<pin>.tar.gz:
the same commit’s tree, with no git and no submodules. The package needs nothing from
them. The Windows half builds that string in one function, Get-WebDavClientRequirement,
which refuses anything but a full 40-character commit SHA. Its Pester cases pin the form
and the refusal (3/3). webdav-download.sh uses the same form. Checked on this host: an
archive install of the pinned commit into a fresh venv imports WebDavClient, which has
download_all_files_iterative.
versions.env’s comment above WEBDAVCLIENT_REF still says “installed from git”. It
moves with the next change to that file, because any byte changed there rebuilds the
Windows chain from Dockerfile.base’s COPY versions.env onward.
2026-09-24 - The pre-commit hook checks the derived doc numbers when their inputs move#
7482747c added two consumer-inventory mutations and left docs/code-quality-tooling.md
quoting 1332 entries. The hook said OK. CI’s preflight then failed test-doc-numbers.sh,
and every doc-numbers.* mutation in all four shards reported a baseline that already
failed (run 36022089345). 2bba2833 fixed the digit. The hook now runs
test-doc-numbers.sh whenever docs/scripts/mutations.json, the hook itself,
docs/code-quality-tooling.md, docs/cross-build-verification.md or AGENTS.md is
staged, and it refuses the commit with the --update command to run. It does not run
when none of them is staged. The new block sits after the doc-duplication gate, so the
two hook spans the docs quote do not move. test-precommit-hook.sh gains the abort case
and the skip case (42 assertions), and three new mutations bite:
pre-commit.doc-numbers-aborts, pre-commit.doc-numbers-trigger-names-the-manifest and
pre-commit.doc-numbers-only-when-an-input-moves. The manifest now holds 1337 entries.
2026-09-24 - Invoke-WithAsanOptions -Options '' adds nothing instead of refusing to bind#
Invoke-WithRuntimePath takes an optional [string]$AsanOptions and hands it to
Invoke-WithAsanOptions, whose -Options was a mandatory [string]. PowerShell refuses
an empty string there, so Invoke-WithRuntimePath -AsanOptions '' could never run.
AccelerANTgine’s Start-Windows.ps1 passes exactly that for its full-application runs, to
opt out of the test-binary defaults. Its Windows lane got past its container build for the
first time in weeks (run 36020001871) and then stopped at the CLI version check: Cannot bind argument to parameter 'Options' because it is an empty string. -Options now takes
[AllowEmptyString()], and an empty value runs the block with ASAN_OPTIONS exactly as the
caller had it: nothing prepended, not even a separator. Testing.Asan.Tests.ps1 has both
shapes; both cases fail against the old module with that same message.
2026-09-24 - The consumer inventory counts a consumer’s own modules as its own#
verify_consumer_inventory.py failed its first run after the fleet renames (36019965353):
OxidANT names a hub path that does not exist: scripts/windows/New-ReleaseArchive.ps1:18 -> WindowsOrtPayload.Common.psm1. That module is OxidANT’s own, in scripts/windows/modules/,
where Resolve-BuildModule falls back to it. It dates from 2026-09-23 and is the first local
module whose name starts with Windows. The gate assumed every Windows* name was the hub’s.
dangling_modules now skips a name the consumer tracks as a .psm1 outside its test trees
(a fixture’s .psm1 exists to be absent and still counts as dangling).
test-consumer-inventory.sh covers both. Two new mutations,
consumer-inventory.own-module-is-not-dangling and consumer-inventory.fixture-psm1-is-not-own,
bite. Against the live fleet: CONSUMER INVENTORY OK, 0 dangling.
2026-09-24 - Workflow names follow the fleet convention; arches for the Python Linux lane#
The owner set one naming rule for the workflows of every repository in the family:
kebab-case files, one file per platform and arch for a build, display names
<Platform> <Arch> · <what> (or <Area> · <what>). The hub goes first. The rule and
the fleet’s rename table:
adopting-in-a-new-project.md § Workflow file names and display names.
The consumer renames cleared most of the workflow-convention backlog, so
workflow-conventions.allow drops twelve CENSUS rows that now measure zero
(OmniAccelerANT, OxidANT and AccelerANTgine all three checks, jotrockenmitlocken
job-timeout and permissions, ANThology job-timeout) and lowers BeschleunigerBallett’s
job-timeout 15 → 13 and permissions 12 → 11, each measured with
verify_workflow_conventions.py on the renamed tree. OrchestrANT keeps its 2 until its
Linux lane splits on arches. test-workflow-lint.sh’s ramp cases graded a fixture
named OxidANT against the REAL allow file, so they passed only while the real OxidANT
still had a backlog; they now carry their own census. third_party/DocumANTation moves
to 2e861fc, its own rename (docs.yml, linux-x64.yml).
Renamed.
ubuntu26.04.yml→linux-x64.yml(“Linux x64 · preflight + mutation gate”) andwindows-scripts.yml→windows-x64.yml(“Windows x64 · script tests”). Their concurrency groups follow the file (linux-x64-…,windows-x64-…), andwindows-x64.yml’s path filter names itself. The README badge moved; a Windows x64 badge is new.Display names only.
actions-selftest.yml“Composite actions · self-test”,consumer-inventory.yml“Consumers · inventory”,ghcr-cleanup.yml“GHCR · cleanup”,llm-stack-serving.yml“LLM stack · serving”,sbom.yml“SBOM”,stale-docs-check.yml“Docs · stale check” (its issue footer too; the issue is still found by its title).The reusable workflows keep their FILE names, because every consumer calls them at
@main:build-docs.yml“Docs · build (reusable)”,lint-gates.yml“Lint gates (reusable)”,python-ci-linux.yml“Python CI · Linux (reusable)”,python-ci-windows.yml“Python CI · Windows (reusable)”,submodule-pins.yml“Submodule pins (reusable)”. AGENTS.md now states that as a rule.python-ci-linux.ymltakesarches(string, default"x64 arm64"), so OrchestrANT and WebDavClient can split intolinux-x64.ymlandlinux-arm64.yml. The default runs both rows with the same job and artifact names as before. A newplanjob builds the matrix and fails on an unknown, repeated or empty name; a static matrix cannot be filtered, because GitHub adds an excludedinclude:row back. Callers see one more check,<job> / plan.python-ci.md§ One arch per caller.Tests. New
tests/test-reusable-linux-lane.sh(33 assertions) runs the plan step itself: the default rows equal the old static matrix, one name gives one row, the refusals fire, and no row uses a*-latestlabel. That last one is needed because the labels now sit in arun:block, which the workflow-convention gate cannot read. Seven new mutations in a new family,linux-lane(declared ingate-proofs.allow), each seen biting; the manifest goes 1325 -> 1332. The threemutations.ci-*entries andtest-mutation-gate.shpoint atlinux-x64.yml.code-dupes.allowgains two rows for the new suite’s preamble, the same row the Windows lane’s suite has.Fleet references. Hub prose that names a consumer workflow uses the new name, with the old one beside it where the text is dated: the
consumers.jsonnotes, the CENSUS reasons inworkflow-conventions.allow(counts unchanged),shellcheck-warnings.allow,code-quality-tooling.md,shared-script-libraries.md,dependency-updates.md,cross-build-verification.md,New-Archive.ps1’s header, andgithub-cli-pipeline-monitoring.md(nowgh run list --workflow linux-x64.yml).ftp-deploys.md’s 2026-09-09 table keeps its old names and line numbers as a dated record and points at the rename table. Left alone on purpose:02-toolchain/rust/cargo_fmt_clippy.shquotesrust_ubuntu26_04.yml:134-139as a dated citation, and that file sits inDockerfile.toolchain’sCOPY linux/scripts/02-toolchain/, so a comment edit would re-key the toolchain stage.For the consumers. A split that adds caller files without a top-level
permissions:raises that repo’spermissionscount above its CENSUS row, and its lint lane fails; give each new file its own block.archesreaches a caller only once this is on hubmain.What re-keys: nothing. No
versions.env,01-core, Dockerfile or other image-closure file changed.
2026-09-24 - Hailo: review fixes#
A review of the entry below found a check that could be skipped, an old-path guarantee
that no test held, and docs claims that went further than the evidence.
HAILO_NESTED_CACHE behaves as it did. Under HAILO_PYHAILORT_IPO=off the install is
stricter; upstream warns as it always did.
hailo-support.md § pyhailort.
Under
off, a failed install into/opt/venv, or no wheel at all, stops the build. The installed-module check ran only insideif uv pip install ...; then, so a failed install only warned, theRUNstayed green, and an image could ship without pyhailort in/opt/venvwhile the docs called the check fatal.install_pyhailortnow prints uv’s own error and dies, namingHAILO_PYHAILORT_IPO=upstream, which keeps the old warning.import hailo_platformstill only warns in both modes, and the gates table now says so. A run without/opt/venv, outside the image, still only stages the wheel.Found on the way: the
ls | headthat finds the wheel was unguarded underpipefail, so “no wheel” never reached itsreturn 0: the build died with rc 2 and no message (shell-safety class 2). Guarded; no wheel is now an error underoffand a warning underupstream.The old nested build’s
HOMEhas a test. The fake cmake recorded theHOMEit ran with, and nothing read it.off, andcarrywithout a launcher, now assert the caller’sHOME, andcarrythe carrier. The carrier test’s login file setsPATHandSCCACHE_DIRitself, which pins that the carrier’s exports come after it.Docs. README and
failure-modes.mdtied a working pyhailort to a build date, but no image has this change yet, the arm64 import is untested, and an image built fromdevelopthat day still carries the stub. Both now name the switch and say the import only warns and is proven on amd64 only;hailo-support.mdtoo. The HailoRUN’s cache mounts are the chain’s own on whichever archCROSS_BUILD_PLATFORMnames, so on the Jetson the old 10G cap could trimsccache-arm64too, not onlysccache-amd64on the cross host:build-cache-tiers.mdand the entry below are corrected.hailo-support.md§ pyhailort now says the shipped module depends on floatingscikit-build-core>=0.10andpybind11>=2.13.6,<3resolves from PyPI, which reach the image now that the module is real. Pinning them waits for the next plannedversions.envre-key (an open question on that page). The entry below also gave# noforwardas a reason for scikit-build-core, which was wrong: the HailoRUNloads the image’sversions.envand can readPY_SCIKIT_BUILD_CORE_VERSIONtoday. Only pybind11 needs a new key.code-dupes.allow: two budgets go back up by one and two rows come back, all four as they were before 2026-09-20.install_pyhailortno longer holds the shingle[ -n "S" ] || return N, so the shingle falls from 7 owners to 6, under the idiom cutoff, and counts for those pairs again. No file in those pairs changed.Tests.
test-hailo-build.sh110 -> 139 assertions: theHOMEeach mode configures with, the login-file order, andinstall_pyhailortitself, run underset -eagainst a fake uv and a sandbox venv in both modes (installed, stub, failed install, failed import, no wheel, no venv). Six new mutations, each seen biting:hailo.off-keeps-home,hailo.no-launcher-no-carrier,hailo.carrier-login-first,hailo.install-failure-fatal,hailo.install-no-wheel-fatalandhailo.install-no-wheel-guard. All 36hailo.*bite; the manifest goes 1252 -> 1258.What re-keys: the wrapper’s Hailo
RUN(build-hailort.sh), which the entry below already re-keys and no host has built yet. Noversions.env,01-coreor Dockerfile change.Verified here: on the Windows host, the suites that read the touched files (the two that fail,
test-version-snapshotandtest-mutation-gate, fail on the same assertions at the parent commit), all 36hailo.*mutations, shellcheck and its warning ratchet, the hook’s fast gates, the doc gates and the manifest’s--stale-check. In the local amd64 image:test-hailo-build.shas uid 1001 (139 passed) with the six new mutations biting there too, andinstall_pyhailortalone as root against the image’s uv and/opt/venv, with a stub wheel, a truncated wheel and no wheel under both values, each with the exit code above. Not verified: a fullbuild-hailort.shrun with these changes, and everything the entry below lists.
2026-09-24 - Hailo: the nested protobuf build is cached, pyhailort is a real module, the old build one switch away#
arm64’s Hailo RUN took 595 s in the 2026-09-22 lane, and 384 s of that was HailoRT’s
configure: it builds a host protobuf (200 objects) under env -i, so the compiler cache
never reached it, while every other compile hit. The same diagnosis found that every image
shipped pyhailort as an empty module. Speedup item hailo-arm64, with its challenge’s
corrections. Owner decision: both options, each default the fast or fixed path and the
old build one switch away.
hailo-support.md § two switches.
HAILO_NESTED_CACHE=carry|off, defaultcarry. Only HailoRT’s configure runs withHOMEat a new directory in theRUN’s tmpfs. It mirrors the realHOME, and its.bash_profileruns the real login file, then exports every exportedSCCACHE_*andCCACHE_*variable (notversions.env’s pins) and both CMake launchers, with the parent’s ownsccacheandccachefirst onPATH.offis the old uncached nested build. Measured natively on amd64: the configure took 9 s warm undercarryand 32 s underoff, and 243 requests reached the cache for the 200 nested objects.Found on the way: the clean
PATHresolves the distro sccache 0.13 in/binbefore the pinned 0.17. Its stats requests fail against the 0.17 server, and with no server running it starts a 0.13 one on the same socket. The carrier puts the parent’s binaries first, so client and server are one binary.The gate (
hailo_assert_nested_cache_reached). Undercarrywith a launcher it fails the build when the cache saw fewer requests than half the nested objects, or when the nested build left no.ninja_log; a spawner without the exactenv -iline fails before the configure. Everything else warns.USE_SCCACHE=0keeps it armed on ccache;USE_CCACHE=0andoffonly warn. One[CACHE] hailo/<phase>: secs= requests= hits= misses= launcher= cap=line per phase: configure, build, pyhailort, libzmq, TAPPAS.HAILO_PYHAILORT_IPO=off|upstream, defaultoff. Upstream forces IPO, and lld cannot link GCC’s slim LTO objects, so every image carried a 4 KB_pyhailortwithoutPyInit__pyhailort.offpatches the forced IPO out of the cached source; the twelve sources compile atcompute_cpp_heavy_jobsand cache;hailo_check_pyextchecks the wheel’s module and the installed one (ELF machine, a definedPyInit__pyhailort), fatal underoff(a failed install too, since the review entry above), a warning underupstream, which rebuilds the old stub. Measured on amd64: a 1.7 MB module, andimport hailo_platformworks on Python 3.14.Cache caps. The Hailo
RUNpassesDockerfile.base’sSCCACHE_CACHE_SIZE=30GandCCACHE_MAXSIZE=30G. The runtime image lost base’s ENV, socompiler-cache.sh’s 10G applied there and trimmed the shared mount, which is the chain’s own cache on the archCROSS_BUILD_PLATFORMnames:sccache-amd64on the cross host,sccache-arm64on the Jetson (corrected in the review entry above). The suite pins the pair to base.Where the code is. New
03-media/build/hailo/hailo-build-lib.sh(the switches, the carrier, the counters, the gate, the IPO patch, the module check) andprobe-hailo-nested-cache.sh, a seconds-long check inside an image:off, thencarrycold and warm.build-hailort.shroutes its configure through the carrier and refuses a typo.lib-orchestrator.shforwards both switches only when set and sources the lib;build-cross-chain.shand both runtime orchestrators refuse a typo (exit 2) before they build anything.Challenge corrections applied. Both switches exist (the design had none for the cache fix, against the owner’s rule); only exported variables are carried, so the unexported 10G default never becomes a nested server’s cap; the
HOMEredirect keeps the realHOME’s reads through the mirror; the gate follows the code’sUSE_*semantics; the cap scope is the build platform’s arch, not only amd64 (the review entry above); the pybind build is capped; the twelve TUs are twelve.Not done, and why. The design’s Unit 2: its stats fix is already in
compiler-cache.sh, and moving the carrier into01-corewould re-key the chain from the compiler stage. Unit 3, the opt-in cross fast path: the recorded-ELcross failure is not diagnosed, and the wrapper’s final stage would need a restructure no BuildKit here could test. Pinning scikit-build-core and pybind11:PY_PYBIND11_VERSIONis 3.x while pyhailort needs <3, so pybind11 needs a newversions.envkey, which re-keys every stage from base; both are left for that re-key (the review entry above says why it now matters).What re-keys: per lane, the wrapper’s Hailo
RUN(Dockerfile.torchand thehailo/directory it COPYs) and the smallRUNafter it, which re-run on every lane anyway. Noversions.env,01-coreor other Dockerfile is touched, and the orchestrators andlib-orchestrator.share outside every image closure. The first run compiles the nested protobuf and the pybind11 sources cold, once.Tests.
tests/test-hailo-build.sh(new, 110 assertions): the switches, the carrier through a realenv -i ... bash -l, every variablesetup_ccacheexports, the configure wrapper against a fake cmake that spawns its nested build the same way, the gate’s thresholds, the counters’ anchoring, the IPO patch both ways, the module check on the shipped stub’s symbol table, cap parity and the wiring.t_stage_build_argsintest-harness.shis now the one owner of the forwarding probetest-web-lane-tools.shhad, becausecode-dupescaught the second copy. 30hailo.*mutations, all seen biting;mutation-family:hailoregistered;file-size.allow+1 line forbuild-cross-chain.sh.Verified here. In the local amd64 image, as root with this tree’s scripts at the image paths:
build-hailort.shend to end undercarrycold and warm,off,upstreamandUSE_SCCACHE=0(every run exit 0, numbers in the doc), the probe as root and as uid 1001, and every suite as uid 1001 (the failures there are the baseline tree’s too). On the Windows host: the touched suites, all 30 new mutations and the 25 existing ones on touched files (11 of those in the image, where their suites run), shellcheck and its warning ratchet on every touched shell file, and the preflight gates. Not verified: arm64 under QEMU, where the saving is (estimated at 2.5 to 4 minutes per arm64 lane), the BuildKitRUNitself, the Jetson and the X100. The commands for the build host are in the doc.
2026-09-24 - The wrapper’s wheelhouse: review fixes#
A review of the entry below found that RUNTIME_WHEELS_SOURCE=export cannot run on
the cross host’s --no-push chains, which that entry and the docs did not say, plus
three smaller gaps; rerunning its suites in the CI image found a fourth. No behaviour
changed: image and export do what they did.
linux-cross-builds.md § The wrapper’s wheelhouse.
exportis refused on a--no-pushchain whose android tag is published, which is every default or variant--no-pushchain on the amd64 cross host. Such a chain threads no android pin, soruntime_android_pinresolves the published tag’s registry digest, and this run’s android layout never has it. The arch stops at the runtime lane, before its base build, with both digests named; the remedy isRUNTIME_WHEELS_SOURCE=image.failure-modes.mdhad suggested re-exporting the layout from the tag the lane names, which would have switched the package build to the published android as well; it now says never. Makingexportread this run’s tag there would give up “the same image asimagemode”, so that is left to the owner. The same resolution meansimagemode on such a chain builds the venv from the PUBLISHED android’s wheels while the package copies from this run’s layout. That predates the knob and is unchanged; it is now documented and a test pins it.The A/B runs through the chain. The runtime lane’s disk watch, whose mid-lane prune is the suspected eviction, runs only in
build-cross-chain.sh, so the standalonebuild-runtime-manifest.shA/B could not reproduce the wait. The primary A/B is nowCROSS_LOCAL_CONTEXT_HANDOFF=0 RUNTIME_WHEELS_SOURCE=<m> build-cross-chain.sh --only runtime --no-push, and the standalone run is a mechanism check only. An arch counts only when its image-mode run shows thewheels-sourceextraction. The runtime lane writes no stage log, so the recipe greps the tee’d transcript, not aruntime.logthat never existed.Tests.
test-runtime-wheels-source.sh(91 -> 101 assertions) checks every[runtime-timing]step in both modes, and the published-tag--no-pushcase:exportrefuses whether or not the registry’s copy was pulled, andimagemounts the registry’s digest. The shared nerdctl stub answersimage inspectper ref. Two new mutations, both seen biting:wheels-source.timed-steps(unwrapping the wrapper step’s timer used to survive) andwheels-source.nopush-published-ref.Both wheels suites are hermetic. In the CI image they failed 7 of 91 and 5 of 21 assertions, at 736dea8a too: the image exports
ONNX_PACKAGE=onnxruntimeandPYTORCH_EXTRA=pytorch-cpu, which the wrapper forwards as operator pins.rw_hermetic_env(inruntime-wheels-fixtures.sh) now clears what the wrapper path and the fixtures read before any case sets its own; an exportedENABLE_NVIDIA=true,RUNTIME_NO_CACHE=1orNERDCTL_BINno longer decides a case either. The dead-function census counts the new helper (508 -> 509 incode-quality-tooling.md).Two stale hook-span references on this branch, both fixed on
developalready and repeated here verbatim so this commit could pass the hook:cross-build-verification.mdquoted the staged-shell block as:101-118(it is:102-119since 57bec177), and the twopre-commit.doc-span-*mutations searched for the old spans (as 1b941556 did). The Windows-host floor fix forverify_doc_links.pycame over fromdevelop’s 5a1648d8 as its own commit, de8aed49.What re-keys, correcting the entry below:
Dockerfile.packageCOPYs01-corewhole (:372), so the package image re-keys from that COPY on, its setup RUNs andwrapper-smokeincluded. That is free on a full chain, where android changes anyway, and one package rebuild per arch on a runtime-only rerun with an unchanged android. This entry’s own code edit is one comment in01-core/runtime-build-fns.sh(it said the pin is always empty under--no-push): the same re-key set as the entry below, and nothing more while no host has built that entry.Verified here: on the Windows host, both wheels suites (also under a hostile exported env), all 23
wheels-source.*mutations, the whole manifest’s--stale-check,build-cross-chain.sh --dry-runof the new A/B command in both modes (it hands the lane--skip-manifestand no--push), and the helper’s dry run showingexportnaming the registry digest; in the local:latest-crossimage, the touched suites green. Not verified: no BuildKit ran, so neither the refusal against a realnerdctl savelayout nor the chain-driven A/B has run.
2026-09-24 - The wrapper’s wheelhouse: RUNTIME_WHEELS_SOURCE=image|export, image the default#
In the 2026-09-22 lane the torch RUN waited 161 / 565 / 661 s (amd64 / arm64 /
riscv64) before uv venv wrote its first file; on 2026-09-21 it waited 96 / 1 / 2 s
with the same scripts. The likely cause: to bind-mount /opt/wheels (about 0.1 GB)
BuildKit must hold and checksum the whole android snapshot (about 12.7 GB), and pulls
it again when something evicted it after the package build. Inferred, not proven.
Owner decision: both options, the current one the default until a host lane
confirms the other. Speedup item uv-wait, with its challenge’s corrections. How to
pick: linux-cross-builds.md § The wrapper’s wheelhouse.
RUNTIME_WHEELS_SOURCE=auto|image|export, defaultauto=image.imageis the old delivery, byte for byte: the wrapper’s argument vector is identical to the pre-change code over 32 combinations of pin, host infix, variant,ARTIFACT_CONTEXT_ROOTand GPU.exportbuilds onlyDockerfile.torch’s newwheels-exportstage (--output type=local) before each arch’s base and package builds. It copies/opt/wheelsout of the image image mode would mount, seals it with a sha256 manifest, and the wrapper mounts that directory (WHEELS_IMAGE=runtime_wheels). Any other value exits 2, at the chain’s start too.Challenge corrections applied. The export reads the ref image mode reads (
runtime_wheels_image_ref, moved out ofappend_wrapper_build_args), not the package’sARTIFACT_IMAGE, so an empty pin, a custom prefix or--artifact-build-mode nativecannot switch the source. UnderARTIFACT_CONTEXT_ROOTit reads the android OCI layout only when its digest equals the containerd image’s, otherwise it stops (always, on a--no-pushchain whose android tag is published: the review entry above). The staging root is minted in the main shell, so--push-allworks. The wrapper re-checks the whole manifest; there is no fallback between modes.imageneeds noARTIFACT_CONTEXT_ROOTrefusal: it is the old code for both lane modes, so the design’s separatecontainerdvalue is gone.Diagnostics in both modes:
[runtime-timing]per step,[wheels]for the export,[torch-run] startas the torch RUN’s first statement,[torch-venv] wheelhouse(a content digest equal to the host’s) and[torch-venv:timing]after everysetup-torch-venv.shstep. The measurement recipe and the A/B.Where the code is.
linux/scripts/lib-runtime-wheels.sh(new, outside every image closure, loaded bylib-orchestrator.sh) holds the modes, the export, the seal and the arch loop both runtime orchestrators now run.01-core/runtime-build-fns.shgains the resolver, theimagearm, a two-way branch inappend_wrapper_build_argsand_runtime_timed: the wrapper’s arguments are built there, so this part could not live elsewhere.What re-keys.
01-coreis mounted whole byDockerfile.toolchain’s verify layer and COPY’d whole after it, so the next full chain rebuilds the compiler image from that layer on (its GCC and LLVM layers still hit), then sdk, media and android for every arch on warm compiler caches; the variant chains’Dockerfile.nvidiaandDockerfile.amdRUNs likewise. That rebuild is ALREADY due: the publishedcross-compiler-amd64dates from 2026-09-22 12:15Z, and 57bec177 and d8c31072 (2026-09-23) changed01-coreandversions.env, which re-keys from base. So this adds nothing to the next chain, if it lands before that chain. Per lane:Dockerfile.torch(the torch RUN, hailo and final) andsetup-torch-venv.sh, which re-run every lane anyway.Dockerfile.packageitself is untouched, but it COPYs01-corewhole, so the package re-keys from that COPY on (the review entry above).Tests.
tests/test-runtime-wheels-source.sh(new, 91 assertions, hermetic) sharestests/runtime-wheels-fixtures.shwithtest-runtime-wheels-context.sh, which now extracts the moved helpers;test-env-contract.shcounts the lib as the knob’s consumer. 21wheels-source.*mutations, each seen biting;mutation-family:wheels-sourceregistered. Twocode-dupes.allowrows (the in-RUN digest twin, and a self-pair that shrank because of the new files) and onefile-size.allowrow (+2 lines inbuild-cross-chain.sh).Verified here (Windows host): the suites above, dry runs of
build-runtime-artifacts.shin both modes and with a typo, and the chain’s typo refusal. Not verified: no BuildKit ran. Thewheels-exportbuild, the named directory context on the host’s rootless OCI worker (0.31.2), the layout digest check against realnerdctl saveoutput, and whetherexportremoves the wait at all are for the host A/B in the recipe above.
2026-09-24 - A mutation-gate timeout on a Windows host is a verdict, not a crash#
verify_mutations.py:_run_testkilled a timed-out test’s tree withos.killpg, which a Windows python does not have, so the first slow test there ended the whole gate in anAttributeErrorwith no verdicts (seen on the pre-push gate of 2026-09-24, ontest-web-lane-tools.shunder parallel load). Without process groups it now runstaskkill /Ton the shell’s tree. On Linux nothing changes.test-mutation-gate.sh: the timeout case asserts the verdict and the absence of a traceback on every host; only its grandchild check stays POSIX-only. Measured on Git Bash: fails before the fix, passes after (98/98 withMSYS=winsymlinks:nativestrict, which the suite’s symlink cases need there). No mutation entry: CI’s Linux python hasos.killpg, so a mutant of the Windows branch could not bite there.
2026-09-24 - The doc-links git-free floor works under a Windows python#
verify_doc_links.py:_static_ignorescomparedstr(path)with the/-spelledUNTRACKED_OUTPUTrows, and a Windows python spells a relative path with\, so without git the floor excluded nothing there. It matches the POSIX form and still returns the caller’sstr(); on Linux nothing changes.test-doc-links.shsaid so on a Windows host (the floor skips 0 of 3 output paths), which failed the commit hook whenever it sampled a doc-links entry there. That suite also needs a one-wordPREFLIGHT_PYTHON(it quotes the value) andPYTHONUTF8=1on such a host.
2026-09-24 - ORT census: an ORT under another name is found by the entry point it defines#
Closes a G6 hole a review found. Take an ORT with its fingerprints stripped,
rename it (libhelper.so), and give it an $ORIGIN RUNPATH beside the chain ORT.
The census called it an importer, modelled its dlopen onto the chain copy, and
passed it. OmniAccelerANT’s packer gives exactly that RUNPATH to every ELF holding
OrtGetApiBase. The whole-path fingerprint (the earlier 2026-09-24 census entry)
had also widened the hole a little: an ORT whose source paths lost their NUL passed too, where it
was STALE before. Details:
onnxruntime-single-source.md § An ORT under another name.
Rule: a file that DEFINES
OrtGetApiBaseis an ORT instance under any name, graded by its bytes; with no fingerprint it isUNPROVEN(“an ORT under another name”). A consumer only imports it ordlsyms it.Linux:
elf_definesinort_census_probe.pylooks the symbol up throughDT_GNU_HASH, orDT_HASHwhen there is no GNU table, asld.sodoes, and ignores an undefined (imported) entry. Such a BIN line ends indef, whichcheck-ort-provenance.shuses for the message.Windows:
Get-PeExportNamesis new inWindowsTargetArch.Common, besideGet-PeImportNames. Both now read the headers through one privateRead-PeLayout, and the import results are unchanged on 87 real PEs. A forwarded export is left out, so a DLL that forwardsOrtGetApiBasetoonnxruntime.dllstays an importer.WindowsOrtProvenance.CommonsetsDefineson such a fact and makes it an instance.Measured in a local
:latest-cross(e8eb8a42), with the image’s chain ORT renamed and stripped two ways, beside the chain copy: the census before this change passed both, and this change givesUNPROVENfor both. Whole-image census: no verdict moved outside those scratch files. The 2026-09-17 OmniAccelerANT release bundle still passes. OmniAccelerANT’s bundle-gate suites pass: 13/13 as committed, and 17/17 for its liboxidant cases. On the Windows host, the three consumers’ ORT suites pass against this hub.Tests:
test-ort-census.sh122 → 132 assertions (a GNU- and a SysV-hashed definition isUNPROVENand red, an import through either stays a green importer);Smoke.OrtCensus.Tests.ps1: the loader table gains four rows (names the chain directory, the same with no ORT, exportsOrtGetApiBase, forwards it), and the separate oxidant case folds into them (26 → 25 tests);TargetArch.PeInspection.Tests.ps15 → 6 (kernel32’s own exports versus its forwarders). 6 new mutation entries (ort-census.probe-defines*,-gnu-hash,-sysv-hash,ort-census.unproven-def), each verified to bite. 5 Windows mutations were run by hand on scratch copies, and each bit.
Code-dupes budgets moved with it: the Smoke suite’s self-pair 17 → 26 (the export table pokes fields the way the PE header does), GenAI/G2 48 → 46, and the FFmpeg/rocm-Torch row is gone (below the threshold now). The two unchanged pairs moved because shingle-owner counts shift.
Not covered: an ORT that is renamed, stripped AND rebuilt without the
OrtGetApiBaseexport; archive members (name and fingerprint only).
2026-09-24 - riscv64 web-lane tools: legacy is the old build verbatim; review fixes#
A review of the entry below found that WEB_LANE_TOOLS_SOURCE=native is not the
build riscv64 had before, as that entry and the docs claimed, and that no knob
combination reached it. How to pick a path:
consumer-image-contract.md § Building the web-lane tools from source.
WEB_LANE_TOOLS_SOURCE=legacy(new): the package stage’s pre-2026-09-23 from-source leg, verbatim.cargo install --locked <tool> --version <pin>intoCARGO_HOME(so cargo records it in.crates.toml), in the stage’s own env: no forced C env, no gate, no cache, no provenance line, and a failed install only WARNs. It reads neither the cross artifact norWEB_LANE_TOOLS_CACHE.What
nativeis, said plainly in the contract, the knob table, AGENTS.md and the failure-mode entry: the package stage with rv64gc Rust, plus four differences from the old build. It forces vendored static C. It installs a bare binary, so a consumer’scargo install <tool>stops onbinary already existswithout--force, as it already does on amd64 and arm64. Its gate is fatal. It reads the cache first.Every guard now has a test and a mutation. Twelve new
rust.web-lane-*entries: the producer’s 30-minutetimeout(a recording stub),unset BUILDARCH BUILDPLATFORM(an inheritedBUILDARCH=arm64must still recordcross:amd64), the “at least one GLIBC_ entry” refusal, a fatal emptygetconf, a fatal failedinstall, the producer’s sysroot GLIBC ceiling, the up-front knob check insetup-package-image.sh(a typo stops amd64 even when its prebuilt succeeds), both no-rustcbranches and three forlegacy. With norustc -Vrelease,crossis now an ERROR wherever an artifact was expected; askippedarch still WARNs.Both suites are hermetic.
test-web-lane-tools.shtookTARGET_ARCHfrom the caller: in the CI image (TARGET_ARCH=amd64) it failed 39 of 194 assertions, and an exportedWEB_LANE_TOOLS_SOURCE=nativefailed 11._wltnow clears the arch and Rust build env (the fixture arch isWLT_T_ARCH).test-setup-package-image.shread the image’s ownversions.envand failed 3 there; its sandbox now clears that, the*_SHA256pins and theWEB_LANE_TOOLS_*switches.What re-keys, against the entry below: android’s
web-lane-toolsproducer RUN and package’s setup RUN, per arch, because both bind-mount the edited library. Noversions.env,01-coreor other Dockerfile instruction changed; theDockerfile.packageedit is a comment.Verified here: both suites on the Windows host (the two JAVA_HOME symlink failures in
test-setup-package-image.share host-only and predate this) and in the:latest-crossimage with its own env, all green there; every mutation tied to either suite bites in that image. Not verified: no chain has run either entry.legacy’s command is the one the 2026-09-22 riscv64 chain ran, but no chain has reached it through the switch.
2026-09-24 - ORT census: a consumer that names the chain directory is not an ORT build#
Fixes G6 failing OmniAccelerANT’s Linux lane (run 35928030957, x64 and arm64):
UNPROVEN /lib/liboxidant.so -- an ORT-named binary with no source fingerprint. The
bundle was correct: it held the chain ORT, byte for byte. The census got the file
wrong. Details and measurements:
docs/onnxruntime-single-source.md § What “the chain ORT” is.
Cause 1: a directory string counted as a fingerprint. OxidANT’s loader keeps
/opt/onnxruntime/onnxruntime/core/as a string to check the ORT it loads. The census treated any file holdingonnxruntime/core/as an ORT build. Now a fingerprint is a whole source-file path ending in NUL (.cc,.cpp,.cxx,.c,.h,.hpp,.inc,.cu,.cuh), the shape__FILE__leaves. Changed inort_census_probe.py(MARK) and in the Windows twin (WindowsOrtProvenance.Common.psm1,$script:OrtPathMarker), whereoxidant.dllwould have beenSTALE.liboxidant.sois now an importer, and G6 checks that it resolves to the chain ORT, which it used to skip. Every fingerprint of the chain ORT 1.29 (592), its dnnl EP (23) and the Android build (545) still matches.Cause 2: a relative root printed as “no fingerprint”. rustc packs string literals with no NUL between them, so the text before the directory became a relative root (
''), whichemit()prints as-. The probe now prints it as., andcheck-ort-provenance.shreads.as relative. A lone relative root isFOREIGN(relative), as on Windows, notUNPROVEN.The same false positive hit G1 on FFmpeg. FFmpeg compiles its configure line into every lib and tool, and the chain’s passes
-I/usr/local/lib/onnxruntime-cpu/include/onnxruntime/core/session(ffmpeg-dnn-backends.sh). The old rule made every FFmpeg ELF an ORT build with no fingerprint. Whole-image census of a local:latest-cross(e8eb8a42), old vs new probe:UNPROVEN14 → 1 (10 FFmpeg ELFs and 3 scratchliboxidant.socopies gone).libavfilteris now the registeredffmpegconsumer it is, graded by RES and STAMP, and the other nine are not ORT at all. The runtime smoke’s census (SHIPPED-TRUTH E) would have reported the same ten on the next image.Stricter, on purpose: a reference file whose roots are all relative is now
FOREIGN, where-passed before. It matches Windows. e8eb8a42 has no such reference: its two unfingerprinted ones (libonnxruntime_providers_shared.so, the Androidlibonnxruntime4j_jni.so) still print-and pass as before. The CI image (ec4bb68b) was not available here to check.Tests:
test-ort-census.sh110 → 122 assertions (the fingerprint shape, a rustc-packed consumer passing beside the chain ORT, then failing with no ORT or a one-byte-off one, a relative-only ORT). 6 new mutation entries (ort-census.probe-*,ort-census.relative-dot), each verified.Smoke.OrtCensus.Tests.ps124 → 26.Reproduced in a local
:latest-cross(e8eb8a42) with the REALliboxidant.sobuilt from OxidANT f018bec with the lane’s features, inside the 2026-09-17 release bundle, through OmniAccelerANT’s own packer and bundle checks. Hub a7ccc896 gives CI’s line andbundle closure: 1 failure(s) across 42 ELF file(s); this change givesORT census PASS. Five mutations of that bundle (ORT removed, one byte off, foreign, no$ORIGIN, a renamed re-rooted ORT) each still fail.Consumers pick it up with a hub pin bump, and their Windows ORT test fixtures need one edit. OxidANT’s loader does not change: the consumer rule is “name the chain directory, never embed a whole ORT source path”. The rule is in
AGENTS.md§ Linux Build Rules and in the section 25 bullet ofdocs/windows-build-invariants.md. But a fixture that fakes an ORT as"$chainSrc OrtGetApiBase"has no fingerprint under the new rule. At this commit, OmniAccelerANT’sOrtRunner.Tests.ps1fails 2 of 6, and OxidANT’sOrtPayload.Tests.ps1and AccelerANTgine’sOrtBundle.Tests.ps1fail one case each (UNPROVENwhereSTALEis expected). Ending the fake path with a NUL ("$chainSrc`0OrtGetApiBase") fixes all three and passes at either hub:onnxruntime-single-source.md. (Corrected on 2026-09-24. This bullet first said nothing in the consumers changes.)
2026-09-24 - Review follow-ups to the sccache-endpoint fix#
Six review findings on the entry below, each verified before it was applied.
A parent the run did not build is graded before a stage inherits it.
FROMcopies the parent image’s config ENV (the merge Dockerfile’s comment said it does not; corrected). With the mergebuiltstage’s own ENV gone, abk-windows-toolchainfrom before the fix passed its endpoint straight through to the published image, and only the final gate, hours later, would have seen it.Invoke-BkStagenow solvesDockerfile.publish-gateon everyBASE_IMAGEthe run neither built nor graded, before the stage; a stale parent fails in seconds and the error says to rebuild it. The fresh toolchain is graded right after its solve. All three sites go throughInvoke-BkPublishGate. A run started before this commit has no gate: restart the chain withtoolchainin-Stagesfrom a fresh driver process; never resume it.windows-build-resources.md§ An image this run did not build.The Windows gate’s default path is tested. The gate’s RUN passes no
-Scopes, and no test did either. Now a uniquely named leaking Process variable must make a bareAssert-ImageEnvPublishablethrow, the Process/Machine/User list is pinned from the AST, and an in-suite mutant for each proves it bites.The Linux image-env gate has no switch. Check 6 of
verify-shipped-wrapper.shsat inside theRUNTIME_IMAGE_SMOKE=1block and never ran on--manifest-only/--repair. It is now_manifest_image_env_gate, the first step ofcreate_manifest, on every path that creates an index; check 6 is gone. Themakehelp and fourimage-env.manifest-*mutations (replacing the twoimage-env.wrapper-*) follow.The consumer probe tries every address at once.
Test-TcpEndpointReachablenow resolves within the budget and connects to every address in parallel. Before, an IPv4-only listener behindhttp://localhost:<port>was removed after 2049 ms, because Windows refuses::1only after ~2 s; now it is kept in 37 ms. New cases: that one, a 200 ms bound against a closed port, and an unresolvable name, with an in-suite mutant per case.The build host keeps its remote tier. Consumer builds on the build host reached WebDAV only through the leaked ENV and would have lost it silently.
Invoke-ContainerBuildnow forwards this host’sSCCACHE_WEBDAV_ENDPOINTandSCCACHE_MULTILEVEL_CHAINinto the container at run time (Add-HostSccacheRemoteEnv) unless-CacheEnvsets them (''opts out). Adocker runby hand still passes-eitself:windows-build-resources.md§ The build host’s remote tier, at run time.Two things this host needed to commit it through the hook.
verify_doc_links.py’s git-free floor comparedstr(path)with POSIX entries, so on Windows it ignored nothing andtest-doc-links.shfailed at baseline; it now matches the POSIX spelling (a no-op on Linux). Andbuild-cross-chain.sh’s_CHAIN_RUNTIME_GATESdoes not name the new Linux gate yet: staging that file samplestest-chain-lifecycle.sh, whose symlink cases fail on a Windows host, so that one-line edit is for a Linux host.Re-key set: nothing beyond the entry below. The driver, the tests and the Linux host scripts are in no image closure, the merge Dockerfile edit is a comment (no LLB change), and the two edited modules are copied only by the final
windows/Dockerfile, which re-keys anyway. Base, the sdk slot and the toolchain are untouched. Linux: nothing re-keys.
2026-09-23 - riscv64 web-lane tools: cross-built in android, cached, native one switch away#
riscv64’s package stage compiled wasm-pack and flutter_rust_bridge_codegen under
QEMU on every chain: 960 s + 1,665 s of a 3,384 s RUN (riscv64 layer file times,
2026-09-22). Owner decision: both options. A cross build on the build host is
the default; the native build stays, one switch away. Speedup item riscv-tools,
with its challenge’s corrections. How to pick a path:
consumer-image-contract.md § Building the web-lane tools from source.
Producer. New stage
web-lane-toolsinDockerfile.android, FROMandroid-sdk, i.e. beforefinalswaps the amd64-hosted cross GCC out. It runs06-packaging/web-lane-tools.sh produce:cargo install --target <triple>under the hub’s ownsetup_linux_cross_env, a 30-minute bound, vendored static C, its own cachemounts. It writes a manifest per tool. A failed cargo or gate recordsstatus=failedand android stays green.finalCOPYs the output as its LAST instruction, so a producer change re-keys nothing above it.Package side.
install_web_lane_toolchainkeeps the prebuilt-first path, so amd64 and arm64 install the same bytes as before. Its from-source leg is nowwlt_install_from_source: the cross artifact or a gated nativecargo install --lockedin the package stage, chosen byWEB_LANE_TOOLS_SOURCE=auto|cross|native(defaultauto), through a version-keyed binary cache (WEB_LANE_TOOLS_CACHE=on|refresh|off).legacy, the old build verbatim, came a day later (2026-09-24 entry).Fail loud on a claim. Every from-source binary passes
wlt_assert_binaryon its staged bytes before install: ELF64, machine, lp64d, loader, aNEEDEDallowlist, the image’s GLIBC ceiling,--version. Artifacts and cache entries must also match their sha256. A binary that claims to be good and fails is fatal in every mode, and so is a bad knob orcrosswithout an artifact. Availability misses still only WARN (rust.web-lane-non-fatal, retargeted to the new file).Native hosts. A build host whose arch is the target (the X100, a Jetson on
CROSS_BUILD_PLATFORM=linux/arm64) getsskipped: native-build-platform, soautobuilds natively there with nothing set.Where it deviates from the design, and why. The COPY sits after the GCC swap, not before it (the challenge: above the swap, every producer change re-ran a prefix-sized copy). The producer loads 01-core by explicit path from per-file mounts, never
source_module, which would findandroid-sdk’s older copy.crossbuilds natively on askippedmanifest, so amd64 and arm64 do not turn a failed prebuilt download into an error. The native leg keeps the old build’s rv64gc Rust, but not its command, its C env or its failure policy (corrected in the 2026-09-24 entry). The knobs are forwarded bylib-orchestrator.sh, not by newversions.envlines (Phase 2 of the design), and the library is bind-mounted into the setup RUN rather than COPYed, so no image gains a file. The producer keys on the rustc it actually runs, and its registry cache id is per target.What re-keys on the next chain. base, compiler, sdk and media: nothing. No
versions.envor01-coreedit, andlib-orchestrator.shis in no Docker closure. android, per arch: the new producer vertex (seconds for amd64/arm64, which only write skip manifests; the cross compile for riscv64) andfinal’s new last layer; the five library stages and the GCC swap hit the cache. package, per arch: the setup RUN and what follows it, which re-run on every chain anyway. New cachemount ids:web-lane-tools-cross-<target>,cargo-registry-web-lane-tools-<target>,web-lane-tools-bin-<arch>.Verified here:
tests/test-web-lane-tools.sh(new, off-target, stubbed readelf/cargo plus real-readelf cases),tests/test-setup-package-image.sh(now drives the real library), 30 newrust.web-lane-*mutations, the preflight gates. Not verified: no chain has run it. The producer has never run insideandroid-sdk; the design’s probe ran the cross build in the amd64 runtime image with apt cross binutils. What to watch for on the first run is indocs/build-watch-list.md.
2026-09-23 - The arm64 and riscv64 GCC ship libsanitizer#
What was broken. In the published :latest (index ec4bb68b), the arm64
(b867d353) and riscv64 (66f4dea1) images’ /opt/gcc-16.2.0 had no
include/sanitizer/ and no libasan, libubsan, liblsan or libtsan. Any
-fsanitize=address build there failed with fatal error: sanitizer/common_interface_defs.h: No such file or directory. It was found through
AccelerANTgine’s linux-debug-GNU preset, where abseil’s dynamic_annotations.h
includes that header. amd64 was not affected. Mechanism, gates and cost:
docs/cross-build-verification.md § The native GCC ships libsanitizer.
Cause. Those images’
ccis the Canadian-native GCC (host == target), andbuild-gcc.shbuilt and installed only libgcc, libstdc++-v3 and libatomic for every--targetbuild.Fix.
build-gcc.shaddstarget-libsanitizerto the make and install targets when--hostequals--target. The plain cross compilers and the full-make host GCC are unchanged. No configure flag,gcc.shor Dockerfile changed.Gates.
build-gcc.shfails when libsanitizer installed no header orlibasan, because its configure can switch itself off silently.swap-native-gcc.shchecks the header andlibasan/libubsan/liblsan/libtsan(pluslibhwasanon amd64 and arm64), each with the image’s ELF machine, on every build-host shape. The native Jetson and X100 lanes’ own full-make GCC is checked too. The wrapper smoke compiles and links-fsanitize=address,undefinedagainst the header and requires both runtimes inNEEDED. The runtime-image battery runs that binary only where the build host’s arch is the image’s, since qemu-user cannot host ASan/LSan reliably. Both smokes’ TU shifts by a variable ((8 >> c)): GCC 16 defaults to C++20, which drops the shift-base check, and a TU with no UBSan call loseslibubsanunder--as-needed. New suitetest-native-gcc-sanitizers.sh, which runs both smokes against a modelled GCC and executes the battery’s container body; 23 mutationsnative-gcc-san.*.What re-keys.
build-gcc.shis in the compiler image’s closure (Dockerfile.toolchain RUN 1, the LLVM RUN, RUN 3c and the/opt/scripts/toolchainCOPY). The next Linux chain therefore rebuilds the one compiler image: the host GCC, both plain crosses, both Canadian natives (now with libsanitizer), then LLVM, Rust and CPython on the new digest. Every stage below it rebuilds on all three arches: sdk, media, android, runtime package, wrapper-smoke, wrapper and the manifest. Three more edits land in stages that re-run anyway on their new parents:swap-native-gcc.sh(the android final COPY),validate-compilers.sh(Dockerfile.package’s artifact-source bind and its COPY) andbuild-gcc.shagain (Dockerfile.package’s02-toolchain/COPY). Base is untouched.smoke-runtime-image.shruns on the host and is in no image. The compile caches survive the re-key, because the sccache/ccache mounts are outside the image digest. The host and plain-cross GCC configure lines are unchanged, so their compiles should hit; the run’s sccache stats will show whether they do. The nvidia and rocm variant images were never affected (their GCC is the build host’s full make). Their chains re-key on the next run because they build on the new shared sdk, with no change in their GCC. Consumers that still pull:latest-crossthrough the hub’s actions at@mainkeep the frozen image.The next run must include the compiler stage. On arm64 and riscv64 a partial rebuild on the old images now fails on purpose, instead of shipping without the runtime: a
--from-stage sdk|media|androidrun on the old compiler digest stops at the android swap, and a--from-stage runtimerun (orbuild-runtime-artifacts.sh/build-runtime-manifest.sh) on the published android images stops at the wrapper smoke withCOMPILER FAIL [gcc-sanitizers]. No arm64 or riscv64 wrapper can be rebuilt from a partial stage until a chain from the compiler stage down has run. amd64 is unaffected.Size. About +79 MB uncompressed in the arm64
/opt/gcc-16.2.0, the size of amd64’s set (79.3 MB, mostly unstrippedlib*san.a). riscv64 has no hwasan, but its static archives are about 3x amd64’s (libstdc++.a171 MB against 55 MB), so expect roughly +190-200 MB there. The compiler, sdk, media and android images carry both native prefixes (roughly +270-280 MB), but those images are not shipped. None of this is measured on a built image; the compressed delta is not known.Unverified until the Linux chain runs on the build host: that libsanitizer configures and builds in the Canadian cross at all (libstdc++-v3 builds the same way, which is the main evidence it will); how much time the two libsanitizer builds add (not measured); the compressed size; and the sanitizer RUN on amd64. The arm64 and riscv64 RUNs happen only on a Jetson or X100 build host. TSan on riscv64 needs an sv39 or sv48 VMA, unverified on the X100. The static and stubbed tests ran on a Windows host. The suite also ran in a local amd64
:latest-cross, where the wrapper smoke compiled, linked and passed with the real GCC 16.2.0, and the smoke TU ran natively under ASan+UBSan.Still missing on arm64 and riscv64, out of scope: libgomp (
omp.h), libitm and gfortran, and a targetlibasanfor the amd64 image’s plain cross compilers.
2026-09-23 - The published image no longer carries the build host’s sccache endpoint#
What broke. :winamd64 (digest 3137eebe…, built 2026-09-22 from hub 0d85b8c1)
shipped SCCACHE_WEBDAV_ENDPOINT=http://<webdav-lan-ip>:5000 — the owner’s LAN WebDAV — plus
the build host’s chain, force-local and cache settings, from the ENV blocks of four
Dockerfiles (patched-llvm from #164, media-builder’s common, the merge built stage,
rocm-migraphx). The consumer wiring turns the sccache launchers on whenever sccache is on
PATH; on a GitHub runner sccache 0.18’s server fails its storage check (tcp connect error)
and exits, every client times out after 10-12 s, and CMake calls clang-cl broken. That turned
AccelerANTgine and OmniAccelerANT red (runs 35921977157, 35912798986), and nobody could see
why, because $null = Invoke-ContainerBuild discarded the build’s stdout. Account and table:
docs/windows-build-resources.md § What the published image carries.
(1) ARGs, not ENV.
SCCACHE_WEBDAV_ENDPOINT,SCCACHE_MULTILEVEL_CHAINandSCCACHE_FORCE_LOCALare ARGs with no default, redeclared in every stage whose RUN compiles through sccache:patched-llvm; media-core-built-onnx/-ffmpeg/-opencv/-hailo, media-core-built, media-litert-built and media-tvm-built; the mergebuiltstage;rocm-migraphx. An ARG does not cross a FROM, and the media-core chain crosses solves. The ENV blocks keep only the container-local runtime defaults (SCCACHE_DIR,SCCACHE_CACHE_SIZE,SCCACHE_ERROR_LOG,SCCACHE_LOG,SCCACHE_IDLE_TIMEOUT), with unchanged values.Dockerfile.sccache-write-probefollows (it ships nothing).(2) Publish gates. Windows:
windows/Dockerfile.publish-gate+WindowsImageEnv.Common.psm1, solved byBuild-Buildkit.ps1after the smoke gate and before-FinalTar/-PushRef, not skippable by-SkipSmokeGate; it grades the Process, Machine and User scopes. Linux: check 6 ofverify-shipped-wrapper.shreads each wrapper’s config ENV, hard even underWRAPPER_CONTENT_GATE=0. Static, both lanes:linux/scripts/verify_image_env.py --dockerfileas pass 0b oflint-dockerfiles.sh, which also refuses a compiling Windows RUN whose stage lacks the endpoint ARG. A leak is a build-host sccache name or an RFC1918/link-local address in a host position;linux/scripts/tests/image-env-cases.jsongrades the PowerShell and the Python matcher alike.(3) Consumer defense for images already published.
Enable-SccacheCompilerWrapper, the choke point ofInitialize-BuildCacheEnvironmentandInvoke-CmakeConfigureAndBuild, now callsClear-UnreachableSccacheEndpoint(exported): a 2 s TCP probe of the endpoint; if it fails, the variable (and a chain naming webdav) is removed for the process with one WARN, and sccache falls back to local disk. A reachable endpoint is untouched.(4) Container output is visible. Every unconsumed docker call in
WindowsContainerBuild.Reuse.psm1ends in| Out-Host— the tar-pipedocker exec, the bind-mountdocker run, the stale-cache cleanups, the output-directory probe and the source prune — so$null = Invoke-ContainerBuildno longer swallows the build.$LASTEXITCODEand the returned object are unchanged.Re-key set. Windows: the
patched-llvmstage (sobk-windows-toolchain), every media-builder stage belowcommonplusbuildmods/tvmmods, the whole merge Dockerfile, and on the rocm lanerocm-migraphx; everything after them re-keys because its parent does (llama, torch, final; final also COPYs the modules dir). NOT re-keyed:Dockerfile.base, the sdk slot (CPU alias,Dockerfile.nvidia,Dockerfile.rocm) and the toolchain’sbuilderandbuilt(CPython) stages — no file they COPY or mount changed. Linux: no Dockerfile changed, nothing re-keys.Not covered, recorded. BuildKit writes a RUN’s build args into the layer history, so the final image’s history still names the endpoint; nothing reads it at run time. Backlog #177 (a secret mount) owns it. Hostnames, loopback and files inside the image are out of the matchers’ scope.
Tests. Windows:
ImageEnv.PublishGate.Tests.ps1(fixture parity, one in-suite mutant per rule, the driver’s call order and two driver mutants),Build.SccacheEndpointProbe.Tests.ps1(closed and listening loopback ports, both wiring sites, one WARN, a mutant),ContainerBuild.Output.Tests.ps1(child-session visibility, an AST guard with two mutants) and a new case inModules.Orchestrators.Tests.ps1. Linux:test-image-env.sh(a fake-nerdctl run of the wrapper gate included). Fifteen newmutations.jsonentries (dockerfile-lint.image-env-*,image-env.*), each proven to bite. Docs:windows-build-resources.md,build-cache-tiers.md,windows-builds.md,windows-build-invariants.md(49 rules now),failure-modes.md,code-quality-tooling.md, the #164 archive entry, backlog #177 andAGENTS.md§ Push And Publish Rules.
2026-09-23 - Windows Scripts CI is green again: an unmounted CUDA root reads as absent, and the Hailo patches are checked#
WindowsSourceBuild.Cuda.psm1.ad5b7d8f(#176) builtGet-CudnnLibraryDir’s andTest-CudaWindowsArm64Payload’s paths withJoin-Path, which resolves the root’s drive and throwsDriveNotFoundExceptionon an unmounted one; the contract (and theSourceBuild.ResolveX:\cases) is$null/$false. Everylint-and-testrun since 2026-09-20 failed on it, on the runner and on any host without anX:. The paths are interpolated now; the rocm lane gate inGet-GpuEnvironmenthad the same latent shape and changes with them. The tests stay as the regression cases.Test-PatchesApplyClean.ps1mapspatches/hailo/tohailo-ai/hailortatv<HAILORT_VERSION>(the tagBuild-HailortFromSource.ps1downloads). Without it the four Hailo patches failedpatch-driftunchecked sinceda14e043; all four apply cleanly at v5.4.0.
2026-09-23 - ONNX Runtime has one source: the chain#
Owner rule: every component that compiles against, links or loads ONNX Runtime uses
the ORT this repo builds from source, on both lanes and in the consumer repos. No
NuGet, PyPI, release-zip, apt, bundled or pyke copy, and no exceptions; a plugin EP
counts as ORT. Every consumer, the six guards and what each cannot see:
docs/onnxruntime-single-source.md (new). The rule
itself: docs/windows-build-invariants.md (48 rules now) and AGENTS.md § Linux Build
Rules.
What was broken. Windows OpenCV downloaded
onnxruntime-win-x64-1.25.1.zipat configure time (arm64 zip on the cross lane) and linked it, unpinned. Windows GenAI compiled against NuGetMicrosoft.ML.OnnxRuntime.DirectML1.24.4 from a feed, unhashed. The rocm venv carried PyPIonnxruntime-ep-webgpu. Neither the OpenCV nor the GenAI download showed in the shipped image. Correction to the 2026-09-23-Variant rocmsdk-slot entry below: its “Found, not fixed (every lane)” OpenCV line was true of the Windows lanes only; Linux pre-setHAVE_ONNXRUNTIMEall along.Windows OpenCV (every lane): a nested-header shim over the chain ORT,
-DHAVE_ONNXRUNTIME=ON, both CMake packages disabled, and a configure gate. G-API’s DirectML EP is compiled in for the first time, delay-loading dxcore/d3d12/dxgi/DirectML through an OpenCV CMake hook. dnn and G-API now needonnxruntime.dll1.30 or later.Windows GenAI: an
ORT_HOMEshim; theortlib/onnxruntimeFetchContent names point at an empty dir, so a fallback fails configure. Configure and tree gates; the configure log is kept.DirectML.h/D3D12Core.dlland a DXC restore are still unhashed NuGet fetches (not ORT), recorded as follow-ups.WebGPU EP: the rocm spike builds ORT’s in-tree WebGPU EP (Dawn on D3D12) into the chain
onnxruntime.dll, with a pinned DXC release beside it. The PyPI plugin and itsTORCH_ROCM_WINDOWS_ORT_EP_WEBGPU_*pins are gone; fiveORT_WEBGPU_WINDOWS_*pins are new.-NoRocmSpikesbuilds without it.G2, the build gate + stamp:
Assert-ChainOrtOnly(WindowsOrtProvenance.Build.psm1) andort_assert_chain_only(linux/scripts/03-media/ort-provenance.sh) end every consumer build: OpenCV, GenAI, FFmpeg, GStreamer, and the AMD GPU EP. They grade the tree, fetch caches, build records and logs, and a pass writes the stamp G1 requires. Placed outside the census module and03-media/core/, per-file mounted, for the cache.G1, the ORT census on the shipped image: Windows smoke section 25; Linux SHIPPED-TRUTH E (
check-ort-provenance.sh,ort_census_probe.py). Byte identity with the image’s own chain ORT, the loader order per importer, the consumer contract, stamps.G3: both final images set
ORT_LIB_LOCATION,ORT_PREFER_DYNAMIC_LINK=1,ORT_SKIP_DOWNLOAD=1andORT_DYLIB_PATH, so anort-sysbuild there cannot fetch pyke’s ORT. Asserted by section 19 and the new contract rowort-crate-env.G4:
verify-critical-fixes.shfix11, a static denylist over both lanes’ scripts, plus the G1/G2/G3 wiring. G6:Test-OrtProvenanceTree/ `check-ort-provenance.sh` for consumer bundles. App venv: one census,
ort-venv-census.py, purges by name pattern and package ownership (no fixed lists) and fails the torch stage unless every ORT distribution is a chain wheel byte for byte. A missing Linux chain wheel is fatal (it used to fall back to PyPI 1.27.0). Smoke section 21 adds DirectML and chain-wheel checks on every amd64 lane (floor 2 → 4). The nvidia smoke counts any GenAI flavour as the GenAI. The Windows appuv syncno longer installs the lock’s PyPI ORT and GenAI before the purge: eachonnxruntime*name inuv.lockis--no-install-package, and one without a chain wheel of its family stops the stage.Consumer CI venvs (
python-ci-*): inside our images,uv_sync_project/Sync-UvProjectDependenciesreconcile onto the chain wheels and fail unless the census and an import prove it. Linux gainsORT_CHAIN_WHEEL_DIR=/opt/onnxruntime-wheels. Owner decision: the 3.13 legs go (the chain wheels are cp314, the images carry 3.14 only).ci_tests.shPY_VERSIONSandci_build_docs.shCOVERAGE_VERSIONdefault to 3.14 and OrchestrANT drops its 3.13 legs. WebDavClient (no ORT) needsdocs-python-version: '3.13'in its workflow no later than the commit that moves its hub pin past this, or its docs job syncs atheris on 3.14 and fails. The Windows image COPYs the census toC:\temp\scripts\, beside its module copy, which failed every sync without it.Linux: a missing chain ORT stops OpenCV (no more silent build without it); dnn’s copies in
/opt/opencv5/libbecome links to the chain;000-onnxruntime.conf; FFmpeg’s chain-Lgoes first, a chain its probe cannot link stops FFmpeg (it used to skip the backend), and a link gate readsconfig.mak; a gate over the gstonnxplugin’sbuild.ninja; apt ORT denied in code, by an apt-plan gate and a dpkg gate. The chain wheel manifest G1 reads follows the wheel throughrepair-wheels.sh’s cross strip and retag, and a twin of its name stops the stage. G2’s default fetch caches match Windows (pyke, pip, uv, NuGet) plus cargo and the FFmpeg SDK cache; uv counts only wheels it downloaded, since its mount keeps old chain wheels. GenAI’s G2 RUN mounts its build’s caches.Hub consumer API:
WindowsOnnx.Commonrefuses NuGet ORT,Get-OnnxPackageLayoutthrows,Get-OnnxChainLayoutis new, andWindowsMediaRuntime.Commonstages the chain only.Consumer repos (working trees; gitlinks not moved): OxidANT drops
download-binariesforload-dynamic, loads only fingerprinted chain files, gates its lock, and ships the chain DLLs in its zip/MSIX/MSI; AccelerANTgine’s CMake requires the fingerprinted chain prefix; OmniAccelerANT stages and stamps the chain ORT beside its runner, refuses anything else at launch, and drops the System32 fallback.No in-box ORT in the images, now asserted. A one-
RUNprobe overbk-windows-base(servercore:ltsc2025, OS 26100.33438) found noonnxruntime*, Windows ML or DirectML DLL anywhere onC:\. Smoke § 25 gainsORT in-boxon every amd64 lane (verdictINBOX, floor 4 → 5; the cross lane skips it), and no exemption can waive an in-box path. A base that ships one keeps the previousWINDOWS_BASE_DIGEST: app-local chain copies cannot clear the census today, and fix11 refuses a script line that deletes or patches an in-box ORT underC:\Windows.Re-keys: the full chain, on both lanes.
versions.envchanged. Windows imports it inDockerfile.base’s last layers, so every stage after base rebuilds on every lane. Linux mounts it into its base, so everything after base rebuilds on every arch. The script edits alone would re-key most of the Linux media build too:Dockerfile.mediamounts01-core(python_uv.shchanged) whole into most of its RUNs, and theonnxruntimescript dir (60-build-genai.shchanged) whole into the GPU ORT, GenAI, wasm and js/pkgconfig RUNs. Images built before this fail the STAMP check until rebuilt.Not proven by a build. Nothing but that probe ran in a container. Likeliest first reds, all fail-closed: GenAI and G-API’s DirectML EP against the 1.30 headers under clang-cl; Dawn under clang-cl; a Linux package depending on
libonnxruntime1.x; a G2 false positive on a real record path.
2026-09-23 - Windows rocm lane: remaining AMD GPU paths#
The rocm lane fills its AMD GPU gaps: OpenCL, Vulkan, FFmpeg Vulkan, llama.cpp Vulkan,
gfx1200 and LiteRT, and the TVM ROCm spike builds again. All of it is gated on
HasRocm; cpu and nvidia keep their flags. Details: docs/windows-rocm.md.
TVM ROCm spike. It had failed the whole media-tvm RUN (IREE included): the minimal-LLVM branch never ran on amd64, because the patched toolchain LLVM (
AArch64;X86) is always on PATH.Get-TvmLlvmChoicenow treats a PATH llvm-config without AMDGPU as absent on the spike and buildsX86;AArch64;NVPTX;AMDGPU.ROCM-FEATURES.txtrecords the read-back target list, andTVM.ps1compares it with what TVM links (amdgpu, oramdgcnbefore LLVM 23).OpenCL ICD.
Install-Rocm.ps1registers TheRock’samdocl64.dllunderHKLM\SOFTWARE\Khronos\OpenCL\Vendors. Before, nothing did, and the loader found no platform.Vulkan loader. New
Install-VulkanLoader.ps1puts LunarG’s signedvulkan-1.dllinto System32 (FFmpeg’sdlopenand Python never read PATH), with the pinned copy and its licence inC:\vulkan-loader. New pinVULKAN_RT_WINDOWS_ZIP_SHA256, refreshed byspec_vulkan. New smokerocm-checks/GpuLoaders.ps1; amfcodec is now load-probed.FFmpeg gets
--enable-vulkanagainst the SDK’s headers and glslc: the vulkan hwdevice, 9 hwaccels, 5 encoders, 18 filters, swscale’s SPIR-V backend. A 35-symbolconfig.makgate and new smoke listings. libplacebo stays off.llama.cpp Vulkan. The same build’s official Vulkan zip in
C:\runtime\opt\llama.cpp-vulkan, its own layer, off PATH.Install-LlamaCppHip.ps1is nowInstall-LlamaCpp.ps1 -Backend hip|vulkan, andLlamaCpp.ps1grades both. New pinLLAMA_CPP_VULKAN_SHA256; the bump spec takes the newest build with both zips.Torch venv. Device wheels for gfx1200 next to gfx1201 (six new pins), and a gate that refuses a rocBLAS GPU without device pins.
ai-edge-litert2.2.0 with its WebGPU accelerator. The PyPI WebGPU plugin EP added here the same morning was replaced by the chain’s own WebGPU EP (entry above).Licences.
deps.jsonrows for the Vulkan loader, the llama.cpp Vulkan build (libomp in both zips),ai-edge-litert, and gfx1200 in the PyTorch row.Re-keys: the full chain, on both lanes. The new pins live in
versions.env, which Windows imports inDockerfile.base’s last layers and Linux mounts into its base. So every Windows stage after base rebuilds on every lane, and every Linux stage after base on every arch. cpu/nvidia output is unchanged. The media-tvm script edit alone would re-key that RUN on every lane (the script is bind-mounted).Nothing here ran in a container or on a GPU. Likeliest first breaks: AMD’s PAL initialising in a GPU-less Server Core (every OpenCV T-API check now loads it); the AMDGPU minimal LLVM’s time and memory; FFmpeg’s Vulkan sources under clang-cl. Windows Defender flags
llama-gguf-split.exeasWacatac.B!mlon this host.
2026-09-23 - Windows -Variant rocm: the ROCm layer moves into the sdk slot, and media turns on AMD GPU features#
The ROCm layer now sits where Dockerfile.nvidia sits (owner directive), so toolchain
and media build on GPU_TYPE=rocm: base → sdk=rocm → toolchain → media → migraphx → llama
→ torch → final. This supersedes the 2026-09-22 “forks after media” entry. Every feature is
gated on the new (Get-GpuEnvironment).HasRocm; the cpu and nvidia lanes keep their flags
and outputs, locked by tests in every touched suite. What each component enables, with
evidence: docs/windows-rocm.md.
Driver. Every rocm tag after
bk-windows-basecarries a-rocminfix (bk-windows-sdk-rocm…bk-winamd64-rocm), so a rocm run never overwrites a default image (golden test for cpu/nvidia on amd64 and arm64). New rocm-only stagesmigraphxandllama;-Stages rocmis refused. New-NoRocmSpikes(drops migraphx, passesTVM_ROCM=0).TVM_ROCMandTORCH_ROCM+ pins go to the rocm lane only (Get-BkRocmStageArg), also through the-ConcurrentAuxchildren. A rocm-Stageslist with a gap in the post-media chain is refused as a stale parent (backlog #39).Dockerfile.rocmgets a 45 GB disk floor.bk-windows-rocmis orphaned: remove it by hand.Contract.
Get-GpuEnvironmentreturnsHasRocm/RocmRootand throws onGPU_TYPE=rocmwithoutlib\cmake\hip(the rocm twin of #45).Invoke-CmakeConfigureappends-DCMAKE_IGNORE_PREFIX_PATH=<ROCm tree>on the rocm lane so TheRock’s flatbuffers/nlohmann_json/zlib never reach a non-HIP build; HIP consumers pass-AllowRocmPrefix. Meson (GStreamer), Bazel (LiteRT-LM), FFmpeg and TVM scrub the tree themselves and prove it with a post-configure gate. HIP device code compiles only with TheRock’s clang++ by absolute path.Media, rocm lane only.
GStreamer:
hip,amfcodec,d3d11,d3d12pinnedenabled(mesonautoalready built them everywhere).FFmpeg: AMD AMF (
--enable-amf, header-onlyAMF_HEADERS_*pin): h264/hevc/av1 encoders, h264/hevc/av1/vp9 decoders, vpp/sr/frc filters,amfhwdevice. A generic H.264/HEVC encoder lookup now resolves to*_amfon this lane.OpenCV: OpenCL T-API was already on everywhere; the clBLAS/clFFT probes are pinned off and a configure gate proves no ROCm leak.
IREE: HIP HAL driver and
rocmcompiler target, with a carried patch so the driver findsamdhip64_7.dll;IREE_ROCM_DEVICE_BC_SHA256mirrors IREE’s bitcode pin.TVM: OpenCL runtime; ROCm codegen + runtime as a spike. Fixed on the way: TVM’s
find_rocmreadROCM_PATHeven withUSE_ROCM=OFF.LiteRT-LM: GPU backend (WebGPU over Dawn on D3D12) with four
LITERT_LM_*_SHA256pins.
New rocm stages.
Dockerfile.rocm-llamainstalls llama.cpp’s official Windows ROCm build b11115 (SHA256-pinned, off PATH,LLAMA_CPP_HIP_HOME).Dockerfile.rocm-migraphx(spike) builds MIGraphX 2.17.0 from source (MLIR=OFF, a configuration no upstream CI builds) and AMD’s ORT plugin EPmigraphx-ep.dll; all 17 archives SHA256-pinned, the EP’s hash-less FetchContent pre-seeded and disconnected.Torch.
Dockerfile.torchendsFROM rocm-${TORCH_ROCM}: cpu/nvidia build the unchangedappstage; the rocm lane installs torch 2.13.0+rocm10.0.0 and torchvision 0.28.0+rocm10.0.0 offline from 10 URL+SHA256 pins (Install-TorchRocm.ps1).ONNX Runtime stays CPU + DirectML; the dead
onnxruntime_USE_ROCMbranch is gone.Smoke.
Test-RocmImage.ps1runs everywindows/scripts/build/rocm-checks/*.ps1(GStreamer, FFmpeg, OpenCV, IREE, TVM, LiteRtLm, Torch, LlamaCpp, MIGraphX), all GPU-less.Test harness.
Get-GpuEnvironmentrocm cases andGet-CMakeRocmIsolationArgsinSourceBuild.Resolve; new suites per component, each mutation-checked.Licences.
deps.jsongains TheRock, the AMF headers, the AMD PyTorch wheels, the llama.cpp and MIGraphX/EP closures and the LiteRT-LM GPU payload. The HIP/OpenCL runtime has no licence file: a public push of:winamd64-rocmis an owner decision.Cost. The touched modules and scripts are bind-mounted into the toolchain and media RUNs, so every lane rebuilds patched LLVM and media once with unchanged flags.
Found, not fixed (every lane, owner decision): OpenCV’s dnn module downloads
onnxruntime-win-x64-1.25.1.zipunpinned at configure time; LiteRT-LM is cloned by tag.Nothing here has been seen running on a GPU yet; see the open points in
docs/windows-rocm.md.
2026-09-23 - CUDA_ARCHITECTURES: Hopper (90) retired too#
Same day, same owner, one entry lighter: 86;87;89;120. 90 (H100/H200) joins
80 (A100/A30) on the commented line in versions.env; re-adding either is
inserting the number in ascending order. The CUDA compile now runs four cubins
per source file instead of five, and the image supports exactly the hardware
the owner has: RTX 30xx/A10/A40, Jetson Orin, Ada, and RTX 50 / RTX PRO
Blackwell. The absence stays a HARD edge – no PTX, so an H100 would fail at
session creation, which is why both retired numbers are documented rather than
deleted.
2026-09-23 - CUDA_ARCHITECTURES: 80 retired, Blackwell (120) added#
Owner decision. The set is now 86;87;89;90;120 — RTX 30xx/A10/A40, Jetson
Orin, Ada, Hopper, and GeForce RTX 50 / RTX PRO Blackwell. 80 (Ampere GA100)
is retired and kept as a commented line in versions.env, because its absence
is a HARD edge: ORT embeds no PTX, so an A100 or A30 now fails at session
creation rather than running a slow path.
Three facts, verified against NVIDIA’s docs and a real nvcc before the edit, decide why the value is spelled exactly this way:
Neighbours cover nothing. A cubin runs on its own major at an equal-or-higher minor: 86 does not reach 87, 90 does not reach 120.
ONNX Runtime narrows it further. v1.30.0 rewrites every entry to
sm_<cc>a-real— arch-specific, cubin-only. In the ORT artefact120covers 12.0 and NOT 12.1 (GB10/DGX Spark);100would cover B100/B200 but not B300 (10.3). The owner’s hardware is RTX-50-class, hence 120 alone.Jetson Thor is 110, its own major, reachable from nothing else — the same shape as Orin’s 87, and a deliberate not-yet for the arm64 lane.
The trailing-90→90a rewrite in 30-build-native-nvidia.sh is GONE. ORT
appends the a itself, so it was redundant — and being a suffix match it would
have silently stopped firing the moment the list no longer ended in 90, which
is exactly what this change does. Its removal also retires the “keep this list
ascending” constraint that versions.env warned about.
Thirteen places carried the literal list; all moved together, including the Windows Pester pin whose comment stated a standing owner directive (“NEVER trim”) that this decision supersedes. The directive survives in its real meaning — no trimming as a speed lever — with the set named as a decision.
How to turn an arch on or off, with the four rules and the cost, is now in
AGENTS.md § GPU architecture coverage; the user-facing card list is in
README.md § Which GPUs :latest-nvidia runs on.
2026-09-22 - hcsshim fork rebased: Install-NewHost builds 5e9df53c and re-pins a reused work dir; Invoke-WithEnv really removes#
Kataglyphis/hcsshim@feature/configurable-teardown-timeout is rebased onto upstream
main 0e1f18b7. The head moves from 19251429 to 5e9df53c, and PR
microsoft/hcsshim#2855 follows it. The patch is unchanged: same patch-id, and none of the
26 new upstream commits touch cmd/containerd-shim-runhcs-v1, internal/hcs, cow,
jobcontainers or uvm. The new build is 25 890 304 bytes with Go 1.27.1, sha256
7A4BF6A3…, vcs.modified=false. gofmt, go vet and the shim package tests pass.
Install-NewHost.ps1pins$forkPin = 5e9df53c…. The old commit is on no branch any more, so a fetch by its SHA works only as long as GitHub keeps the object.Sync-ShimForkCheckoutis new. It also re-pins a%TEMP%\kataglyphis-hcsshim-forkleft behind by an earlier run. Before this, a reused work dir skipped the fetch entirely, so a pin bump could quietly rebuild the old tree.Tests:
NewHost.ShimFork.Tests.ps1(3 cases).Mutation-checked by hand against three broken copies of the function: re-pin skipped, always fetch, fetch error swallowed. Each one made a case fail.
The test harness’s
Invoke-WithEnvnow really removes a variable. A PowerShell$nullreaches .NET’sSetEnvironmentVariableas''. That leaves the variable set to an EMPTY value, which child processes still see. It had two effects:$nullin-Varsnever removed a variable.A variable that was unset before the call was left behind as empty, although the comment said “removing”.
Measured on PS 7.6.6 / .NET 10: git then reads
GIT_DIR=''and stops withfatal: not a git repository: ''.cmd’sif definedcalls the same variable undefined, which is why nothing noticed before.The fix passes
[NullString]::Value.Harness.WithEnv.Tests.ps1(2 cases) and the shim-fork suite fail against the old version.The budgets of two code-dupes pairs involving
TestHarness.psm1shrank as a result (47 → 34, 35 → 28).
Docs:
windows-host-setup.md§ R1 has the new build numbers, the new stock size after Stevedore’s 2026-09-21 update (25 975 296), and how to update an existing clone after a rebase.windows-build-lanes.mdandfailure-modes.mdname the new binary.The
hcsshim-teardown-timeout/README.mdstatus header now records the rebase.
2026-09-22 - Windows -Variant rocm: the driver builds :winamd64-rocm#
Build-Buildkit.ps1 -Variant rocm builds the ROCm image. It runs the default chain
up to media, then Dockerfile.rocm → torch → final under its own tags
(bk-windows-rocm, bk-windows-torch-rocm, bk-winamd64-rocm), so a rocm run never
overwrites the default images. -Variant rocm -Stages rocm,torch,final reuses an
existing default media.
-Variant ''|nvidia|rocm;-Gpustays and means nvidia.Resolve-BkVariantrefuses before buildkitd is touched: rocm on arm64,-Gpuwith rocm,-Stages rocmon another variant, and a push tag that does not match the variant (only rocm may push to:winamd64-rocm).Install-Rocm.ps1refuses a media base that carriesGPU_TYPE,CUDA_ROOTorCUDA_PATH: the shared media tag can hold a leftover-Gpubuild.Smoke:
EXPECT_ROCM=1runsTest-RocmImage.ps1after the CPU suite. It checks the env contract, the absence of CUDA, that AMD’s LLVM does not shadowclang-cl/lld-link,.info\version, the device bitcode, and ahipcc --offload-arch=gfx1201compile.Torch stays CPU until OrchestrANT has a Windows ROCm extra.
The nvidia and default runs are unchanged: same tags, same push behaviour.
Tests:
Driver.Variant.Tests.ps1(8) andRocm.Install.Tests.ps1(17).
2026-09-22 - Hooks on a Windows host: pre-push clears git’s environment, the shellcheck ratchet grades again#
Git exports GIT_DIR (and GIT_WORK_TREE, GIT_INDEX_FILE, GIT_PREFIX) to hooks,
and the pre-push hook passed them straight to the mutation gate. Its fixtures run
git -C <tmp> init. With GIT_DIR set, each init re-initialised the REAL gitdir
instead of the temp one. For a submodule gitdir (.git/modules/<name>, a path that
does not end in /.git) git guesses “bare” and writes core.bare = true. Seen on a
push from this repo’s checkout inside OmniAccelerANT: from then on every git command
warned core.bare and core.worktree do not make sense, git status refused to run,
and git check-attr found nothing, so the EOL-attribute suite failed on 64 files. The
branches and the index stayed untouched.
linux/host-config/git-hooks/pre-pushnow runsunset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIXfirst, exactly as pre-commit already did.test-prepush-hook.shruns the hook with all four variables exported and asserts that none reaches a gate. Mutationmutations.push-clears-git-envturns theunsetinto a no-op, and the test goes red.Recovery and the symptom text:
docs/failure-modes.md.
The shellcheck warning ratchet never graded a file on a Windows host. Two bugs:
A bare
bashinsubprocessresolves to System32’s WSL launcher before PATH. That launcher mangles theC:\script path, so every staged.shfailed the pre-commit ratchet.--filespaths came back with\, while lint-shell.sh’s scope uses/, so each file counted as “outside the scope” (0 of 394 graded).
verify_shellcheck_warnings.py now takes PATH’s bash (_bash()), converts the MSYS
--print-bin answer with cygpath -w, and normalises --files to /. All three are
no-ops on Linux. Mutation shellcheck-warnings.bash-from-path bites the new
test-shellcheck-warnings.sh case. The suite itself still has 4 Linux-only assertions on
Windows, where it runs fake shellcheck scripts through native Python; at HEAD, 51 of
its 68 assertions failed there.
2026-09-22 - Windows ROCm layer: windows/Dockerfile.rocm + Install-Rocm.ps1 (not wired yet)#
ROCm on Windows now has a build path. It uses AMD’s documented Windows tar
install: the same TheRock 10.0.0 release, from the same stable.repo.amd.com host,
that setup-rocm-repo.sh installs on Linux. The driver does not build it yet.
-Variant rocm is the next step.
windows/scripts/host/Install-Rocm.ps1:refuses anything but amd64;
downloads
therock-dist-windows-<family>-<release>.tar.gzand verifies it against the pinned SHA256, refusing an empty pin;extracts it with System32
tar.exeintoC:\TheRock\build;asserts the layout (hipcc, hipconfig, hipInfo,
amdhip64_*.dll, the HIP headers, AMD’s clang, the device bitcode,.info\version= the pin);runs
hipcc --versioninside the container.
windows/Dockerfile.rocmforks after media, right before torch, because nothing in the Windows media chain can consume ROCm: MIGraphX is Linux-only. It sets AMD’s variables but never putslib\llvm\bin(AMD’sclang-cl.exe) on PATH, and appendsbin\last.versions.env:ROCM_WINDOWS_RELEASE=10.0.0,ROCM_WINDOWS_GFX_FAMILY=gfx120X-all,ROCM_WINDOWS_TARBALL_SHA256. AMD publishes no checksum, so it is self-measured likeROCM_GPG_KEY_SHA256.Rocm.Install.Tests.ps1covers the URL guard, the amd64 refusal, the layout gate (each required file removed in turn must fail it) and the PATH rules.Why, the measured hashes and sizes, and the licence inventory:
docs/windows-builds.md§ ROCm layer.
2026-09-22 - ROCm ASAN is optional and OFF; the plan for the first :latest-rocm run#
The owner asked for AMD’s AddressSanitizer packages alongside the normal ones,
then decided against them as a default once the size was measured: >100 GiB in a
container image is not acceptable. So ENABLE_ROCM_ASAN (Dockerfile.amd,
default false) gates the parallel packages-asan repo stanza, the install of
amdrocm-asan${ROCM_VERSION}, and whether copy_rocm_payload lets
/opt/rocm/core-asan-* into the shipped image at all.
Measured against the live repo index on 2026-09-22, which is why it is off:
amdrocm-llvm-dev-asan10.0 is 61.7 GiB installed, the full ASAN set
134.8 GiB (the normal image is ~19 GiB), all 30 gfx-specific ASAN packages
are gfx942/gfx950 only, and there is no ASAN MIGraphX and no ASAN torch wheel —
so the two things this image exists for stay uninstrumented either way.
Co-installing also collides on update-alternatives: the ASAN debs register the
same names at the same priority, so /opt/rocm/{core,lib,bin} and hipcc can
resolve into the ASAN tree, and which one wins flips between rebuilds. The knob
therefore re---sets every hijacked alternative back and then ASSERTS the normal
tree owns them, failing the build if not. None of this path has run yet.
Ten non-ASAN items from the same sweep are written up in
docs/linux-accelerator-images.md § ROCm, largest first: per-gfx metapackages
(~18.4 GiB -> 9-13 GiB), ROCM_PATH/PATH missing in the shipped image,
asserting the installed versions (stable is a ROLLING suite, so our
ROCM_VERSION pin is a label, not a constraint), the resolved ld.so.conf
path, /dev/kfd access for uid 1001, no seccomp=unconfined and no baked
HSA_OVERRIDE_GFX_VERSION, writable MIOpen caches, a GPU-less build-time
self-check (amd-smi, not the deprecated rocm-smi), AMD’s CDI container
toolkit, and a Renovate comment that watches a git tag instead of the apt
package.
2026-09-22 - :latest-cross is retired, not deprecated#
The alias lived for one day. The owner decided against a deprecation window, so
the mechanism is gone rather than disabled: CROSS_LEGACY_ALIAS_TAG,
cross_final_image_legacy_alias and the second manifest push in
build-runtime-manifest.sh are deleted. From here on a release publishes
:latest and nothing else.
The registry tags stay, and deleting them would take the fleet’s Linux CI down
with them. Every consumer resolves its container ref
through this repo’s composite actions pinned at @main (98 ...@main refs
across six repos, none passing an explicit image:), and main is 56 commits
behind: it still says CI_IMAGE_LINUX_TAG=latest-cross and both action defaults
still name the old tag. So the tag the fleet actually pulls today is
:latest-cross. The owner decided to stay on develop, so that merge is not scheduled:
:latest-cross remains the fleet’s CI ref, frozen at the last release that
published it. It is documented as do-not-delete rather than pending.
The deletion itself is also not a plain “delete the version”: :latest and
:latest-cross are ONE GHCR package version (sha256:e0de6c95…, and the same
for the arm64/riscv64 pairs), and GHCR deletes by version, not by tag. The old
name has to be made a version of its own first, or the deletion has to wait for
the release after which :latest has moved on and :latest-cross is frozen
alone. ghcr-delete-tags.sh already refuses the unsafe form: it skips a version
whose digest is shared with a tag being kept, so running it today is a no-op,
not a disaster (docs/linux-host-setup.md § B8).
test-tag-naming.sh keeps a regression guard: no tag function may compose the
old name again. Dated history keeps it, because the tag really was called that.
2026-09-22 - GPU variant chains: CROSS_VARIANT=nvidia|rocm builds :latest-nvidia / :latest-rocm#
A GPU image is now a variant CHAIN, not a toggle on the default one.
Before this change ENABLE_NVIDIA=true / ENABLE_AMD=true changed only the
build args. A GPU run would have pushed CUDA bytes under the default
cross-media-<arch>, cross-android-<arch>, :latest-<arch> and :latest. The
NVIDIA/AMD layers were not stages at all, only hand-run commands whose tags were
deleted from the registry this morning.
Tags.
CROSS_VARIANT(implied byENABLE_NVIDIA/ENABLE_AMD; both is an error) adds-<variant>to every tag from the newgpustage on::cross-toolchain-<v>-<arch>,:cross-media-<v>-<arch>,:cross-android-<v>-<arch>,:latest-<v>-<arch>and the manifest:latest-<v>. Base, compiler and sdk stay shared. A variant never gets the:latest-crossalias.Stage graph.
gpu(Dockerfile.nvidia/Dockerfile.amd) goes between sdk and media when a variant is set. The default graph is unchanged.Refusals (
stage-defs.shcross_variant_refusal, shared bybuild-cross-chain.shandbuild-cross-stage.sh):A variant starts at
gpuand refuses to build base, compiler or sdk.Every target must be the build platform’s arch, because CUDA/ROCm are installed for and compiled against it. rocm is amd64-only.
A pushing variant must build on
linux/amd64, because the shared:cross-sdk-<arch>it builds on is the amd64 lane’s.The Jetson lane (native,
--no-push) stays allowed.The variant’s
ENABLE_*/ENABLE_TENSORRTare exported where the graph is built. Otherwise a single-stage rebuild wrote CPU bytes under-nvidiatags.
Runtime helpers.
build-runtime-artifacts.shandbuild-runtime-manifest.shrefuse an output prefix without-<variant>, andbuild-runtime-artifacts.shdefaults tocross_final_image_tag. Before this,ENABLE_NVIDIA=true build-runtime-artifacts.sh --pushpublished a CUDA wrapper as:latest-amd64.Strictly serial (owner decision). The pidfile is claimed atomically (noclobber) at the check, and a live chain makes a second one refuse to start. Chains share the buildkit store and the disk guard, and the guard evicts whatever the running chain does not protect. A variant keeps its own
chain-status-<v>.jsonandout/build-logs/<v>/(also whenmakepasses the default--log-dir). Its runtime lane budgets 180 GB.NVIDIA defaults.
ENABLE_TENSORRT=false(nolibnvinferin the runtime payload yet). The wrappers takeonnxruntime-gpu+pytorch-cu130.ROCm runtime.
copy_rocm_payloadcopies/opt/rocminto the package, and000-rocm.confputs its libraries on the loader path. Before this, the MIGraphX EP had nothing to load.TheRock’s absolute
update-alternativeslinks are resolved hop by hop inside the artifact and re-made relative.A tree without
libamdhip64is fatal.Dockerfile.packagenow passesENABLE_AMDto the copy; without it the copy never ran. The wrappers takeonnxruntime-migraphx+ the app’spytorch-rocm71extra. That index stops at torch 2.13, so the pin enforcement now re-installs thePYTORCH_VERSIONpair from the newPYTORCH_ROCM_INDEX=rocm7.14(there is no rocm10 line). The old code’s CPU fallback would have swapped a ROCm torch for a CPU one without a word.
One onnxruntime flavour per GPU venv. The amd64 CPU
onnxruntime_dnnl(and a plainonnxruntime) are pruned next toonnxruntime_gpu/onnxruntime_migraphx. Before this, both force-installed into onesite-packages/onnxruntime/, and ARCH-PARITY refused the image. The parity table knowsENABLE_AMD→onnxruntime_migraphx, and the rocm wrapper gets the build-time twin of the CUDA check:torch.version.hipplusMIGraphXExecutionProvider.Reviewed before merge. An adversarial review (34 findings, 22 upheld by two independent skeptics) produced the items above. It also raised
test-gpu-variant.sh(45 assertions, through the real entry points in read-only modes) and env-isolated the default tag suites.The Jetson lane’s names follow the variant: the example image is
:latest-nvidia-hostarm64-arm64.arm64 CUDA from an amd64 host is feasible but not built. NVIDIA’s
ubuntu2604/cross-linux-sbsarepo carriescuda-cross-sbsa-13-4, the cross cuBLAS/cuFFT/cuSPARSE/cuRAND andlibcudnn9-cross-sbsa-cuda-13. NCCL exists only as a native sbsa.deb(unpack it into the target tree). Wiring it needs the ORT/OpenCV/TVM CUDA cross flags and an ELF-machine gate. Until then, the arm64 half of:latest-nvidiais built natively.
2026-09-22 - :latest-cross becomes :latest; variants are :latest-<variant>; the :hailo variant is retired#
The Linux default manifest is :latest (owner directive 2026-09-22). The
old name was historical. CI_IMAGE_LINUX_TAG=latest, and every derived tag
follows the prefix: wrappers :latest-<arch>, runtime stages
:latest-base-<arch> / :latest-package-<arch>, host-infixed runs
:latest-hostarm64. The registry was switched without a rebuild. The live
3-arch index (sha256:e0de6c95…) and its three children were re-tagged
:latest and :latest-{amd64,arm64,riscv64}, so :latest and :latest-cross
resolve to identical bytes. The old dead :latest from the ghcr-cleanup bug
was already gone (404), so nothing was overwritten.
:latest-cross stays as a deprecated alias until 2026-10-31.
build-runtime-manifest.sh now also pushes the same index under the name in
CROSS_LEGACY_ALIAS_TAG (cross_final_image_legacy_alias in tag-naming.sh).
That happens only for the amd64 lane’s default image, and only after :latest
passed every gate. After the date, empty the key and delete the tag. The
freshness check now receives --tag for the index the run actually wrote,
instead of always reading the default.
Variant grammar: <version>[-<variant>][-<arch>]. Variants exist only for
stacks that cannot ship in :latest, which today means :latest-nvidia and
:latest-rocm (rocm, not amd, so a variant never reads like an arch).
Neither is published yet. The registry’s :nvidia / :amd tags are
single-arch 2026-04 builds on the Ubuntu 24.04 base and were deliberately not
promoted. Hailo and QNN are built into :latest instead. The current release
carries HailoRT 5.4.0 + hailonet on amd64/arm64 (verified in the shipped
wrappers). It carries no QNN, because no QAIRT zip was staged in
linux/qnn-sdk/ for that build.
The standalone Hailo variant is gone. linux/Dockerfile.hailo is deleted,
along with its two code-dupes.allow rows. The published :hailo (run
20260919-…) was a generation older than the standard wrapper that already
carried the same payload.
Registry cleanup (same day): 21 stale tagged versions deleted after
proving no kept index referenced any of them: :hailo, :hailo-amd64,
:hailo-arm64; the April GPU chains (:nvidia, :amd, toolchain-/media-/
android-/torch- nvidia/amd, toolchain-amd, latest-cross-nvidia-amd64);
the old-name runtime intermediates latest-cross-{base,package}-{amd64,arm64,riscv64};
and the pre-chain :toolchain. No buildcache-* tags existed.
2026-09-22 - The arm64 GPU runtime image runs on a Jetson#
The runtime lane (base -> package -> wrapper) had never carried a GPU. Built
natively on a Jetson AGX Orin with ENABLE_NVIDIA=true, it now ships CUDA,
cuDNN and NCCL into the package, and installs pytorch-cu130 and
onnxruntime-gpu in the wrapper. On the Orin’s GPU (see
docs/linux-host-setup.md § B2b for the rootless call):
PyTorch:
cuda available: True, deviceOrin (8, 7), a matmul matching the CPU.ONNX Runtime 1.29: a MatMul served by
CUDAExecutionProvider.OpenCV 5.0 CUDA: one device,
cuda.thresholdbit-identical to the CPU.nvcc -arch=sm_87in the image: a kernel over 1M elements, 0 wrong.
Fixed on the way, each with a suite that goes red when the fix is removed:
--no-push wrapper builds could not see the android wheels (now a directory
context), the torch pin step swapped CUDA torch back to CPU, a read-only
/opt/wheels made the ORT-variant prune fail silently, GenAI asked for TRT-RTX
without TensorRT, the Vulkan prune left 1.8 GB of x86-64 in the arm64 image on
a non-amd64 builder, and the arm64 gtk4 exception now follows the loader rather
than the arch.
A USB camera works too: linux/jetson-webcam/run.sh streams SSDLite
detections from the Orin’s GPU to a browser at the camera’s 30 fps, 13 ms per
inference. torchvision’s own call managed 9 fps: its per-class postprocess spent
182 ms launching kernels. The app replays the network from a CUDA graph and runs
one vectorized NMS, with the same detections.
Not yet: the image’s media layer predates the 2026-09-18 pin wave (ORT 1.29,
GenAI still trt-rtx), and the official PyTorch cu130 wheels warn that they
do not target compute capability 8.7.