Skip to content

Runtime Architecture Check

Every nupkg produced by build_aot.py (or by build_both.py) carries a .supported-rids sidecar manifest at the nupkg root. A deploy consumer can read it before calling CreateWorker to assert the local RID is supported — failing fast with a clear error rather than letting the WM's NativeDllEngine fail at load time with a cryptic "library not found" message. No current script in this repo implements this check (the worked example below is a pattern to adopt, not something already wired into scripts/create_worker.py) — add it to your own deploy tooling if you need it.

The sidecar format

A plain text file at the nupkg root, one RID per line, in build order:

linux-arm64
linux-x64

The order matters: it's the same order the build script processed the RIDs. Consumers may rely on the first entry as a "default" RID.

A worked example

The check is straightforward: fetch the nupkg, read the sidecar, compare against the local RID, fail fast if unsupported — before any CreateWorker call:

def _current_rid() -> str:
    machine = platform.machine().lower()
    system = platform.system().lower()
    arch = {"x86_64": "x64", "amd64": "x64",
            "aarch64": "arm64", "arm64": "arm64"}.get(machine, machine)
    os_ = {"linux": "linux", "darwin": "osx",
           "windows": "win"}.get(system, system)
    return f"{os_}-{arch}"


async def _assert_runtime_supported(worker_url: str) -> None:
    """Fetch the nupkg header and verify the local RID is in
    .supported-rids. Skip if the sidecar is missing (back-compat with
    pre-0.0.8 nupkgs)."""
    try:
        nupkg_bytes = await run_in_executor(urllib.request.urlopen, worker_url)
    except Exception:
        return  # network error — let the WM fail at load time
    with zipfile.ZipFile(io.BytesIO(nupkg_bytes)) as zf:
        if ".supported-rids" not in zf.namelist():
            return
        supported = zf.read(".supported-rids").decode().splitlines()
    current = _current_rid()
    if current not in supported:
        raise UnsupportedWorkerRuntimeError(
            f"Worker nupkg supports {supported}, but you are on {current}. "
            f"Rebuild with --rids {current} or run on a supported host.")

The UnsupportedWorkerRuntimeError is a clear, actionable error. The user sees "supports [linux-arm64, linux-x64], but you are on osx-arm64" and knows what to do.

What happens on each platform

Local RID Supported nupkg RIDs Result
linux-x64 [linux-x64] ✓ pass
linux-x64 [linux-arm64] ✗ fail: "supports [linux-arm64], but you are on linux-x64"
linux-arm64 [linux-arm64, linux-x64] ✓ pass
osx-arm64 [linux-arm64, linux-x64] ✗ fail: "supports [linux-arm64, linux-x64], but you are on osx-arm64"
win-x64 [linux-arm64, linux-x64] ✗ fail: same message
linux-arm64 nupkg without sidecar ✓ pass (back-compat: trust the legacy flow)

Not every AOT nupkg necessarily carries the sidecar (e.g. one built before write_supported_rids existed, or by a build script that predates it) — the check above skips itself when the sidecar is missing, and the consumer falls back to letting the WM's NativeDllEngine fail at load time (preserves back-compat rather than blocking a deploy on a false negative).

Adding the check to a new consumer

The check is a ~30-line function (above). Any consumer that registers a worker with the WM can use it:

async def create_and_start_worker(api, worker_url, ...):
    await _assert_runtime_supported(worker_url)
    # proceed with CreateWorker ...

Or wrap it at the deployment level: a CI step that runs on the deploy target can read the sidecar and fail the deploy with the same UnsupportedWorkerRuntimeError message before the WM is even involved.

The sidecar's role in CI / pre-flight

No worker in this repo currently ships a dedicated AOT smoke test, but the pattern (worth adopting for a worker that needs one) is a local script that asserts:

  1. .supported-rids exists at the nupkg root.
  2. Its contents list exactly the rids that were built, in build order, one per line.

This catches the case where the build script accidentally drops the sidecar (e.g. if a future refactor moves the staging logic and the .supported-rids write is forgotten).

See also

  • Build Script — how the sidecar is written (the write_supported_rids step in build_aot.py)
  • Packaging Modes — the nupkg structure (where .supported-rids lives)