ONNX Runtime has one source: the chain#

Owner rule, 2026-09-23. Every component that compiles against, links or loads ONNX Runtime uses the ORT this repository builds from source. Nothing downloads, restores or installs a second one: no NuGet package, no PyPI wheel, no release zip, no apt package, no copy bundled inside another project, no pyke build. There are no exceptions, and a plugin EP counts as ORT.

This page owns the mechanism: which consumers exist, how each one reaches the chain ORT, which guard proves it, and what each guard cannot see. The rule itself, as an agent must keep it, is windows-build-invariants.md § ONNX Runtime has exactly one source for Windows and AGENTS.md § Linux Build Rules for Linux.

What “the chain ORT” is#

Windows

Linux

Built by

windows/scripts/build/Build-OnnxFromSource.ps1

linux/scripts/03-media/build/onnxruntime/

Installed at

C:\runtime\lib\onnxruntime-source (ONNX_ROOT): bin\onnxruntime.dll, lib\onnxruntime.lib, flat headers in include\onnxruntime\

/usr/local/lib/onnxruntime-cpu on every variant, plus /usr/local/lib/onnxruntime-gpu on the GPU variants

Python wheel

C:\runtime\wheels (PYTHON_WHEELS)

/opt/wheels at build time; the installed flavour is kept in /opt/onnxruntime-wheels (ORT_CHAIN_WHEEL_DIR). Its cp3XYt twin, built natively from the same tree, ships in /opt/wheels-free-threaded (consumer-image-contract.md § The free-threaded wheels)

Source root compiled into it

C:\temp\onnx-src

/opt/onnxruntime (/opt/onnxruntime-android for Android)

The last row is the fingerprint every guard uses. ORT compiles its source paths into its binaries through __FILE__, so the chain’s DLL carries C:\temp\onnx-src\onnxruntime\core\... and its .so carries /opt/onnxruntime/onnxruntime/core/.... Measured on 2026-09-23: Windows ML’s C:\Windows\System32\onnxruntime.dll (1.17) carries C:\__w\1\s\onnxruntime\..., and the PyPI GPU wheel carries N:\_work\1\s. Two consequences:

  • Moving either source directory, or adding -ffile-prefix-map / -fmacro-prefix-map to the ORT build, breaks every guard and every consumer repo at once. They fail closed, but they all fail.

  • A version is not provenance. PyPI publishes onnxruntime 1.31.0 for cp314 too, without DirectML, so a name-and-version check cannot tell it from the chain wheel. The guards compare bytes.

The census (G1, G6) reads a whole source-file path, not the directory (since 2026-09-24). A fingerprint is onnxruntime/core/ or onnxruntime/contrib_ops/, then a path ending in .cc, .cpp, .cxx, .c, .h, .hpp, .inc, .cu or .cuh, then a NUL byte. That is the shape __FILE__ leaves in a binary. Before, the directory alone counted, and that failed OmniAccelerANT’s Linux lane (run 35928030957, x64 and arm64):

  • OxidANT’s loader (ort_runtime.rs) keeps /opt/onnxruntime/onnxruntime/core/ as a string, because it looks for that fingerprint in the ORT file it loads. The Windows build keeps C:\temp\onnx-src\onnxruntime\core\.

  • rustc stores string literals back to back, with no NUL between them. The census read the text before the directory, ...invalid Once state, as a relative build root and called liboxidant.so an ORT build without a fingerprint: UNPROVEN. On Windows the same string would have made oxidant.dll STALE.

  • Now such a file is an importer. G6 checks that its ld.so lookup lands on the chain ORT, a check it used to skip for it.

  • G1 had the same fault. FFmpeg compiles its configure line into every library and tool, and the chain’s line holds -I/usr/local/lib/onnxruntime-cpu/include/onnxruntime/core/session, so every FFmpeg ELF counted as an ORT build with no fingerprint. Now libavfilter is the ffmpeg consumer in the contract, and the rest are not ORT.

Nothing real was lost. Measured on 2026-09-24, old rule against new:

  • in a local :latest-cross (e8eb8a42, ORT 1.29.0), the chain libonnxruntime.so keeps all 592 of its fingerprints, libonnxruntime_providers_dnnl.so 23 of 23, the Android build 545 of 545;

  • on a Windows host, a chain onnxruntime.dll (a consumer’s runner copy, root C:\temp\onnx-src) keeps 608 of 608, and Windows ML’s in-box 1.17 in System32 518 of 518.

The PyPI and DirectML wheels and the WebGPU EP were not measured.

The rule for a consumer: it may name the chain directory, but it must never embed a whole ORT source-file path. A file that does is counted as an ORT build and fails the census as STALE or FOREIGN, so the mistake fails closed.

The same shape binds a consumer’s test fixtures. A fake ORT written as "$chainSrc OrtGetApiBase" (a space after the .cc) has no fingerprint under this rule. A chain fixture then no longer ties to the chain root, and a stale one reads UNPROVEN where the test expects STALE. End the fake path with a NUL, "$chainSrc`0OrtGetApiBase": that passes the census before and after the change, so it can land ahead of the pin bump. Three Windows ORT suites needed it: OmniAccelerANT’s OrtRunner.Tests.ps1 (2 of 6 failed at the fix), OxidANT’s OrtPayload.Tests.ps1 and AccelerANTgine’s OrtBundle.Tests.ps1 (one case each).

An ORT under another name#

A fingerprint and a name are not the only way to recognise an ORT. Every ORT build defines its entry point, OrtGetApiBase, and a consumer only imports it, or looks it up with dlsym. So since 2026-09-24 the census also counts a file that defines it as an ORT instance, whatever the file is called:

  • Linux (ort_census_probe.py, elf_defines): the ELF’s dynamic symbol table holds OrtGetApiBase with a section index (not SHN_UNDEF). The probe finds the symbol through DT_GNU_HASH, or DT_HASH when there is no GNU table, the same lookup ld.so makes. Its BIN line ends in def.

  • Windows (WindowsOrtProvenance.Common, through Get-PeExportNames in WindowsTargetArch.Common): the PE’s export table names OrtGetApiBase, and not as a forwarder. A forwarder (onnxruntime.OrtGetApiBase) runs onnxruntime.dll’s code, so it stays an importer and G6 resolves it.

Such a file is then graded by its bytes like any other ORT instance. With no fingerprint and no chain match it is UNPROVEN (“an ORT under another name”). Before, it was an importer, and an $ORIGIN RUNPATH next to the chain ORT was enough for G6 to pass it. OmniAccelerANT’s packer gives exactly that RUNPATH to every ELF that holds OrtGetApiBase. The narrower fingerprint of 2026-09-24 had widened the hole a little: an ORT whose paths lost their NUL passed too.

Measured on 2026-09-24 in a local :latest-cross (e8eb8a42). The image’s chain ORT was renamed to libhelper.so, given an $ORIGIN RUNPATH and put beside the chain ORT, in two forms. In the first, every ORT directory string is overwritten. In the second, the directories are kept and the NUL after each path is broken. Both have one byte flipped. The fix branch before this change passed both (ORT census PASS), and now both are UNPROVEN.

Nothing real moved. In a whole-image census of that image, no file changed its verdict, apart from the scratch copies above. On real files:

  • the chain libonnxruntime.so defines the symbol, found through both hash tables;

  • libAccelerANTgine.so and liboxidant.so do not;

  • the 2026-09-17 OmniAccelerANT release bundle still passes.

On Windows, the chain onnxruntime.dll and Windows ML’s copy both export it, and oxidant.dll and the 80-odd GStreamer and runtime DLLs of a runner do not.

Not covered: an ORT that is renamed, stripped of its fingerprints and rebuilt so it no longer exports OrtGetApiBase. That is deliberate evasion, not an accident this gate exists for. Archive members are still found by name and by fingerprint only, because nothing loads them from inside the archive.

What was wrong before 2026-09-23#

  • Windows OpenCV downloaded onnxruntime-win-x64-1.25.1.zip at configure time (-win-arm64- on the cross lane), with no pin and no SHA256, and linked dnn and G-API against it. The shipped image still looked clean: dnn’s install glob reads bin\, and the zip keeps its DLLs in lib\.

  • Windows GenAI compiled and linked against Microsoft.ML.OnnxRuntime.DirectML 1.24.4, which ortlib.cmake restores from the aiinfra ORT-Nightly feed with no hash whenever ORT_HOME is unset.

  • The rocm venv carried PyPI’s onnxruntime-ep-webgpu 0.4.0.

  • The app venv reconcile uninstalled fixed name lists and missed -webgpu, -rocm, -qnn, -openvino, -training and -winml. On Linux a missing chain wheel fell back to the app lock’s PyPI 1.27.0 without a word.

  • The hub’s Windows consumer API (WindowsOnnx.Common) installed NuGet ORT, and WindowsMediaRuntime.Common staged only NuGet runtimes\<rid>\native layouts.

  • Linux held the rule by accident: OpenCV built without ORT when the chain was missing; dnn’s install copy in /opt/opencv5/lib won the ld.so lookup; an apt soname repair could pull libonnxruntime1.x (it did, on 2026-08-27); FFmpeg fell back to any vendor libonnxruntime.pc.

  • Consumer CI venvs synced inside our images tested PyPI ORT.

  • Consumer repos: OxidANT statically linked pyke’s ORT 1.28.0; AccelerANTgine’s CMake searched C:/onnxruntime, Program Files, vcpkg, /usr and pkg-config; OmniAccelerANT could load System32’s Windows ML copy.

Every consumer, and how it reaches the chain ORT#

Consumer

Lane

How it gets the chain ORT

Its own gate, then G2

OpenCV dnn + G-API

Windows, every lane incl. the arm64 cross

§ OpenCV on Windows below

Get-OpencvOrtConfigureFinding

OpenCV

Linux, both passes

opencv-ort.sh: every ORT CMake input explicit (HAVE_ONNXRUNTIME=1, both DOWNLOAD_* off, ONNXRUNTIME_PREFER_STATIC=OFF, the CMake packages disabled, the version read from the chain library). A missing chain stops the build. dnn’s install copies in /opt/opencv5/lib become links to the chain file

opencv_ort_assert_configure, opencv_ort_assert_installed

GenAI

Windows

an ORT_HOME shim at <src>\ort-home with the chain’s headers, import lib and DLL; FETCHCONTENT_SOURCE_DIR_ORTLIB and ..._ONNXRUNTIME point at an empty dir, so any fallback fails configure

Get-GenaiOrtConfigureFinding; Get-GenaiOrtTreeFinding reaches G2 through -Finding

GenAI

Linux

60-build-genai.sh --ort_home <chain>

linux/scripts/03-media/verify-genai-ort.sh in the genai verify RUN

FFmpeg

Windows

the chain headers in compat\onnx, the chain import lib

G2 reads config.mak and config.log

FFmpeg

Linux

the chain’s -L goes ahead of every other --extra-ldflags; a missing chain, or a probe that cannot link it, stops the build

ffmpeg_ort_link_findings reads ffbuild/config.mak the way ld resolves it

GStreamer onnx plugin

Windows (merge stage)

the merged ONNX_ROOT

G2 reads build.ninja, intro-dependencies.json and the meson log

GStreamer onnx plugin

Linux

the chain .pc

gst_onnx_ort_findings reads build.ninja

AMD GPU plugin EP (migraphx-ep.dll)

Windows rocm spike

built against the image’s ORT

G2 in phase 5

WebGPU EP

Windows rocm spike

built in-tree into the chain onnxruntime.dll, not as a plugin

configure and wheel gates in Build-OnnxFromSource.ps1 (windows-rocm.md § ONNX Runtime WebGPU EP)

App venv (OrchestrANT)

both

purge every ORT distribution the census names, force-install the chain wheels, then the census check

ort-venv-census.py

Consumer CI venvs

both, inside our images

uv_reconcile_chain_ort / Sync-UvChainOnnxRuntime

census check plus an import proof (python-ci.md § Trap 3)

Rust ort / ort-sys built in our images

both

G3: the image ENV names the chain ORT

Test-Container § 19, contract row ort-crate-env

WindowsOnnx.Common, WindowsMediaRuntime.Common (AccelerANTgine’s lane)

Windows

Get-OnnxChainLayout; NuGet ORT is refused

WindowsOnnx.Common.Tests.ps1, WindowsMediaRuntime.Common.Tests.ps1

apt and ld.so

Linux

the soname deny is in code; 000-onnxruntime.conf sorts ahead of 000-opencv.conf

ort-runtime-gate.sh, in validate-media-runtime.sh and configure-runtime.sh

FFmpeg on Linux stops; it never builds without ORT. ffmpeg_probe_libonnxruntime exits when the chain is missing or its synthesized-.pc probe cannot compile and link it; before 2026-09-23 it skipped the backend. The probe runs after _ffmpeg_cross_args, with the compiler and cross search directories FFmpeg’s configure gets, and the ORT stage leaves what it reads on every arch by the same code: the flat include/onnxruntime_c_api.h (copy_onnx_headers_to_output) and the lib/libonnxruntime.so link (ensure_onnxruntime_symlink). So it fails only where FFmpeg’s own require libonnxruntime would. The skip would stop the build anyway, one step later: ffmpeg_ort_link_findings requires CONFIG_LIBONNXRUNTIME=yes.

OpenCV on Windows: the shim, the hook and the gate#

OpenCV 5.0.0 had two defects on every Windows lane:

  • modules/dnn/CMakeLists.txt:141 tests HAVE_ONNXRUNTIME, which FindONNX never sets. dnn then downloaded its own ORT, FORCE-rewrote ONNXRT_ROOT_DIR (:279) and re-ran FindONNX (:287). Evidence: out/build-logs/rebuild-push-amd64-20260922-034439.log lines 307666, 308144 and 308502.

  • G-API’s dml_ep.cpp compiled to a stub that throws. FindONNX.cmake:93 looks for include/onnxruntime/core/providers/dml/, but ORT (1.30 and 1.31 alike) installs dml_provider_factory.h flat into include/onnxruntime/.

Build-OpencvFromSource.ps1 fixes both without a source patch:

  • The shim. New-OpencvOrtNestedInclude rebuilds ORT’s source-tree header layout at C:\temp\opencv-src\ort-nested. It throws when the chain lacks onnxruntime_cxx_api.h or dml_provider_factory.h.

  • The arguments, the same on every lane (Get-OpencvOrtCmakeArgs): the shim as ONNXRT_ROOT_DIR, the chain’s lib as CMAKE_LIBRARY_PATH, -DHAVE_ONNXRUNTIME=ON, the chain version, and CMAKE_DISABLE_FIND_PACKAGE_onnxruntime / _ONNXRuntime. The config packages stay off because the chain’s onnxruntimeConfig.cmake names the DLL as its link library.

  • The hook. windows/scripts/patches/opencv/cmake-hooks/POST_CREATE_MODULE_LIBRARY_opencv_gapi.cmake is loaded by OpenCV itself through OPENCV_CMAKE_HOOKS_DIR. It gives opencv_gapi /DELAYLOAD for dxcore.dll, d3d12.dll, dxgi.dll and DirectML.dll, the set ORT delay-loads. Without it cv2 would fail to load wherever dxcore.dll is missing (Windows Server 2019). Only hook files may live in that directory: OpenCV registers every *.cmake in it.

  • The gate. Get-OpencvOrtConfigureFinding reads the configure log, CMakeCache.txt and build.ninja. It stops the stage on a download line, a summary other than ONNX Runtime: YES (ver <chain>), a cache entry naming 3rdparty/onnxruntime, a dml_ep.cpp compile without HAVE_ONNX_DML, any HAVE_ONNX_COREML define, or a gapi link without the four /DELAYLOADs.

What changes in the shipped bytes on every lane: opencv_dnn500.dll and opencv_gapi500.dll compile against the chain’s headers (1.31 since 2026-10-13) and need an onnxruntime.dll of that version or later. dml_ep.cpp is compiled with HAVE_ONNX_DML instead of the stub that throws; no DirectML session has run yet. On the arm64 cross lane opencv_gapi500.dll is the first PE that imports dxcore.dll. servercore:ltsc2025 carries it (measured 2026-09-23 at the image’s own OS build), so the merge stage’s import walk resolves it. A future base without it fails that walk; the fix is a measured -ClientOsPattern entry, never dropping the delay-load.

The guards#

The six guards come in two orthogonal halves plus reach. G1 judges the shipped bytes and G2 judges the build inputs. Neither is enough alone: G1 would pass the pre-2026-09-23 Windows OpenCV image, and G2 cannot see a consumer nobody registered. The STAMP verdict joins them: every consumer G1 finds in an image must carry a stamp G2 wrote against this image’s chain ORT.

Guard

What it proves

Where

What it does NOT cover

G1 ORT census

every ORT binary in the image is the chain’s, byte for byte, and every importer resolves to it

Windows: WindowsOrtProvenance.Common.psm1, Test-Container § 25. Linux: check-ort-provenance.sh + ort_census_probe.py, smoke SHIPPED-TRUTH E (cross-build-verification.md § E)

header-only provenance; files the image user cannot read; which file a dlopen by absolute path picks

G2 build gate + stamp

no foreign ORT in a consumer’s tree, fetch caches, build records or logs; a pass writes the stamp

Windows: Assert-ChainOrtOnly in WindowsOrtProvenance.Build.psm1. Linux: ort_assert_chain_only in linux/scripts/03-media/ort-provenance.sh

an ORT under a non-ORT file name; what loads at run time; pip’s pre-23.3 HTTP cache; on Linux the bytes in uv’s cache, which is graded by where uv fetched a wheel (§ The fetch caches G2 grades)

G3 fetch-proof crate env

an ort-sys build in our images links the chain ORT and cannot download pyke’s

windows/Dockerfile, linux/Dockerfile.package; asserted by Test-Container § 19 and the ort-crate-env contract row

Rust builds outside our images; a consumer that overrides the env

G4 static denylist

no script, Dockerfile or workflow re-arms a download, a NuGet/PyPI/apt ORT, a download-binaries crate or a re-arming env value; nothing deletes or patches an in-box ORT under C:\Windows; the G1/G2/G3 wiring stays in place

fix11_ort_single_source_2026_09 in linux/scripts/verify-critical-fixes.sh (preflight slug critical-fixes)

an upstream bump that fetches by itself (G2/G1 catch it); names built at run time; a verb and an ORT token on different lines; docker run -e; a Cargo.toml outside this repo

G5 documented invariant

the next agent knows the rule

windows-build-invariants.md, AGENTS.md, this page

nothing mechanical, except that fix11 greps the invariant heading

G6 consumer bundle census

a shipped app carries the chain ORT of the image it was built in, and every importer finds it

Test-OrtProvenanceTree -Root <dir> (Windows), check-ort-provenance.sh <dir> (Linux)

which copy a bare host really loads beyond the modelled loader order

G2 runs at the end of every consumer build, after the consumer’s own gate and before its tree is removed:

Lane

Call sites

Stamp

Windows

Build-OpencvFromSource.ps1, Build-OnnxGenaiFromSource.ps1, Build-FfmpegFromSource.ps1, Build-GstreamerFromSource.ps1, Build-OrtAmdgpuEpFromSource.ps1

C:\runtime\share\ort-provenance\<consumer>.json

Linux

build-opencv.sh (both passes), build-ffmpeg.sh, build-gstreamer-monorepo.sh, verify-genai-ort.sh

<prefix>/ort-provenance/<consumer>.json under /opt/opencv5, /opt/ffmpeg, /opt/gstreamer, /usr/local/lib/onnxruntime-genai

A stamp names the sha256 of the chain core library it was gated against. A failing rerun deletes the old stamp first. G1 arms its STAMP verdict once the G2 code is present, and an image built before G2 therefore fails STAMP until its consumer stages are rebuilt.

The chain wheel manifest follows the wheel (Linux). The runtime image never holds the chain wheel, so 03-media/runtime/collect-artifacts.sh hashes its native members into <prefix>/ort-provenance.sha256 for G1. The same RUN then runs repair-wheels.sh, which on a cross build strips and repacks every wheel (and natively may let auditwheel rewrite one), and the venvs install those bytes. So repair-wheels.sh proves every manifest row against /opt/wheels before it rewrites anything (ort_manifest_rows check) and re-points each row at the rewritten wheel of the same name, platform tag aside, afterwards (follow). A wheel under the chain wheel’s name with other bytes, or a second wheel of that name, platform tag aside, before or after the rewrite, stops the media stage: the retag would rename such a twin onto the chain wheel. A wheel the manifest never listed never enters it. The cp3XYt twins have their own manifest, <prefix>/ort-provenance-free-threaded.sha256, which repair-wheels.sh --free-threaded proves and follows against /opt/wheels-free-threaded the same way. G1 reads both, and accepts a chain member of an ORT twin in that store.

Placement is a cache rule. Both G2 files import or source nothing, and each is mounted per file into exactly its consumer RUNs: C:\bkmnt\ortmods\ on Windows, /opt/scripts/03-media/ort-provenance.sh on Linux. They stay out of the census module, out of buildmods, migraphxmods, WindowsSourceBuild.Common and Build-MediaCoreAll.ps1, and out of 03-media/core/ and 03-media/runtime/, which whole images copy. Otherwise one edit would re-key five compile RUNs. fix11 enforces the mounts.

How G4 runs: one judging pass#

fix11 reads its scan set once into a corpus (<path>:<line>:<text>, comment lines dropped). The fix11_ort_* checks do not grep it; they QUEUE rules, and _f11_judge answers every rule in one awk pass, a verdict per rule in queue order:

Helper

Rule

FAIL names

_f11_deny <what> <re> [re2 re3 not path-in path-out]

no live line matches

the first five lines

_f11_require <path> <re> <want> <what> [not]

N (exactly) or N+ (at least) lines of one file match; a ^-path is a path ERE

the count

_f11_pair <what> <re> <re-b>

every file with a line matching <re> also has one matching <re-b>

the files

_f11_count <key> <path> <re>

no verdict: the count lands in _F11_N[<key>] for a bash check that runs after the pass

—

Until 2026-09-24 each rule was its own awk run over the corpus, about 50 per gate run. test-critical-fixes.sh runs fix11 once per knocked-out row, so the suite went from 4.4 s to 45-59 s on the CI runner, and the 68 mutation entries that re-run it pushed the preflight job past its 45-minute timeout.

Why one pass is written the way it is. gawk (the runner’s awk) caches a dynamic regex per call site and recompiles it whenever the site sees a different one. So the judge loops rules outside and lines inside: each rule’s regexes are compiled once. _f11_env_writes had one match() site shared by seven regexes, recompiled seven times per line: 22 s of the runner’s 32 s real-tree run. Each form now has its own site, and lines naming no ort-sys variable are skipped first. Measured 2026-09-24 in the CI-parity container, real tree: gawk 10.1 s → 0.7 s, mawk 1.4 s → 0.4 s.

A pass that dies is a FAIL. If the judge returns fewer verdicts than rules, fix11 fails (the judging pass broke); otherwise a broken awk would pass every rule it was handed.

The fetch caches G2 grades#

Every G2 call also grades the fetch caches of the machine it runs on: Get-OrtGateDefaultCache on Windows, ort_gate_default_caches on Linux. Each root is read at its tool’s override variable, else at the tool’s default.

Cache

Windows

Linux

What fails

pyke’s ORT download (ort.pyke.io)

under %LOCALAPPDATA%

under $XDG_CACHE_HOME, else ~/.cache

any file

pip (PIP_CACHE_DIR)

yes

yes

an HTTP body that is a wheel with a top-level onnxruntime/ package; a body or directory it cannot read; an ORT-named file

uv (UV_CACHE_DIR)

yes

yes

Windows: an ORT file whose bytes are not the chain’s. Linux: an ORT wheel uv fetched over HTTP

NuGet (NUGET_PACKAGES)

yes

yes

an ORT-named file or archive, a microsoft.ml.onnxruntime* directory (GenAI’s is not ORT)

cargo’s registry and git (CARGO_HOME)

no

yes

the same

FFmpeg’s SDK cache (FFMPEG_SDK_CACHE)

no

yes

the same

Linux grades uv by source, not by bytes. There uv and pip are BuildKit cache mounts that outlive every build, so uv’s cache holds the unpacked chain wheels of earlier builds, whose bytes are not this build’s. uv records a local wheel (/opt/wheels, a --find-links directory) with a .rev entry and one it downloaded with .http, under wheels-v*/<bucket>/<package>/; only the .http kind can be foreign. A PyPI ORT that any earlier build downloaded into one of these mounts fails every later G2 call until a RUN with the same mount removes it (uv cache clean onnxruntime, or the pip body the finding names). That is fail-closed on purpose.

Not graded: ccache and sccache (object files, no ORT names) and apt’s archive, which the apt-plan and dpkg gates own. test-ort-provenance-build.sh fails when any other cache mount of a Linux G2 RUN is missing from the default list. GenAI’s G2 runs in its own RUN after the build RUN, so that RUN mounts the build RUN’s uv and pip caches under the same ids; the same test fails when one is missing.

The venv census. linux/scripts/03-media/runtime/ort-venv-census.py is the one classifier for Python environments, on both lanes. It treats as ORT every distribution named onnxruntime or onnxruntime-* and every owner of the onnxruntime, onnxruntime_genai or onnxruntime_extensions package, never a fixed list. --check --store DIR requires each of them to match the store wheel of the same name and version file by file, onnxruntime to have exactly one owner, and the import to resolve to it. Build-TorchApp.ps1 embeds a verbatim copy, pinned by TorchApp.OrtCensus.Tests.ps1.

The app uv sync installs no ORT. On Windows every onnxruntime or onnxruntime-* name in uv.lock goes to --no-install-package, and a locked runtime or GenAI flavour with no chain wheel of its family stops the stage. Linux skips onnxruntime and onnxruntime-genai when a chain wheel covers them.

The in-box ONNX Runtime (Windows ML)#

Windows 11 ships an ORT with the OS. This client host carries Windows ML’s C:\Windows\System32\onnxruntime.dll 1.17, a SysWOW64 twin, and copies under Edge WebView and the Windows App Runtime. The images carry none. A one-RUN probe over bk-windows-base on 2026-09-23 (servercore:ltsc2025 at the WINDOWS_BASE_DIGEST pin, OS build 26100.33438) found no onnxruntime*, Windows.AI.MachineLearning*.dll, Microsoft.AI.MachineLearning*.dll or DirectML*.dll anywhere on C:\, the VS Build Tools included. The only name hits were vcpkg’s port records C:\vcpkg\versions\o-\onnxruntime*.json, which are not binaries.

Smoke § 25 asserts this on every amd64 lane, because the standard LoadLibrary search puts System32 ahead of PATH: an in-box copy would take every importer that reaches the chain ORT through PATH. The INBOX verdict fails on any ORT instance under the Windows directory, and on Windows ML’s API DLL, which carries the ORT ABI under another name. No $ortCensusExemption entry can waive an in-box path. The cross lane skips the assertion: the image’s Windows directory is not the device’s, and G6 already assumes a client System32 copy.

If a base bump brings one, keep the previous WINDOWS_BASE_DIGEST in versions.env. Do not exempt it, and never delete or patch files under C:\Windows in a layer; fix11 refuses a script line doing so to an ORT or Windows ML file. Staging the chain DLL beside each exe does not clear the census today. Image mode searches only an importer’s own directory before System32, so a plugin still resolves to the in-box copy (UNRESOLVED). A chain copy outside the prefix and the wheel store is ELSEWHERE. The in-box file keeps its bytes verdict (FOREIGN for a Windows ML build), which no exemption can waive. Admitting such a base is a deliberate census change on three fronts: exe-directory chain copies as a byte-checked allowed home, image mode modelling the host exes’ directories, and a decision on the in-box bytes verdict.

Adding a new ORT consumer#

  1. Build it against the chain. Headers and import library come from ONNX_ROOT or the chain prefix. Close every fetch branch its build system has: a pre-set cache variable, an ORT_HOME, a FETCHCONTENT_SOURCE_DIR_* pointed at an empty directory, disabled CMake package lookups.

  2. Give it its own gate when its build system has a known fetch branch, as OpenCV and GenAI do. Throw on any finding.

  3. Call G2 once, after the build and before the tree is removed. Pass at least one log and every build record; a call without a log is a finding.

  4. Mount the G2 file per file into that one RUN. Never widen a shared closure for it.

  5. Register it in Get-OrtConsumerContract (Windows) or ort_census_contract (Linux), with its stamp path. Anything carrying the ORT ABI outside the contract is UNREGISTERED.

  6. Add its row to F11_GATE_CALLS in verify-critical-fixes.sh. Renaming a gate function later means editing that table in the same change.

  7. Python: its wheel goes into the chain store, and the census must pass. Never a PyPI ORT, never a prebuilt plugin-EP wheel.

  8. Run-time loading: resolve the chain file by absolute path. A bare-name load on Windows reaches System32’s Windows ML copy first. Windows packaging: stage the chain files beside the exe with Copy-ChainOrtBeside and prove what ships with New-OrtProvenPayload or Assert-ChainOrtTree (the shared glue), never by copying ORT by hand.

  9. Licences: a docs/deps/deps.json row for anything it ships beside the chain ORT.

  10. Mutation-test it: a suite that goes red when its check is removed, and a docs/scripts/mutations.json entry on Linux.

The shared Windows glue#

windows/scripts/modules/WindowsOrtPayload.Common.psm1, a consumer-side module (no image stage loads it). OxidANT, OmniAccelerANT and AccelerANTgine each carried it under their own names until 2026-09-25.

  • Copy-ChainOrtBeside -OnnxRoot <root> -Destination <dir> replaces every ORT-family DLL at the top of the directory with the chain install’s: the core, the provider bridge and DirectML.dll. -All copies every DLL of the chain’s lib\ and bin\ instead, EP sidecars included (QNN’s backends, WebGPU’s DXC). The install is Get-OnnxChainLayout’s, so an unset ONNX_ROOT, a NuGet tree and another arch’s onnxruntime.dll all throw before anything is copied.

  • Assert-ChainOrtTree -Root <tree> is G6 over the tree, plus three checks G6 does not make, in -OrtDirectory, where the exe loads ORT from: onnxruntime.dll missing there (MISSING), an ORT-family name the chain has not got (STRAY), and a DirectML.dll with other bytes than the chain’s (CHANGED). The chain is ORT’s prefix plus, when ONNX_GENAI_ROOT names it, the chain GenAI install, whose onnxruntime-genai.dll the media runtime stages beside ORT. -WaiveUnresolved is for a Python package whose __init__ registers that directory with os.add_dll_directory. G6 models an exe’s loader, not that call, so only its UNRESOLVED verdicts are waived.

  • New-OrtProvenPayload -ExePath <exe> -Destination <dir> copies the exe, the DLLs beside it and each -IncludeDirectory tree (a GStreamer plugin tree) into a fresh directory and proves it there. A package that ships from that directory ships the bytes proved. An exe that loads no ORT, directly or through a DLL it ships, must carry none (UNEXPECTED).

What stays in each consumer is its layout: OmniAccelerANT’s runner stamp, which a host re-proves at launch, and AccelerANTgine’s install tree and Python package.

The consumer repositories#

These changes sit in the consumers’ working trees. Their gitlinks move only after each repo commits and pushes, and each repo’s own AGENTS.md and CHANGELOG.md carry the detail.

  • OxidANT. Every onnxruntime* feature is ort/load-dynamic + api-24, declared on the workspace dependency, and download-binaries is gone. kataglyphis_inference::ort_runtime::ensure_ort_loaded() loads an absolute path only, from ORT_DYLIB_PATH, the exe directory, ONNX_ROOT\bin / ORT_LIB_LOCATION or the image prefix, and refuses a file without the chain source-root fingerprint. scripts/linux/check-ort-chain-only.sh gates Cargo.lock and the resolved feature graph in its Linux lane. The Windows zip, MSIX and MSI ship the chain onnxruntime.dll, onnxruntime_providers_shared.dll and DirectML.dll, staged from ONNX_ROOT\bin; each package’s payload passes G6 first, and counts as loading ORT when its exe or any DLL it ships does.

  • AccelerANTgine. cmake/SystemLibDependencies.cmake REQUIREs ORT from one prefix with NO_DEFAULT_PATH, and stops with FATAL_ERROR unless the runtime library carries the chain fingerprint. Its own Windows lane stages through the hub’s Copy-MediaRuntimeBundle, which is chain-only once its hub pin moves, and runs G6 over each staged bin\ and over the release install tree its NSIS/WiX/ZIP installers pack, into which the CMake file installs the proven onnxruntime.dll; its Python package ships the chain layout only, G6-proved.

  • OmniAccelerANT. Build-Windows.ps1 stages the chain ORT beside the runner exe on every build, after the C:\runtime\bin glob, which no longer copies ORT, and stamps the runner’s G6 pass. Start-Windows.ps1 re-runs G6 on the host against that stamped copy, refuses an unstamped, foreign or stray runner and an ORT_DYLIB_PATH of other bytes, and no longer puts C:\onnxruntime\lib on PATH. The runner’s own chain copy therefore always wins over System32’s. runner\Release, the MSIX copy, is rebuilt from a proven preset on every build and re-proved before msix:create. The Linux bundle takes libonnxruntime* from a fingerprinted chain directory only, and check-bundle-closure.sh runs G6 over it whenever a file is ORT-named or names the ORT ABI. The cat-stream pi-bundle is G6-proved in the image run that assembles it.

  • OrchestrANT drops its Python 3.13 test legs (owner decision 2026-09-23): the chain wheels are cp314 and the images carry CPython 3.14 only. The hub’s python-ci defaults (PY_VERSIONS, COVERAGE_VERSION) moved to 3.14 with it.

Two couplings to keep in mind:

  • The consumers’ packaging proof is this repo’s G6, so it follows a move of C:\temp\onnx-src or /opt/onnxruntime (Get-OrtChainSourceRoot, ort_census_chain_roots). Literal copies of the fingerprint remain in their source pickers: AccelerANTgine’s CMake, OmniAccelerANT’s rust_builder/linux/CMakeLists.txt and bundle-runtime.sh, and OxidANT’s ort_runtime.rs.

  • Both images bake ORT_DYLIB_PATH (G3). Inside an image, OxidANT treats it as the only candidate, so an app’s own staged copy is not the one loaded there, and a GPU consumer must point it at onnxruntime-gpu.

Not proven by a build yet#

One lane has reached part of it: on 2026-09-25 the Windows rocm lane’s AMDGPU EP stage ran G2 and hit the third red below, on the EP’s own intermediate .tar (fixed the same day, windows-rocm.md). For every other lane the first run is still the real test. The likeliest first reds, all fail-closed:

  • GenAI and OpenCV’s G-API DirectML EP compiling against the 1.31 headers under clang-cl for the first time.

  • A distro package in the Linux media closure that depends on libonnxruntime1.x (Ubuntu’s gstreamer1.0-plugins-bad does).

  • A G2 false positive on a real record path; the finding names the record and the path.

  • A PyPI ORT that an earlier build left in a Linux uv or pip cache mount, now that G2 grades those mounts.

Failure messages#

You see

Where

Meaning

ORT gate (<consumer>): N finding(s), the build reached an ONNX Runtime other than the chain's

Windows, end of a consumer build

G2; each FAIL: line above it names a file, record path or log line

ORT-GATE FAIL (<consumer>): ..., then ORT-GATE FAILED

Linux, end of a consumer build

G2, same rules

uv's cache holds an ONNX Runtime wheel downloaded over HTTP, pip's HTTP cache holds an ONNX Runtime wheel

Linux, G2

a PyPI ORT in a cache mount, left by this build or an earlier one: § The fetch caches G2 grades

ERROR: ORT wheel manifest ...: no wheel in /opt/wheels holds these bytes any more

Linux media runtime RUN, repair-wheels.sh

a wheel under the chain wheel’s name replaced it after collect-artifacts.sh hashed it

ERROR: ORT wheel manifest ...: other wheels share its name, platform tag aside

the same

a second wheel of the chain wheel’s name, version and ABI is in /opt/wheels; the retag or auditwheel would write it over the chain wheel

ERROR: ORT wheel manifest ...: not exactly one rewritten wheel of that name carries it

the same

after the strip and retag two wheels share the chain wheel’s name, or its member is gone

no build record names the chain ONNX Runtime

either lane, G2

the consumer built without ORT at all

ORT census: STAMP ...

smoke

the consumer’s layer predates G2, or ran against another ORT build: rebuild that stage

FOREIGN, STALE, UNPROVEN, ELSEWHERE, UNRESOLVED, UNREGISTERED, DIST, NONE

smoke § 25 / SHIPPED-TRUTH E

G1 verdicts, defined in cross-build-verification.md § E

INBOX

smoke § 25, ORT in-box (Windows amd64)

the base image now ships an ORT in its Windows directory: § The in-box ONNX Runtime

ORT-CENSUS FAIL ...

torch stage

failure-modes.md

uv.lock pins ... has no chain wheel of that family

Windows torch stage, before uv sync

the store lacks that flavour’s chain wheel: a media-stage problem

ERROR: chain ORT: ...

a consumer’s uv sync

python-ci.md § Trap 3

FAIL fix11: ...

preflight critical-fixes

G4 found a re-armed download or a lost wiring row

<id> is an ONNX Runtime package and is refused

Install-OptionalNuGetPackage

the NuGet facility is chain-only