From e32391bdb178ae5291341528b641e3fbd0dd2f04 Mon Sep 17 00:00:00 2001 From: Andrew Mello Date: Thu, 29 Oct 2026 17:32:05 -0700 Subject: [PATCH 1/3] Pin classes, pin-guard, scoped images-lock, tagged refs and tool pins out of workflows pins/classes gives every pin in the repo a class: ours (the default, always the newest stable release), eat-stock (tracks Intel EAT's releases), ledger (a ledger row's runner or records) or historical (a finished run's record). A ledger line is an arm while any of its rows is PENDING in the EXPERIMENTS.md Hypotheses table and historical once every row has a verdict, so a verdict landing needs no reclassification. historical-ref lines hold finished runs' exact pins that live runners keep. hack/pin-classes answers class, effective, excludes and coverage. hack/pin-guard enforces the ledger rules on a diff: a historical record is never modified, deleted or renamed; a historical-ref string keeps its count; an arm's pin tokens change only with a dated amendment heading in the same diff that names a PENDING row of the line or one of its aliases; an autobump branch touches neither .github/ nor pins/classes, and EXPERIMENTS.md only on autobump/amend-*; an unclassified pin-bearing file under bench/ or oracle/ fails. A new ci.yml job, pin-guard, runs it with the tests and hack/pin-classes coverage. scripts/images-lock: repin and the new drift subcommand leave ledger, historical and historical-ref files alone; update --only FILE... resolves and checks only the references those files source and keeps the other entries, so one moved digest elsewhere no longer fails a bump; http() retries 429 and 5xx up to five times and honours Retry-After; pins fails on a digest-only reference in an ours or eat-stock file. The digest-only refs in the launcher, share/ and CI now carry the tag that resolves to the same digest (checked against each registry); no digest changed. Consumers that compare or cache by image (plan cells, tune fingerprints, autoquant cache, oracle bundles and release checks) compare name plus digest, so their behaviour is unchanged. Tool versions the workflows installed inline (Prometheus, kubeconform, Chrome for Testing, jsonschema, helm v4, shellcheck, packaging) move to hack/ci-tools.env, which each job loads into GITHUB_ENV, so a token without the workflows permission can move every pin. --- .github/workflows/build-and-push.yml | 5 +- .github/workflows/ci.yml | 67 +++- CONTRIBUTING.md | 39 +- console/plan.py | 11 +- hack/ci-tools.env | 34 ++ hack/pin-classes | 297 +++++++++++++++ hack/pin-guard | 348 ++++++++++++++++++ images.lock | 37 +- images.unpinned | 13 +- oracle/bundle.py | 9 + oracle/project.py | 3 +- oracle/test_oracle.py | 10 + oracle/validate.py | 6 +- pins/classes | 108 ++++++ scripts/images-lock | 197 ++++++++-- scripts/k3d-gpu | 28 +- share/agent-chat-ui.yml | 2 +- share/telemetry/intel-gpu-exporter.yml | 2 +- tests/agent_kubeconform.sh | 5 +- tests/audit_test.sh | 7 +- tests/chat_ui_test.sh | 6 +- tests/ci_wiring_test.sh | 79 ++++ tests/console/audit/chat-ui-noauth.golden | 4 +- tests/console/audit/chat-ui.golden | 4 +- tests/console/audit/default-json.golden | 6 +- tests/console/audit/default.golden | 4 +- tests/console/audit/image-digest-moved.golden | 4 +- tests/console/audit/image-no-digest.golden | 4 +- tests/console/audit/image-not-in-lock.golden | 4 +- tests/console/audit/ipv6-unauth.golden | 4 +- tests/console/audit/kubeconfig-0644.golden | 4 +- tests/console/audit/loopback-published.golden | 4 +- tests/console/audit/runtime-fetch.golden | 4 +- tests/console/audit/runtime-pull.golden | 4 +- tests/console/audit/unauth-published.golden | 4 +- tests/console/green-report.html | 4 +- tests/console/test_console.py | 11 + tests/console_test.sh | 2 +- tests/detect_test.sh | 18 +- tests/fixtures/alderlake-n/facts.json | 4 +- tests/fixtures/alderlake-n/report.html | 4 +- tests/fixtures/arrowlake-h/facts.json | 4 +- tests/fixtures/arrowlake-h/report.html | 4 +- tests/fixtures/arrowlake-s/facts.json | 4 +- tests/fixtures/arrowlake-s/report.html | 4 +- tests/fixtures/arrowlake-u/facts.json | 4 +- tests/fixtures/arrowlake-u/report.html | 4 +- tests/fixtures/battlemage-b580/facts.json | 4 +- tests/fixtures/battlemage-b580/report.html | 4 +- tests/fixtures/battlemage-pro-b60/facts.json | 4 +- tests/fixtures/battlemage-pro-b60/report.html | 4 +- .../battlemage-pro-b70-k616/facts.json | 4 +- .../battlemage-pro-b70-k616/report.html | 4 +- tests/fixtures/dg2-a770-i915/facts.json | 4 +- tests/fixtures/dg2-a770-i915/report.html | 4 +- tests/fixtures/flex140-unbound/facts.json | 4 +- tests/fixtures/flex140-unbound/report.html | 4 +- tests/fixtures/gaudi3-hls/facts.json | 4 +- tests/fixtures/gaudi3-hls/report.html | 4 +- tests/fixtures/gaudi3-no-driver/facts.json | 4 +- tests/fixtures/gaudi3-no-driver/report.html | 4 +- tests/fixtures/lunarlake-k6.8/facts.json | 4 +- tests/fixtures/lunarlake-k6.8/report.html | 4 +- tests/fixtures/lunarlake/facts.json | 4 +- tests/fixtures/lunarlake/report.html | 4 +- tests/fixtures/meteorlake/facts.json | 4 +- tests/fixtures/meteorlake/report.html | 4 +- tests/fixtures/novalake/facts.json | 4 +- tests/fixtures/novalake/report.html | 4 +- tests/fixtures/npu-unserved/facts.json | 4 +- tests/fixtures/npu-unserved/report.html | 4 +- tests/fixtures/pantherlake-k6.14/facts.json | 4 +- tests/fixtures/pantherlake-k6.14/report.html | 4 +- tests/fixtures/pantherlake/facts.json | 4 +- tests/fixtures/pantherlake/report.html | 4 +- tests/fixtures/pvc-max1550-unbound/facts.json | 4 +- .../fixtures/pvc-max1550-unbound/report.html | 4 +- tests/fixtures/qat-420xx/facts.json | 4 +- tests/fixtures/qat-420xx/report.html | 4 +- tests/fixtures/raptorlake-nvidia/facts.json | 4 +- tests/fixtures/raptorlake-nvidia/report.html | 4 +- .../fixtures/raptorlake-stale-guc/facts.json | 4 +- .../fixtures/raptorlake-stale-guc/report.html | 4 +- tests/fixtures/wildcatlake/facts.json | 4 +- tests/fixtures/wildcatlake/report.html | 4 +- tests/fixtures/xeon-6741p/facts.json | 4 +- tests/fixtures/xeon-6741p/report.html | 4 +- tests/fixtures/xeon-dmr/facts.json | 4 +- tests/fixtures/xeon-dmr/report.html | 4 +- tests/fixtures/xeon-gnr/facts.json | 4 +- tests/fixtures/xeon-gnr/report.html | 4 +- tests/fixtures/xeon-gnrd/facts.json | 4 +- tests/fixtures/xeon-gnrd/report.html | 4 +- tests/fixtures/xeon-max-hbm-cache/facts.json | 4 +- tests/fixtures/xeon-max-hbm-cache/report.html | 4 +- tests/fixtures/xeon-max-hbm-devdax/facts.json | 4 +- .../fixtures/xeon-max-hbm-devdax/report.html | 4 +- tests/fixtures/xeon-max-hbm/facts.json | 4 +- tests/fixtures/xeon-max-hbm/report.html | 4 +- tests/fixtures/xeon-spr-accel/facts.json | 4 +- tests/fixtures/xeon-spr-accel/report.html | 4 +- tests/fixtures/xeon-srf/facts.json | 4 +- tests/fixtures/xeon-srf/report.html | 4 +- tests/image_pins_test.sh | 187 +++++++++- tests/kubeconform.sh | 4 +- tests/pin_classes_test.py | 280 ++++++++++++++ tests/pin_guard_test.sh | 265 +++++++++++++ tests/remote/chat-ui.golden | 2 +- tests/share_kubeconform.sh | 5 +- tests/tune_test.sh | 5 + 110 files changed, 2119 insertions(+), 287 deletions(-) create mode 100644 hack/ci-tools.env create mode 100755 hack/pin-classes create mode 100755 hack/pin-guard create mode 100644 pins/classes create mode 100644 tests/pin_classes_test.py create mode 100755 tests/pin_guard_test.sh diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index 46c75bd8..8286b024 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -643,11 +643,14 @@ jobs: with: ref: main + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" + - name: Move the pins to the newest compatible releases id: bump run: | python3 -m venv "$RUNNER_TEMP/venv" - "$RUNNER_TEMP/venv/bin/pip" install --disable-pip-version-check packaging==26.3 + "$RUNNER_TEMP/venv/bin/pip" install --disable-pip-version-check "packaging==${PACKAGING_VERSION}" "$RUNNER_TEMP/venv/bin/python" hack/bump-convert-pins | tee "$RUNNER_TEMP/bump.txt" # The moved set must resolve together: pip's resolver, no install. mapfile -t pins < <(jq -r '.pins | to_entries[] | "\(.key)==\(.value)"' console/convert-pins.json) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebc42427..71546270 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,11 +20,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" - uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 with: scandir: . severity: warning - version: v0.11.0 + version: ${{ env.SHELLCHECK_VERSION }} actionlint: name: Workflow lint @@ -84,13 +86,10 @@ jobs: telemetry-tests: name: Telemetry manifests runs-on: ubuntu-latest - env: - PROMETHEUS_VERSION: 3.15.1 - PROMETHEUS_SHA256: 6a726885328ad9efebbf36e876ce2fb6225468a71fd1e7ce3ba666d5f4d97a84 - # ghcr.io/yannh/kubeconform:v0.8.0 - KUBECONFORM_IMAGE: ghcr.io/yannh/kubeconform@sha256:faffaf43f95aa6425306e1ab8d6fcad72acb9049158f38e574c085ea1ec0f64e steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" - name: Install promtool run: | tgz="prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz" @@ -132,8 +131,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" - name: Install jsonschema for the schema cross-check (pinned) - run: python3 -m venv /tmp/jsonschema && /tmp/jsonschema/bin/pip install --disable-pip-version-check jsonschema==4.26.0 + run: python3 -m venv /tmp/jsonschema && /tmp/jsonschema/bin/pip install --disable-pip-version-check "jsonschema==${JSONSCHEMA_VERSION}" - env: CONSOLE_JSONSCHEMA_PYTHON: /tmp/jsonschema/bin/python run: tests/console_test.sh @@ -162,10 +163,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" - id: chrome uses: browser-actions/setup-chrome@48ad923757ca74d66703209fe939badbdf80f2f4 # v2.2.0 with: - chrome-version: 156.0.8078.73 + chrome-version: ${{ env.CHROME_VERSION }} # The headless load runs Chrome in a network namespace (unshare -rn); # Ubuntu's AppArmor blocks unprivileged user namespaces by default. - run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 @@ -291,11 +294,10 @@ jobs: chat-ui-manifest: name: Chat UI manifest (static + kubeconform) runs-on: ubuntu-latest - env: - KUBECONFORM_VERSION: v0.8.0 - KUBECONFORM_SHA256: 9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" - name: Install kubeconform run: | curl -fsSL -o kubeconform.tar.gz \ @@ -447,14 +449,13 @@ jobs: kserve-example: name: KServe example (stub install + kubeconform 1.34/1.35) runs-on: ubuntu-latest - env: - KUBECONFORM_VERSION: v0.8.0 - KUBECONFORM_SHA256: 9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Tool versions (hack/ci-tools.env) + run: grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" - uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: - version: v4.3.1 + version: ${{ env.HELM4_VERSION }} - name: Install kubeconform and PyYAML run: | curl -fsSL -o kubeconform.tar.gz \ @@ -483,3 +484,39 @@ jobs: - run: python3 tests/known_issues_test.py - run: python3 tests/ledger_freshness_test.py - run: hack/known-issues check + + # ---- Pin classes (pins/classes) ---- + # A finished run's record is never edited; a PENDING ledger row's pin moves only + # with a dated amendment in the same diff; an autobump branch touches neither + # .github/ nor the ledger (hack/pin-guard). Every pin-bearing file under bench/ and + # oracle/ is classified (hack/pin-classes coverage). A pull request is checked from + # the commit it forked at to its head (HEAD^2 of the merge ref). + pin-guard: + name: Pin classes (ledger and historical pins) + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - run: python3 tests/pin_classes_test.py + - run: tests/pin_guard_test.sh + - name: hack/pin-classes coverage + run: hack/pin-classes coverage + - name: hack/pin-guard + env: + EVENT: ${{ github.event_name }} + BASE_REF: ${{ github.base_ref }} + BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + case "${EVENT}" in + pull_request) base="origin/${BASE_REF}" head=HEAD^2 ;; + push) base=HEAD^1 head=HEAD ;; + *) base=origin/main head=HEAD ;; + esac + args=(--base "${base}" --head "${head}" --branch "${BRANCH}") + if [[ "${BRANCH}" == autobump/* ]]; then args+=(--autobump); fi + hack/pin-guard "${args[@]}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab7bfb9d..07c76adc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,19 +114,32 @@ Include relevant details: (`tests/console/audit/default`), diffed against `tests/console/audit/*.golden` (`tests/audit_test.sh --update` rewrites those). - Changed an image in `share/`, the Dockerfile or an image pin in `scripts/k3d-gpu`? - Run `scripts/images-lock update` (it reads the registries) and commit - `images.lock`; CI's `scripts/images-lock check` fails until you do. -- Every image reference in the repo is pinned `name:tag@sha256:`. Write - the tag, run `scripts/images-lock repin ` (it reads the registries) to add - the digest; `tests/image_pins_test.sh` (`scripts/images-lock pins`) fails on a - tag-only or `:latest` reference that `images.unpinned` does not list with a - reason. A rendered component's image pins live in its `component.conf` - (`IMAGE_PINS`). The nightly `repin-images` job opens a PR when a registry moves - a digest under an unchanged tag. Its token cannot push `.github/workflows/`, so - it runs `repin --exclude '.github/workflows/*'` and a workflow keeps no - `name:tag@sha256` of its own: it reads the pin from the Dockerfile, - `docs-build/Dockerfile` or `hack/ci-images.env` (`tests/image_pins_test.sh` - fails on a pin written in a workflow). + Run `scripts/images-lock update --only ` (it reads the + registries; without `--only` it checks every pinned digest and a digest a registry + moved elsewhere fails it) and commit `images.lock`; CI's `scripts/images-lock + check` fails until you do. `scripts/images-lock drift` lists the pins whose tag a + registry has moved, and writes nothing. +- Every image reference in the repo is pinned `name:tag@sha256:`, the tag + naming the version. Write the tag, run `scripts/images-lock repin ` (it + reads the registries) to add the digest; `tests/image_pins_test.sh` + (`scripts/images-lock pins`) fails on a tag-only, `:latest` or digest-only + reference that `images.unpinned` does not list with a reason. A rendered + component's image pins live in its `component.conf` (`IMAGE_PINS`). The nightly + `repin-images` job opens a PR when a registry moves a digest under an unchanged + tag. Its token cannot push `.github/workflows/`, so it runs `repin --exclude + '.github/workflows/*'` and a workflow keeps no version of its own: it reads image + pins from the Dockerfile, `docs-build/Dockerfile` or `hack/ci-images.env`, and + tool versions from `hack/ci-tools.env` (`tests/image_pins_test.sh` and + `tests/ci_wiring_test.sh` fail on a pin or version written in a workflow). +- `pins/classes` says who may move each pin. `ours` (the default) is k3d-gpu's own + and tracks the newest stable release; `eat-stock` tracks Intel EAT's newest + release; a `ledger` path is a ledger row's runner: while a listed row is PENDING + its pins move only with a dated amendment in the same PR, and once every listed + row has a verdict it is `historical`, a finished run's record that is never edited. + `repin` never touches ledger or historical paths. CI's `pin-guard` job runs + `hack/pin-guard` on every PR (`tests/pin_guard_test.sh` shows each rule) and + `hack/pin-classes coverage`, which fails on a pin-bearing file under `bench/` or + `oracle/` that no line classifies: add one for a new bench directory. - `tests/console_mcp_test.sh` checks that the MCP tools `hardware` and `plan_launch` return the CLI `facts --json` and `plan --json` bytes on every fixture, and that the MCP `apply` tool writes nothing without a plan hash the diff --git a/console/plan.py b/console/plan.py index 4a3dcda9..81d5acd1 100755 --- a/console/plan.py +++ b/console/plan.py @@ -101,6 +101,15 @@ def check_overrides(overrides): return out +def image_id(ref): + """name@sha256: for a ref that carries a digest, its tag dropped (the digest + is the image, so name:tag@sha256:X and name@sha256:X are one pin); else ref.""" + name, at, digest = ref.partition("@") + if at and ":" in name.rsplit("/", 1)[-1]: + name = name.rsplit(":", 1)[0] + return name + at + digest + + def cell_status(row, pins): """("green" | "amber" | "grey", reasons).""" if row["source"] == "community": @@ -116,7 +125,7 @@ def cell_status(row, pins): pin = pins.get(row["engine"]["name"]) if pin is None: reasons.append("this release pins no %s image" % row["engine"]["name"]) - elif pin != row["engine"]["image"]: + elif image_id(pin) != image_id(row["engine"]["image"]): reasons.append("engine pin moved since the measurement (cell %s, release %s)" % (row["engine"]["image"], pin)) return ("grey" if reasons else "green"), reasons diff --git a/hack/ci-tools.env b/hack/ci-tools.env new file mode 100644 index 00000000..bd38eb9a --- /dev/null +++ b/hack/ci-tools.env @@ -0,0 +1,34 @@ +# Tool versions the CI and release workflows install, one KEY=value per line. They +# live here, outside .github/workflows/, so a bump is a change to this file: a token +# without the `workflows` permission can push it, and pins/classes classes it ours. +# A job loads them in its first step after checkout: +# grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV" +# and reads them as ${{ env.KEY }} (action inputs) or $KEY (run steps). +# tests/ci_wiring_test.sh fails on a version literal left in a workflow. + +# prometheus/prometheus: promtool checks the telemetry rules (ci.yml telemetry-tests). +PROMETHEUS_VERSION=3.15.1 +PROMETHEUS_SHA256=6a726885328ad9efebbf36e876ce2fb6225468a71fd1e7ce3ba666d5f4d97a84 + +# yannh/kubeconform: the release binary (chat-ui-manifest, kserve-example) and the +# image (telemetry-tests and tests/kubeconform.sh, agent_kubeconform.sh, +# share_kubeconform.sh), the same release. +KUBECONFORM_VERSION=v0.8.0 +KUBECONFORM_SHA256=9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883 +KUBECONFORM_IMAGE=ghcr.io/yannh/kubeconform:v0.8.0@sha256:faffaf43f95aa6425306e1ab8d6fcad72acb9049158f38e574c085ea1ec0f64e + +# Chrome for Testing: report.html's offline load (report-html). +CHROME_VERSION=156.0.8078.73 + +# python-jsonschema: the facts/v1 schema cross-check (console-tests). +JSONSCHEMA_VERSION=4.26.0 + +# helm v4 for the KServe example's install path (kserve-example). Rendering share/ +# uses the v3 line in hack/tools.env. +HELM4_VERSION=v4.3.1 + +# koalaman/shellcheck, run by ludeeus/action-shellcheck (shellcheck). +SHELLCHECK_VERSION=v0.11.0 + +# pypa/packaging for hack/bump-convert-pins (build-and-push.yml bump-convert-pins). +PACKAGING_VERSION=26.3 diff --git a/hack/pin-classes b/hack/pin-classes new file mode 100755 index 00000000..3aaaa73e --- /dev/null +++ b/hack/pin-classes @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +"""pins/classes: the pin class of every path in the repo. + + hack/pin-classes class PATH the kind of the first pins/classes line that + matches PATH: ours, eat-stock, ledger or historical; + an unmatched path is ours, except a file under bench/ + or oracle/ that holds a pin token: unclassified + hack/pin-classes effective PATH as class, with a ledger line resolved against the + Hypotheses table of EXPERIMENTS.md: arm while any of + its rows is PENDING, historical once a listed row has + a verdict and none is PENDING, ours while no listed + row is in the table yet (a row being pre-registered) + hack/pin-classes excludes the glob of every ledger and historical line + hack/pin-classes coverage fails (exit 1) on a pin-bearing file under bench/ or + oracle/ that no line classifies, a historical-ref + string missing from its file, a ledger line naming a + row the Hypotheses table does not have, and a line + that matches no file + +pins/classes, one line each (# starts a comment line; first match wins; globs are +fnmatch patterns on repo-relative paths, where * also matches /): + + ours|eat-stock|historical [reason] + ledger amend= [reason] + historical-ref + +ours k3d-gpu's own pins: always the latest stable release +eat-stock tracks Intel EAT's latest release; every change is reviewed +ledger a ledger row's runner or records. The rows are the Hypotheses table ids + that use the path; the aliases are the heading prefixes its amendments + use ("### amendment : ...") +historical a finished run's record: never edited +historical-ref an exact string in a mixed file (a finished run's pin inside a live + runner) that must keep occurring as often as it does + +A pin token (pin_tokens) is an image reference (name:tag with a registry path, +name@sha256:..., name:tag@sha256:..., or name:tag after image:, FROM or *IMAGE=), +a bare sha256:<64 hex> digest, pkg==version, a 40-hex string, or an assignment +NAME=value whose NAME ends in TAG, VERSION, REV, REVISION, COMMIT, IMAGE or DIGEST. +Comment lines and *.md files hold none. + +Python standard library only. Options: --root DIR (default: this repo), +--classes FILE (default: ROOT/pins/classes). +""" + +import argparse +import fnmatch +import importlib.util +import os +import pathlib +import re +import subprocess +import sys + +KINDS = ("ours", "eat-stock", "ledger", "historical") +GUARDED_PREFIXES = ("bench/", "oracle/") +CLASSES_FILE = "pins/classes" + +_REPO = r"[a-z0-9][a-z0-9.-]*(?::[0-9]+)?(?:/[a-z0-9][a-z0-9._-]*)+" +_TAG = r"[A-Za-z0-9_][A-Za-z0-9._-]{0,127}" +TOKEN_RES = [re.compile(p) for p in ( + # name[:tag]@sha256:, or a bare tag@sha256: (B1_TAG=v0.28.0@sha256:...) + r"(?[a-z0-9][a-z0-9._/-]*:" + _TAG + r")(?![\w.@/:-])", + # pkg==version, the name with a lower-case letter (awk's NR==2 is no pin) + r"(? '" % (name, n)) + refs.append(Line(n, parts[1], "historical-ref", ref=parts[2].strip())) + continue + if len(parts) < 2 or parts[1] not in KINDS: + raise ValueError("%s:%d: want ' %s [...]'" % (name, n, "|".join(KINDS))) + glob, kind, rest = parts[0], parts[1], parts[2] if len(parts) > 2 else "" + if kind != "ledger": + lines.append(Line(n, glob, kind, reason=rest)) + continue + lp = rest.split(None, 2) + if len(lp) < 2 or not lp[1].startswith("amend="): + raise ValueError("%s:%d: want ' ledger amend= [reason]'" % (name, n)) + rows = [r for r in lp[0].split(",") if r] + aliases = [a for a in lp[1][len("amend="):].split(",") if a] + bad = [r for r in rows if not re.fullmatch(r"H\d+[a-z]{0,2}", r)] + if not rows or bad: + raise ValueError("%s:%d: ledger rows must be Hypotheses table ids like H54q, got %r" % (name, n, lp[0])) + lines.append(Line(n, glob, kind, rows, aliases, lp[2] if len(lp) > 2 else "")) + return lines, refs + + +def load(path): + with open(path, encoding="utf-8") as f: + return parse(f.read(), os.path.relpath(path)) + + +def match(lines, path): + """The first line whose glob matches path, or None.""" + return next((ln for ln in lines if ln.matches(path)), None) + + +def guarded(path): + return path.startswith(GUARDED_PREFIXES) + + +def class_of(lines, path, text=None): + """(kind, line): the matching line's kind; unmatched is ours, or unclassified for + a pin-bearing file under bench/ or oracle/ (text: the file's content).""" + ln = match(lines, path) + if ln: + return ln.kind, ln + if guarded(path) and text is not None and text_tokens(path, text): + return "unclassified", None + return "ours", None + + +def resolve(line, ledger): + """arm, historical or ours for a ledger line under one Hypotheses table.""" + states = [ledger.get(r) for r in line.rows] + if "PENDING" in states: + return "arm" + if any(s for s in states): + return "historical" + return "ours" + + +def effective(lines, ledger, path, text=None): + kind, ln = class_of(lines, path, text) + return (resolve(ln, ledger) if kind == "ledger" else kind), ln + + +def read_ledger(root): + """{"H1": "INCONCLUSIVE", ...}: tests/ledger_drift_check.py's ledger() over ROOT.""" + here = pathlib.Path(__file__).resolve().parent.parent + spec = importlib.util.spec_from_file_location("ledger_drift_check", here / "tests" / "ledger_drift_check.py") + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.ledger(pathlib.Path(root)) + + +def tracked(root): + try: + out = subprocess.run(["git", "-C", root, "ls-files", "-z"], capture_output=True, check=True).stdout + return sorted(p for p in out.decode().split("\0") if p) + except (OSError, subprocess.CalledProcessError): + paths = [] + for d, dirs, files in os.walk(root): + dirs[:] = sorted(x for x in dirs if x != ".git") + paths += [os.path.relpath(os.path.join(d, f), root).replace(os.sep, "/") for f in files] + return sorted(paths) + + +def read_text(root, path): + try: + with open(os.path.join(root, path), encoding="utf-8") as f: + return f.read() + except (UnicodeDecodeError, FileNotFoundError, IsADirectoryError): + return None + + +def coverage(root, lines, refs, ledger): + """Problems, one string each.""" + problems, paths = [], tracked(root) + for path in paths: + if not guarded(path) or match(lines, path): + continue + text = read_text(root, path) + if text is not None and text_tokens(path, text): + problems.append("%s: holds pin tokens (%s) and no %s line classifies it" + % (path, text_tokens(path, text)[0], CLASSES_FILE)) + for r in refs: + text = read_text(root, r.glob) + if text is None: + problems.append("%s:%d: historical-ref file %s does not exist" % (CLASSES_FILE, r.n, r.glob)) + elif r.ref not in text: + problems.append("%s:%d: historical-ref %r is missing from %s" % (CLASSES_FILE, r.n, r.ref, r.glob)) + for ln in lines: + missing = [r for r in ln.rows if r not in ledger] + if missing: + problems.append("%s:%d: %s names %s, not in the EXPERIMENTS.md Hypotheses table" + % (CLASSES_FILE, ln.n, ln.glob, ", ".join(missing))) + if not any(ln.matches(p) for p in paths): + problems.append("%s:%d: %s matches no file; drop it" % (CLASSES_FILE, ln.n, ln.glob)) + return problems + + +def main(argv): + here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + ap = argparse.ArgumentParser(prog="pin-classes", description=__doc__.split("\n")[0]) + ap.add_argument("command", choices=("class", "effective", "excludes", "coverage")) + ap.add_argument("path", nargs="?") + ap.add_argument("--root", default=here) + ap.add_argument("--classes") + a = ap.parse_args(argv[1:]) + if (a.command in ("class", "effective")) != bool(a.path): + ap.error("class and effective take one PATH; excludes and coverage take none") + try: + lines, refs = load(a.classes or os.path.join(a.root, CLASSES_FILE)) + if a.command == "excludes": + for ln in lines: + if ln.kind in ("ledger", "historical"): + print(ln.glob) + return 0 + if a.command == "coverage": + problems = coverage(a.root, lines, refs, read_ledger(a.root)) + for p in problems: + print("FAIL " + p) + if problems: + return 1 + print("ok every pin-bearing file under bench/ and oracle/ is classified in %s; " + "%d historical-ref strings present" % (CLASSES_FILE, len(refs))) + return 0 + path = os.path.normpath(a.path).replace(os.sep, "/") + text = read_text(a.root, path) + if a.command == "class": + print(class_of(lines, path, text)[0]) + else: + print(effective(lines, read_ledger(a.root), path, text)[0]) + return 0 + except (ValueError, OSError) as e: + sys.stderr.write("pin-classes: %s\n" % e) + return 2 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/hack/pin-guard b/hack/pin-guard new file mode 100755 index 00000000..de8646cc --- /dev/null +++ b/hack/pin-guard @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +"""Enforce pins/classes on the diff merge-base(BASE, HEAD)..HEAD. + + hack/pin-guard --base REF [--head REF] [--autobump] [--branch NAME] [--classes FILE] + + a. historical path (a historical line, or a ledger line whose rows all have a + verdict in BASE's Hypotheses table): any modification, deletion or rename fails + b. historical-ref: each string occurs in its file as often as at the merge-base + (a string the merge-base does not hold yet is being recorded, and passes) + c. arm path (a ledger line with a PENDING row in BASE's table), outside */results/, + a file the diff modifies, deletes or renames: when the pin tokens on removed + lines differ from those on added lines (as multisets), the same diff must add an + EXPERIMENTS.md heading + ### amendment : (YYYY-MM-DD) + ("Amendment" and "(<host>, YYYY-MM-DD)" are read the same way) + whose names include a PENDING row of the line or one of its aliases, dated no + earlier than the merge-base's commit date minus one day. A PENDING row with a + "### <row or alias> run" heading in BASE gets a warning: it has started. + A ledger line whose rows all have verdicts is an arm for this diff, not + historical, when the diff records a run of one of them (adds a "### <names> + run" heading, or rewrites its Hypotheses table line) or pre-registers a row of + it (absent from BASE's table, PENDING in HEAD's, named by an added + "### <names> pre-registration" heading, which then sets its pins; the row may + add itself to the line in HEAD's pins/classes in the same diff), or adds a + PENDING row to the line in HEAD's pins/classes with an amendment naming it (the + amendment moves that row's arm onto this runner, and is held to the rule above). + d. --autobump (an autobump/* branch): touching .github/ or pins/classes fails, and + touching EXPERIMENTS.md fails unless the branch is autobump/amend-* + e. a changed file under bench/ or oracle/ that holds a pin token and no line + classifies fails: classify it in pins/classes + +Classes come from BASE's pins/classes; a path it leaves unmatched takes HEAD's line +(a path the diff classifies). --classes FILE replaces both (a replay of commits that +predate pins/classes). Exit 0 clean, 1 on a failure, 2 on a usage or git error. +""" + +import argparse +import collections +import datetime +import importlib.machinery +import importlib.util +import os +import pathlib +import re +import subprocess +import sys +import tempfile + +HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_loader = importlib.machinery.SourceFileLoader("pin_classes", os.path.join(HERE, "hack", "pin-classes")) +_spec = importlib.util.spec_from_loader("pin_classes", _loader) +pc = importlib.util.module_from_spec(_spec) +_loader.exec_module(pc) + +AMEND_RE = re.compile(r"^### (?P<names>.+?) [Aa]mendment [A-Z]+[0-9]+: .+ \((?:[^()]*,\s*)?\d{4}-\d{2}-\d{2}\)\s*$") +PREREG_RE = re.compile(r"^###\s+(?P<names>.+?)\s+pre-registration\b") +RUN_RE = re.compile(r"^###\s+(?P<names>.+?)\s+(?:re-)?run\b") +DATE_RE = re.compile(r"(\d{4}-\d{2}-\d{2})\)\s*$") +ROW_RANGE = re.compile(r"^H(\d+)([a-z]{0,2})-H(\d+)([a-z]{0,2})$") +TABLE_ROW = re.compile(r"^\|\s*(H\d+[a-z]{0,2})\s*\|") + + +class Git: + def __init__(self, root): + self.root = root + + def run(self, *args, ok=(0,)): + p = subprocess.run(["git", "-C", self.root] + list(args), capture_output=True) + if p.returncode not in ok: + raise RuntimeError("git %s: %s" % (" ".join(args), p.stderr.decode(errors="replace").strip())) + return p + + def out(self, *args): + return self.run(*args).stdout.decode(errors="replace") + + def show(self, rev, path): + """The file at rev, or None when rev has no such (text) file.""" + p = self.run("show", "%s:%s" % (rev, path), ok=(0, 128)) + if p.returncode: + return None + try: + return p.stdout.decode("utf-8") + except UnicodeDecodeError: + return None + + +def names_of(text): + """The row ids and aliases a heading's <names> part lists, casefolded; H30-H32 is + H30, H31 and H32.""" + out = set() + for tok in re.split(r"[\s,/]+|\band\b", text): + tok = tok.strip("*`'\".:;()") + if not tok: + continue + out.add(tok.casefold()) + m = ROW_RANGE.match(tok) + if m: + a, b = int(m.group(1)), int(m.group(3)) + out.update({("H%d%s" % (a, m.group(2))).casefold(), ("H%d%s" % (b, m.group(4))).casefold()}) + if not m.group(2) and not m.group(4) and 0 <= b - a <= 50: + out.update(("H%d" % i).casefold() for i in range(a, b + 1)) + return out + + +def headings(text, rx): + return [line for line in (text or "").splitlines() if rx.match(line)] + + +def added_lines(diff_text): + """(removed, added) content lines of a -U0 diff.""" + removed, added, hunk = [], [], False + for line in diff_text.splitlines(): + if line.startswith("diff --git"): + hunk = False + elif line.startswith("@@"): + hunk = True + elif hunk and line.startswith("-"): + removed.append(line[1:]) + elif hunk and line.startswith("+"): + added.append(line[1:]) + return removed, added + + +def ledger_of(text): + if text is None: + return {} + with tempfile.TemporaryDirectory() as d: + pathlib.Path(d, "EXPERIMENTS.md").write_text(text) + return pc.read_ledger(d) + + +class Guard: + def __init__(self, root, base, head, autobump, branch, classes_file): + self.git = Git(root) + self.base = self.git.out("rev-parse", "--verify", base + "^{commit}").strip() + self.head = self.git.out("rev-parse", "--verify", head + "^{commit}").strip() + self.mb = self.git.out("merge-base", self.base, self.head).strip() + self.autobump, self.branch = autobump, branch + self.fails, self.warns = [], [] + if classes_file: + lines, refs = pc.load(classes_file) + self.base_lines, self.head_lines, self.refs = lines, [], refs + else: + base_text = self.git.show(self.base, pc.CLASSES_FILE) + head_text = self.git.show(self.head, pc.CLASSES_FILE) + self.base_lines, base_refs = pc.parse(base_text) if base_text else ([], []) + self.head_lines, head_refs = pc.parse(head_text) if head_text else ([], []) + seen = {(r.glob, r.ref) for r in base_refs} + self.refs = base_refs + [r for r in head_refs if (r.glob, r.ref) not in seen] + base_exp = self.git.show(self.base, "EXPERIMENTS.md") + self.ledger = ledger_of(base_exp) + self.head_ledger = ledger_of(self.git.show(self.head, "EXPERIMENTS.md")) + self.run_names = [names_of(RUN_RE.match(h).group("names")) for h in headings(base_exp, RUN_RE)] + exp_removed, exp_added = added_lines(self.diff("EXPERIMENTS.md")) + self.amendments = [] + for line in exp_added: + m = AMEND_RE.match(line) + if m: + d = DATE_RE.search(line) + self.amendments.append((line, names_of(m.group("names")), d.group(1) if d else None)) + self.preregs = set() + self.landing = set() # rows whose run this diff records + for line in exp_added: + m = PREREG_RE.match(line) + self.preregs |= names_of(m.group("names")) if m else set() + m = RUN_RE.match(line) + self.landing |= names_of(m.group("names")) if m else set() + # A Hypotheses table line the diff rewrites: a verdict landing, or a re-opened row's. + rewritten = {m.group(1) for m in map(TABLE_ROW.match, exp_removed) if m} + self.landing |= {m.group(1).casefold() for m in map(TABLE_ROW.match, exp_added) if m and m.group(1) in rewritten} + day = self.git.out("log", "-1", "--format=%cs", self.mb).strip() + self.floor = (datetime.date.fromisoformat(day) - datetime.timedelta(days=1)).isoformat() + + def diff(self, *paths): + return self.git.out("diff", "--no-ext-diff", "--no-color", "-U0", "-M", self.mb, self.head, "--", *paths) + + def line_for(self, path): + ln = pc.match(self.base_lines, path) + return ln if ln else pc.match(self.head_lines, path) + + def effective(self, path): + ln = self.line_for(path) + if ln is None: + return "ours", None + if ln.kind != "ledger": + return ln.kind, ln + state = pc.resolve(ln, self.ledger) + if state == "historical" and (self.prereg_rows(ln, path) or self.landing_rows(ln) + or self.joined_rows(ln, path)): + # reused by a row this diff pre-registers or amends onto it, or a re-run it records + state = "arm" + return state, ln + + def landing_rows(self, ln): + """Rows of the line whose run record this diff adds ("### <names> run ...").""" + return [r for r in ln.rows if r.casefold() in self.landing] + + def prereg_rows(self, ln, path): + """Rows of the line this diff pre-registers: absent from BASE's table, PENDING in + HEAD's, named by an added pre-registration heading. HEAD's ledger line for path + counts too, so a new row can add itself to a finished runner's line in the same + diff (no other change to BASE's line takes effect).""" + head = pc.match(self.head_lines, path) + rows, aliases = list(ln.rows), list(ln.aliases) + if head is not None and head.kind == "ledger": + rows += [r for r in head.rows if r not in rows] + aliases += [a for a in head.aliases if a not in aliases] + new = [r for r in rows if r not in self.ledger and self.head_ledger.get(r) == "PENDING"] + return [r for r in new if r.casefold() in self.preregs or any(a.casefold() in self.preregs for a in aliases)] + + def joined_rows(self, ln, path): + """PENDING rows (in BASE's table) that HEAD's ledger line for path adds to BASE's + line, each named by an amendment heading this diff adds: the amendment moves the + row's arm onto this runner. check_arm then holds the amendment to its date floor.""" + head = pc.match(self.head_lines, path) + if head is None or head.kind != "ledger": + return [] + named = set().union(*(a[1] for a in self.amendments)) + return [r for r in head.rows if r not in ln.rows and self.ledger.get(r) == "PENDING" + and r.casefold() in named] + + def fail(self, msg): + self.fails.append(msg) + + def changes(self): + """[(status letter, [path] or [old path, new path])] of the diff.""" + fields = self.git.out("diff", "--name-status", "-z", "-M", self.mb, self.head).split("\0") + out, i = [], 0 + while i < len(fields) and fields[i]: + code = fields[i][0] + n = 2 if code in "RC" else 1 + out.append((code, fields[i + 1:i + 1 + n])) + i += 1 + n + return out + + def check(self): + changes = self.changes() + for code, paths in changes: + old, new = paths[0], paths[-1] + for p in set(paths): + self.check_autobump(p) + if code in "MDRT": + kind, ln = self.effective(old) + if kind == "historical": + what = {"M": "modified", "D": "deleted", "R": "renamed to " + new, "T": "changed type"}[code] + why = ("%s line %d" % (pc.CLASSES_FILE, ln.n)) if ln else pc.CLASSES_FILE + self.fail("%s: historical record %s (%s: %s)" % (old, what, why, self.state_of(ln))) + continue + target = old if code == "D" else new + kind, ln = self.effective(target) + # A new file moves no pin a row already has; its pins are guarded from then on. + if kind == "arm" and code not in "AC" and "/results/" not in "/" + target: + self.check_arm(code, old, new, target, ln) + if code != "D" and pc.guarded(new) and self.line_for(new) is None: + text = self.git.show(self.head, new) + if text is not None and pc.text_tokens(new, text): + self.fail("%s: holds pin tokens (%s) and no %s line classifies it: classify it in %s" + % (new, pc.text_tokens(new, text)[0], pc.CLASSES_FILE, pc.CLASSES_FILE)) + self.check_refs({p for _, paths in changes for p in paths}) + return self.fails, self.warns + + def state_of(self, ln): + if ln is None or ln.kind != "ledger": + return "historical" + (" (%s)" % ln.reason if ln and ln.reason else "") + return "ledger rows %s have verdicts in %s's EXPERIMENTS.md" % (",".join(ln.rows), self.base[:12]) + + def check_autobump(self, path): + if not self.autobump: + return + if path.startswith(".github/"): + self.fail("%s: an autobump branch never touches .github/" % path) + elif path == pc.CLASSES_FILE: + self.fail("%s: an autobump branch never reclassifies pins" % path) + elif path == "EXPERIMENTS.md" and not (self.branch or "").startswith("autobump/amend-"): + self.fail("EXPERIMENTS.md: only an autobump/amend-* branch touches the ledger (branch %s)" % (self.branch or "?")) + + def check_arm(self, code, old, new, target, ln): + if target.endswith(".md"): + return + removed, added = added_lines(self.diff(*sorted({old, new}))) + gone = collections.Counter(t for line in removed for t in pc.pin_tokens(line)) + came = collections.Counter(t for line in added for t in pc.pin_tokens(line)) + if gone == came: + return + pending = [r for r in ln.rows if self.ledger.get(r) == "PENDING"] + for r in pending: + if any(r.casefold() in n for n in self.run_names) or any( + a.casefold() in n for n in self.run_names for a in ln.aliases if a not in self.ledger): + self.warns.append("%s: %s has a run heading in EXPERIMENTS.md already: this pin change " + "amends a row that has started" % (target, r)) + break + if not pending and self.prereg_rows(ln, target): + return # the pre-registration this diff adds sets the new row's pins + rows = pending or self.landing_rows(ln) or self.joined_rows(ln, target) + owners = {r.casefold() for r in rows} | {a.casefold() for a in ln.aliases} + if [a for a in self.amendments if a[1] & owners and a[2] and a[2] >= self.floor]: + return + stale = [a for a in self.amendments if a[1] & owners and (not a[2] or a[2] < self.floor)] + changed = sorted((gone - came).elements())[:2] + sorted((came - gone).elements())[:2] + if stale: + self.fail("%s: arm pin change (%s); the amendment %r is dated before %s" + % (target, ", ".join(changed), stale[0][0], self.floor)) + else: + self.fail("%s: arm pin change (%s) for %s %s with no amendment in this diff: add " + "'### <%s> amendment <ID>: <title> (YYYY-MM-DD)' to EXPERIMENTS.md" + % (target, ", ".join(changed), "PENDING" if pending else "re-run", ",".join(rows), + "|".join(rows + list(ln.aliases)))) + + def check_refs(self, changed): + for r in self.refs: + if r.glob not in changed: + continue + before = (self.git.show(self.mb, r.glob) or "").count(r.ref) + after = (self.git.show(self.head, r.glob) or "").count(r.ref) + if before and after != before: + self.fail("%s: historical-ref %r occurs %d time(s), %d at the merge-base (%s line %d)" + % (r.glob, r.ref, after, before, pc.CLASSES_FILE, r.n)) + + +def main(argv): + ap = argparse.ArgumentParser(prog="pin-guard", description=__doc__.split("\n")[0]) + ap.add_argument("--base", required=True) + ap.add_argument("--head", default="HEAD") + ap.add_argument("--autobump", action="store_true") + ap.add_argument("--branch", default=os.environ.get("GITHUB_HEAD_REF") or os.environ.get("GITHUB_REF_NAME")) + ap.add_argument("--classes") + ap.add_argument("--root", default=os.getcwd()) + a = ap.parse_args(argv[1:]) + try: + g = Guard(a.root, a.base, a.head, a.autobump, a.branch, a.classes) + fails, warns = g.check() + except (RuntimeError, ValueError, OSError) as e: + sys.stderr.write("pin-guard: %s\n" % e) + return 2 + for w in warns: + print("::warning::" + w) + for f in fails: + print("FAIL " + f) + if os.environ.get("GITHUB_ACTIONS"): + print("::error::" + f) + if fails: + return 1 + print("ok %s..%s: pins/classes holds (%d warning(s))" % (g.mb[:12], g.head[:12], len(warns))) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/images.lock b/images.lock index a7b8bdb7..5052f203 100644 --- a/images.lock +++ b/images.lock @@ -62,7 +62,7 @@ "sources": [ "scripts/k3d-gpu K3D_GPU_AGENT_VIDEO_IMAGE" ], - "tag": null + "tag": "2026.2.0-ubuntu24" }, { "digest": "sha256:386e6bec28b3f25a0e4e1c0fb726f026e8ed199bd74e4aa0fb226631359f7daf", @@ -127,7 +127,7 @@ "sources": [ "scripts/k3d-gpu AGENT_DEFAULT_IMAGE" ], - "tag": null + "tag": "1.5" }, { "digest": "sha256:dd9874fb6a29aa6b3070754760876b42d0e440bb34537d7196d51bc15abb0068", @@ -190,14 +190,7 @@ "digest": "sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58", "name": "docker.io/openvino/model_server", "sources": [ - "scripts/k3d-gpu AGENT_OVMS_IMAGE" - ], - "tag": null - }, - { - "digest": "sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58", - "name": "docker.io/openvino/model_server", - "sources": [ + "scripts/k3d-gpu AGENT_OVMS_IMAGE", "scripts/k3d-gpu NPU_UMD_IMAGE", "share/npu-embeddings.yml" ], @@ -297,7 +290,7 @@ "sources": [ "scripts/k3d-gpu AGENT_DEFAULT_IMAGE" ], - "tag": null + "tag": "v0.32.0" }, { "digest": "sha256:73cbb215d3d43ae5b42816217b200386eb08d567767fcb36cba163152c5f3c79", @@ -316,14 +309,6 @@ ], "tag": "v0.15.0" }, - { - "digest": "sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "name": "ghcr.io/ggml-org/llama.cpp", - "sources": [ - "scripts/k3d-gpu K3D_GPU_LLAMA_VULKAN_IMAGE" - ], - "tag": null - }, { "digest": "sha256:7afc81bfe7a93de287324044fe86ccc70b1b7ac8832ed8e6ea658e11fb448ddf", "name": "ghcr.io/ggml-org/llama.cpp", @@ -348,13 +333,21 @@ ], "tag": "server-intel-b11960" }, + { + "digest": "sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "name": "ghcr.io/ggml-org/llama.cpp", + "sources": [ + "scripts/k3d-gpu K3D_GPU_LLAMA_VULKAN_IMAGE" + ], + "tag": "server-vulkan-b11960" + }, { "digest": "sha256:9b5424634b74f0c6905109fc46f90a7f569ab7bde1211d3ac5f9c32a17895e60", "name": "ghcr.io/home-operations/drm-exporter", "sources": [ "share/telemetry/intel-gpu-exporter.yml" ], - "tag": null + "tag": "0.3.4" }, { "digest": "sha256:1479956240397c7207c3d38c0d296bd591cd66f26d432452f9a2d75336843776", @@ -378,7 +371,7 @@ "sources": [ "share/agent-chat-ui.yml" ], - "tag": null + "tag": "v0.12.2" }, { "digest": "sha256:27c1b2553a690ca3d29889dcd61df97525d46f58272c0be8dc63c2b18f120075", @@ -442,7 +435,7 @@ "sources": [ "scripts/k3d-gpu AGENT_DEFAULT_IMAGE" ], - "tag": null + "tag": "1.24.2-481" } ], "k3s_tag": "v1.37.1-k3s1", diff --git a/images.unpinned b/images.unpinned index 98180692..aaa5a308 100644 --- a/images.unpinned +++ b/images.unpinned @@ -1,6 +1,7 @@ -# Image references allowed without a digest pin. `scripts/images-lock pins` (CI and -# tests/image_pins_test.sh) fails on any other name:tag, bare name or :latest -# reference, and on a line here that matches nothing. +# Image references allowed without a digest pin, or with a digest but no tag. +# `scripts/images-lock pins` (CI and tests/image_pins_test.sh) fails on any other +# name:tag, bare name or :latest reference, on a name@sha256 with no tag outside the +# ledger and historical paths of pins/classes, and on a line here that matches nothing. # # <path glob> <reference, or * for every reference in the file> <reason> @@ -30,3 +31,9 @@ tests/model_share_test.sh localhost:5000/agent chec tests/image_pins_test.sh * the pin gate's own unpinned inputs tests/eat_test.sh * stubs of EAT's gateway chart files, the input gateway-charts.py pins tests/ledger_freshness_test.py cryptoandcoffee/k3d-gpu:0.1.70 a fixture ledger row's node image the re-measure queue parses, never pulled +tests/pin_classes_test.py * the pin-token parser's own inputs + +# Digest-only test inputs: the form a user may pass, or an old release pinned. +tests/detect_test.sh intel/intel-gpu-plugin@* a fake digest-only plugin image: up must still add its args +tests/llamacpp_test.sh openvino/model_server@* a digest-only K3D_GPU_AGENT_IMAGE the fit must not hand to llama.cpp +oracle/test_oracle.py openvino/model_server@* release 0.1.19's OVMS pin (digest only then), the server image the seed bundles record diff --git a/oracle/bundle.py b/oracle/bundle.py index eac88025..25273214 100644 --- a/oracle/bundle.py +++ b/oracle/bundle.py @@ -63,6 +63,15 @@ def image_registry(ref: str) -> str | None: return None +def untagged(ref: str) -> str: + """name@sha256:<digest> for a ref that carries a digest, its tag dropped. A bundle + records the server image by digest alone; the launcher pins name:tag@sha256:...""" + name, at, digest = ref.partition("@") + if at and ":" in name.rsplit("/", 1)[-1]: + name = name.rsplit(":", 1)[0] + return name + at + digest + + def public_image(ref: str) -> bool: host = image_registry(ref) return host is None or host in PUBLIC_REGISTRIES diff --git a/oracle/project.py b/oracle/project.py index 22f69c60..d82bb096 100644 --- a/oracle/project.py +++ b/oracle/project.py @@ -283,8 +283,9 @@ def model_block(model_id, revision, weights_sha256, weights_bytes, quant): def tool_block(version, server_image, k3s=None): if not re.match(r"^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|git-[0-9a-f]{40})$", version or ""): raise ProjectionError(f"tool version {version!r} is neither a release (x.y.z) nor git-<sha>") + server_image = B.untagged(server_image or "") return {"version": version, "k3s": k3s, - "server_image": server_image if DIGEST_IMAGE_RE.match(server_image or "") + "server_image": server_image if DIGEST_IMAGE_RE.match(server_image) and B.public_image(server_image) else None, "node_image_digest": None} diff --git a/oracle/test_oracle.py b/oracle/test_oracle.py index 9323b28e..1f987d4a 100644 --- a/oracle/test_oracle.py +++ b/oracle/test_oracle.py @@ -440,6 +440,16 @@ def test_V4_release_and_provenance(self): self.assertEqual(check(reseal(b)).verdict, "reject") self.assertEqual(check(reseal(b), trusted=True).verdict, "accept") + def test_V4_tagged_release_pin_matches_the_digest(self): + # A release pins name:tag@sha256:X; a bundle records the image as name@sha256:X. + name, digest = AGENT_IMAGE.split("@") + tagged = "%s:2026.4.1-gpu@%s" % (name, digest) + self.assertEqual(check(self.b, pins={"0.1.19": tagged}).verdict, check(self.b).verdict) + self.assertNotIn("V4", rules(check(self.b, pins={"0.1.19": tagged}))) + self.assertEqual(V.script_pins('AGENT_OVMS_IMAGE="%s"\n' % tagged), {AGENT_IMAGE}) + self.assertEqual(P.tool_block("0.1.19", tagged)["server_image"], AGENT_IMAGE) + self.assertEqual(P.tool_block("0.1.19", AGENT_IMAGE)["server_image"], AGENT_IMAGE) + def test_V5_tampered_claims_are_rejected(self): b = copy.deepcopy(self.b) b["results"]["capacity"]["agents_per_box"] += 1 diff --git a/oracle/validate.py b/oracle/validate.py index 3f0f4ae6..dc946cf7 100644 --- a/oracle/validate.py +++ b/oracle/validate.py @@ -218,8 +218,8 @@ def to_us(v): def script_pins(text): - """Every server image a k3d-gpu script pins by digest.""" - return {a or b for a, b in AGENT_IMAGE_RE.findall(text or "")} + """Every server image a k3d-gpu script pins by digest, as name@sha256:...""" + return {B.untagged(a or b) for a, b in AGENT_IMAGE_RE.findall(text or "")} def release_pin(version, repo=REPO): @@ -237,7 +237,7 @@ def release_pin(version, repo=REPO): def as_pin_set(pin): if pin is None: return None - return {pin} if isinstance(pin, str) else set(pin) + return {B.untagged(p) for p in ([pin] if isinstance(pin, str) else pin)} def check_release(b, res, trusted, pins, repo): diff --git a/pins/classes b/pins/classes new file mode 100644 index 00000000..fdd88aca --- /dev/null +++ b/pins/classes @@ -0,0 +1,108 @@ +# Pin classes: who may move the pins (image refs, pkg==version, commits, *_TAG/ +# *_VERSION/*_IMAGE/... assignments) in each path. hack/pin-classes reads this file, +# hack/pin-guard enforces it on every PR and push to main, and `scripts/images-lock +# repin` leaves ledger and historical paths alone. +# +# <glob> ours|eat-stock|historical [reason] +# <glob> ledger <rows csv> amend=<aliases csv> [reason] +# historical-ref <path> <exact string> +# +# First match wins; * also matches /. A path no line matches is ours, except a file +# under bench/ or oracle/ that holds a pin: hack/pin-classes coverage fails on it. +# +# ours k3d-gpu's own pins: always the latest stable release. +# eat-stock tracks Intel EAT's latest release (intel/enterprise-agent-toolkit); reviewed. +# ledger a ledger row's runner or records. While any listed row is PENDING in the +# EXPERIMENTS.md Hypotheses table the path is an arm: a pin changes only +# with a dated amendment in the same diff whose heading names a PENDING +# listed row or an alias ("### <alias> amendment <ID>: <title> (YYYY-MM-DD)"). +# Once a listed row has a verdict and none is PENDING it is historical. +# historical a finished run's record: never modified, deleted or renamed. +# historical-ref a finished run's pin inside a live runner: it keeps occurring as +# often as it does. + +# --- EAT stock: what Intel's Enterprise Agent Toolkit ships -------------------------- +integrations/eat/** eat-stock EAT's Helm values, gateway charts and bastion, tracked to EAT's latest release +examples/kuberay/** eat-stock the RayCluster EAT's KubeRay path deploys +examples/agents/litellm-gateway/** eat-stock EAT's genai-gateway (LiteLLM) agent example +contrib/eat/** eat-stock the patch and values contributed to EAT +integrations/eas/** eat-stock EAS, the stack EAT 2.0 deploys through: pins.json freezes what EAS's own configs resolve to + +# --- Finished records ------------------------------------------------------------------ +console/matrix.v1.json historical the measured cells of H6 and H8 the planner reads +oracle/seed/** historical the maintainer bundles of the H6 and H8 runs +oracle/raw/** historical the raw H6 and H8 records the seed bundles were built from + +# --- Runners of finished rows. Ledger lines, so the class follows the ledger: every row +# listed has a verdict, so each resolves to historical (hack/pin-classes effective). +bench/amx-autoquant/** ledger H27,H27x amend=Cross-host H27 REFUTED; owner decision pending (H27x REFUTED too) +bench/apb/arm.sh ledger H8 amend= H8's arms +bench/apb/vllm-cuda.yaml ledger H8 amend= H8's vLLM CUDA arm (A3) +bench/apb/h7.sh ledger H7 amend= H7's runner +bench/dra_round.sh ledger H16,H17 amend= the H16/H17 harness +bench/noisy.sh ledger H3,H26 amend= H3 and H26's noisy neighbour +bench/rdt-isolate/** ledger H26 amend= H26's runner +bench/thermal-resource/** ledger H23 amend= H23's runner and records +bench/prefill-guard/** ledger H5 amend= H5's stage-1 harness and records +bench/h54gpu/** ledger H54b,H54c amend= H54b and H54c's runner and records +bench/h54/diag/** ledger H54a amend=H54 H54 note N2, the B1g stall diagnostic of H54a +bench/eatflow/*constraints.txt ledger H40,H41,H42,H43,H44,H45,H46,H47,H48 amend= the H40-H48 BFCL and coding-agent pip sets + +# --- Records of finished rows inside live runner trees ------------------------------- +bench/eatflow/results/2026-10-17/** ledger H40,H41,H42,H43,H44,H45,H46,H47,H48 amend=Crescent the H40-H48 desktop run +bench/eatflow/results/2026-10-18/** ledger H47,H49,H47b,H49b amend=Crescent the H47, H49, H47b and H49b runs +bench/eatflow/results/2026-10-19/** ledger H43,H45 amend=Crescent the H43 and H45 runs +bench/eatflow/results/2026-10-20/** ledger H50,H50o,H51,H52 amend=Crescent,H52 the first crescent runs +bench/eatflow/results/2026-10-21/** ledger H50,H50o,H52b,H56 amend=Crescent,H52 H52b's pre-check, H56 stage 1, the QAT export +bench/eatflow/results/*/H52b*/** ledger H52b amend=Crescent,H52 H52b's run +bench/h54/results/2026-10-21/** ledger H54a,H54d amend=H54 H54a's run and H54d's not-run record +bench/h54/results/*/B1g-stall-diag/** ledger H54a amend=H54 H54 note N2's records +bench/h57d/results/2026-10-21/** ledger H57da,H57dm,H57db amend=H57d the H57d run +bench/hetero-spec/results/** ledger H22,H34 amend=H34 the H22 and H34 runs +bench/xhost/results/2026-10-20/** ledger H25x,H27x,H28x,H34x amend=Cross-host the first cross-host runs + +# Pins of finished rows that live runners keep so those rows reproduce. +historical-ref bench/eatflow/live.sh ${LIVE_EAT_B1_TAG:-v0.24.0@sha256:b0e6f8604413a8bfcca8836bb4b57b47f1aa653957e07f8903ece531309981a1} +historical-ref bench/eatflow/live.sh image=vllm/vllm-openai@sha256:a4a4c0437bf7240089da5f08aa370c4aee17ae5290f7a3b468825ee26c4c3a6b +historical-ref bench/eatflow/live.sh ollama/ollama:0.40.5@sha256:5d3a2f25f9825628daba94dc0b1d421c7d57d6c03ae5faa9ba050e058bab8241 +historical-ref bench/eatflow/live.sh ghcr.io/ggml-org/llama.cpp:server-b11706@sha256:f41d47eee678c02656eefc9ed8cce76cab584aa7032805eeef0e6639fcfda485 +historical-ref bench/h54/h54.py "vllm024": "vllm/vllm-openai-cpu:v0.24.0@sha256:b0e6f8604413a8bfcca8836bb4b57b47f1aa653957e07f8903ece531309981a1" +historical-ref bench/h54/h54.py "ovms_eat": "2025.4@sha256:c370720007564bbdcbca13b9b714e4537068542cda512e8095259e66b617962a" + +# --- Ledger rows: runners of PENDING rows and their finished siblings ---------------- +bench/eatflow/agent.lock ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H50,H50o,H53,H56 amend=Crescent the agent lock: H40-H48, then crescent M1's Gemma 4 and H53's Qwen3.8 sampling +bench/eatflow/live.sh ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the agent-flow runner (desktop rows, then crescent) +bench/eatflow/crescent/** ledger H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the crescent wrappers, setup and holds +bench/eatflow/** ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the agent-flow harness (pairs, solo, BFCL, tasks) +bench/apb/h7x* ledger H7x amend=Cross-host H7x's runner +bench/apb/test_h7x.py ledger H7x amend=Cross-host H7x's runner tests +bench/xhost/** ledger H7x,H25x,H27x,H28x,H34x,H60 amend=Cross-host the cross-host workspace, lock and holds +bench/h54/** ledger H54a,H54q amend=H54 the H54 runner; H54q is PENDING +bench/h57d/** ledger H57da,H57dm,H57db,H57dp,H57de amend=H57d the H57d runner; H57dp and H57de are PENDING +bench/h59/** ledger H59,H59d,H59w amend=H59 H59's scorer +bench/flashnext/** ledger H60 amend=H60 H60's runner +bench/admission/** ledger H55 amend=H55 H55's SLO-gated admission harness +bench/models/** ledger H50,H50o,H57da,H57dm,H57db,H57dp,H57de amend=Crescent,H57d the Gemma 4 QAT export and grid (crescent amendment M1, H57d) +bench/wildct/** ledger H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb,H59w amend=Wildct the wildct client host's runner +bench/load/** ledger H1b,H20,H21,H28,H30,H31,H32,H49b,H56,H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb amend=Wildct the matched background-load harness (amendment 27) +bench/sandbox_round.sh ledger H18,H18w amend=Wildct H18's sandbox round, replicated on wildct +bench/zero-copy-models/** ledger H28,H28x,H28w amend=Wildct,Cross-host H28's probe, replicated cross-host and on wildct +bench/shared-prefix/** ledger H25,H25x,H25w amend=Wildct,Cross-host H25's probe, replicated cross-host and on wildct +bench/hetero-spec/** ledger H22,H34,H34x,H34w amend=Wildct,Cross-host H22 and H34's probes; h34.sh replicated cross-host and on wildct +bench/joule-pack/** ledger H21 amend=H21 H21's probe +bench/phase-steer/** ledger H20 amend=H20 H20's probe +bench/sku-model/** ledger H24 amend=H24 H24's probe and records +bench/tune-noise/** ledger H19 amend=H19 H19's runner +bench/uncore-phase/** ledger H29 amend=H29 H29's probe +bench/vs_matched.py ledger H30,H31,H32 amend= the H30-H32 matched-load comparison + +# --- k3d-gpu's own bench tooling (shipped by nfpm, or tests of what ships) ------------- +bench/vs.py ours k3d-gpu vs (nfpm ships it) +bench/llm_load.py ours k3d-gpu tune's load generator (nfpm ships it) +bench/energy.sh ours k3d-gpu tune's energy reader (nfpm ships it) +bench/agent_trace.py ours k3d-gpu capacity's trace replayer (nfpm ships it) +bench/agent_trace_gen.py ours k3d-gpu capacity's trace generator (nfpm ships it) +bench/test_agent_trace.py ours tests of the shipped trace replayer +bench/competitors.lock ours the competitor engines k3d-gpu vs runs (nfpm ships it) +bench/apb/** ours k3d-gpu capacity's apb.py and capacity.py (nfpm ships them), stage.py and their tests +oracle/** ours the capacity oracle's code, schema and tests (bundle.py, project.py, validate.py and share.py ship) diff --git a/scripts/images-lock b/scripts/images-lock index 39cacb5b..ff6c4c02 100755 --- a/scripts/images-lock +++ b/scripts/images-lock @@ -3,13 +3,22 @@ scripts/images-lock check offline: images.lock lists exactly the images the repo references, with the digests the repo pins - scripts/images-lock update network: resolve each digest from its registry, - read k3s's own image list, rewrite images.lock + scripts/images-lock update [--only FILE...] + network: resolve each digest from its registry, + read k3s's own image list, rewrite images.lock; a + pinned digest its tag no longer resolves to fails. + --only resolves and checks only the references FILEs + source (k3s-images.txt too when the Dockerfile is one) + and keeps every other entry as images.lock has it; a + reference another file adds, or pins to a digest the + lock does not hold, fails (name that file too) scripts/images-lock pins offline: every image reference in the repo is pinned - by digest (name:tag@sha256:...), except those that - images.unpinned lists, each with its reason; and every - NVIDIA apt package (nvidia-*, libnvidia-*) a Dockerfile - installs is pinned pkg=version + by digest under its tag (name:tag@sha256:...), except + those that images.unpinned lists, each with its reason + (a digest with no tag passes only in a ledger or + historical path of pins/classes: records keep what + ran); and every NVIDIA apt package (nvidia-*, + libnvidia-*) a Dockerfile installs is pinned pkg=version scripts/images-lock digest REF... network: print the registry's digest of each name:tag scripts/images-lock repin [--exclude GLOB]... [FILE...] @@ -18,7 +27,13 @@ repo, or in FILEs, and write the registry's digest; files whose repo-relative path matches a GLOB are left alone (the nightly passes '.github/workflows/*': its - token cannot push workflow files) + token cannot push workflow files), and so are the ledger + and historical paths of pins/classes and the files its + historical-ref lines name (a pending row's pins move + only by a ledger amendment, a finished run's never) + scripts/images-lock drift network: list the name:tag@sha256 pins (outside those + same paths) whose tag the registry now resolves to + another digest; writes nothing, exits 0 Sources of image references (the lock holds all of them and nothing else): - share/**/*.yml every `image:` line (the baked manifests and the @@ -36,8 +51,9 @@ Sources of image references (the lock holds all of them and nothing else): (coredns, local-path-provisioner, metrics-server, pause, ...): k3s itself starts them in the cluster -`pins` and `repin` read every tracked text file except prose (*.md, docs/) and -recorded data (research/, data/, oracle/raw/, */results/). An image reference is +`pins` and `repin` read every tracked text file except prose (*.md, docs/), +recorded data (research/, data/, oracle/raw/, */results/) and pins/classes (its +historical-ref strings quote finished runs' refs). An image reference is the value of an `image:` / "image": key, a `container:` key, a FROM line, a *IMAGE= / *IMG= assignment, a ${*IMAGE:-default}, a docker:// URL, or a Helm values mapping's sibling `repository:` (with `registry:`) and `tag:` keys in a *.yaml/*.yml @@ -57,7 +73,10 @@ Python standard library only. Options: --root DIR (default: this repo). """ import argparse +import email.utils import fnmatch +import importlib.machinery +import importlib.util import json import os import re @@ -83,6 +102,9 @@ ACCEPT = ", ".join([ ]) REGISTRY_HOSTS = {"docker.io": "registry-1.docker.io"} ALLOW_FILE = "images.unpinned" +# pins/classes names paths and quotes finished runs' refs exactly (historical-ref): its +# strings are not image uses, and repin must never move them. +CLASSES_FILE = "pins/classes" # One image reference: [registry[:port]/]path[:tag][@sha256:digest]. NAME_TAG = (r"(?:[a-z0-9][a-z0-9.-]*(?::[0-9]+)?/)?[a-z0-9][a-z0-9._-]*(?:/[a-z0-9][a-z0-9._-]*)*" r"(?::[A-Za-z0-9_][A-Za-z0-9._-]{0,127})?") @@ -118,6 +140,39 @@ from audit import parse_ref # noqa: E402 (one image-reference parser for the l import facts # noqa: E402 import schema_check # noqa: E402 +CLASSES_TOOL = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "hack", "pin-classes") +RETRY_STATUS = (429, 500, 502, 503, 504) +_classes = {} + + +def classes(root): + """(glob lines, historical-ref paths) of ROOT's pins/classes; none without the file.""" + if root not in _classes: + path = os.path.join(root, CLASSES_FILE) + if not os.path.isfile(path): + _classes[root] = ([], set()) + else: + if not os.path.isfile(CLASSES_TOOL): + raise ValueError("%s exists but %s does not" % (path, CLASSES_TOOL)) + loader = importlib.machinery.SourceFileLoader("pin_classes", CLASSES_TOOL) + pc = importlib.util.module_from_spec(importlib.util.spec_from_loader("pin_classes", loader)) + loader.exec_module(pc) + lines, refs = pc.load(path) + _classes[root] = (lines, {r.glob for r in refs}) + return _classes[root] + + +def pin_class(root, path): + """The kind of the first pins/classes line matching path (ours when none does).""" + lines, _ = classes(root) + return next((ln.kind for ln in lines if ln.matches(path)), "ours") + + +def frozen(root, path): + """True for a path `repin` and `drift` leave alone: a ledger or historical line's, + or a file a historical-ref line names.""" + return pin_class(root, path) in ("ledger", "historical") or path in classes(root)[1] + def dockerfile_froms(text): """The images the Dockerfile's FROM lines name, ARG defaults substituted.""" @@ -290,7 +345,7 @@ def scanned_files(root): paths += [os.path.relpath(os.path.join(dirpath, f), root).replace(os.sep, "/") for f in sorted(filenames)] return [p for p in sorted(paths) if not p.endswith(".md") and not p.startswith(SKIP_PREFIXES) and "/results/" not in "/" + p - and p not in ("images.lock", ALLOW_FILE)] + and p not in ("images.lock", ALLOW_FILE, CLASSES_FILE)] def read_text(root, path): @@ -393,6 +448,13 @@ def pins(root): if text is None: continue for line, ref, _ in image_refs(text, path): + if is_pinned(ref) and parse_ref(ref)[1] is None and pin_class(root, path) not in ("ledger", "historical"): + hits = allowed(allow, path, ref) + used.update(hits) + if not hits: + problems.append("%s:%d: %s has no tag (write name:tag@sha256:... with the tag the digest is, " + "or allow it in %s with a reason)" % (path, line, ref, ALLOW_FILE)) + continue if is_pinned(ref): continue hits = allowed(allow, path, ref) @@ -410,9 +472,11 @@ def pins(root): return problems -def repin(root, paths, exclude=()): +def repin(root, paths, exclude=(), write=True): """Rewrite each name:tag reference to name:tag@<the registry's digest>, except in - files whose repo-relative path matches an exclude glob; returns the changes.""" + files whose repo-relative path matches an exclude glob and in frozen() files. + Returns [(path, line, written text, new text, name:tag, old digest or None)]; + write=False changes nothing (drift).""" allow, digests, changes = read_allow(root), {}, [] def resolved(ref): @@ -422,17 +486,17 @@ def repin(root, paths, exclude=()): return digests[(name, tag)] for path in paths or scanned_files(root): - if any(fnmatch.fnmatchcase(path, g) for g in exclude): + if any(fnmatch.fnmatchcase(path, g) for g in exclude) or frozen(root, path): continue text = read_text(root, path) if text is None: continue - edits = {} # (start, end) -> new text + edits = {} # (start, end) -> (new text, name:tag, old digest) for m in PINNED_RE.finditer(text): if parse_ref(m.group("ref"))[1] is not None: d = resolved(m.group("ref")) if d != m.group("digest"): - edits[m.span("digest")] = d + edits[m.span("digest")] = (d, m.group("ref"), m.group("digest")) for _, ref, end in image_refs(text, path): name, tag, digest = parse_ref(ref) if end is None or tag is None: @@ -440,14 +504,16 @@ def repin(root, paths, exclude=()): if digest: # PINNED_RE moved name:tag@sha256 written as one; this is a values tag@sha256 d = resolved(ref) if d != digest: - edits[(end - len(digest), end)] = d + edits[(end - len(digest), end)] = (d, ref.rsplit("@", 1)[0], digest) elif not allowed(allow, path, ref): - edits[(end, end)] = "@" + resolved(ref) + edits[(end, end)] = ("@" + resolved(ref), ref, None) if not edits: continue - for (start, end), new in sorted(edits.items(), reverse=True): - changes.append("%s:%d: %s -> %s" % (path, text.count("\n", 0, start) + 1, text[start:end] or "(tag only)", new)) + for (start, end), (new, ref, old) in sorted(edits.items(), reverse=True): + changes.append((path, text.count("\n", 0, start) + 1, text[start:end] or "(tag only)", new, ref, old)) text = text[:start] + new + text[end:] + if not write: + continue full = os.path.join(root, path) with open(full + ".tmp", "w", encoding="utf-8") as f: f.write(text) @@ -456,17 +522,36 @@ def repin(root, paths, exclude=()): return changes +def retry_after(value): + """Seconds a Retry-After header asks for (delta-seconds or an HTTP date), or None.""" + if not value: + return None + try: + return max(0, int(value)) + except ValueError: + pass + try: + return max(0, int(email.utils.parsedate_to_datetime(value).timestamp() - time.time())) + except (TypeError, ValueError, OverflowError): + return None + + def http(req): - """urlopen with three tries on network errors (an HTTP status is not retried).""" - for attempt in range(3): + """urlopen with up to five attempts. A network error, an HTTP 429 or a 5xx is + retried after the Retry-After the reply names (capped at 120 s), else after 2, 4, + 8 and 16 s; any other HTTP status is raised at once.""" + for attempt in range(5): try: return urllib.request.urlopen(req, timeout=60) - except urllib.error.HTTPError: - raise + except urllib.error.HTTPError as e: + if e.code not in RETRY_STATUS or attempt == 4: + raise + wait = retry_after(e.headers.get("Retry-After")) except (urllib.error.URLError, TimeoutError): - if attempt == 2: + if attempt == 4: raise - time.sleep(3 * (attempt + 1)) + wait = None + time.sleep(min(120, 2 ** (attempt + 1) if wait is None else wait)) def registry_digest(name, tag): @@ -495,18 +580,49 @@ def registry_digest(name, tag): raise ValueError("%s:%s: registry refused the pull token" % (name, tag)) -def update(root, lock_path): - found, k3s_tag = collect(root) +def k3s_images(k3s_tag): + """The image references of k3s release k3s_tag's k3s-images.txt.""" with http(K3S_IMAGES_URL % urllib.parse.quote(k3s_tag.replace("-k3s", "+k3s"))) as r: - k3s_refs = [line.strip() for line in r.read().decode().splitlines() if line.strip()] - for ref in k3s_refs: + return [line.strip() for line in r.read().decode().splitlines() if line.strip()] + + +def update(root, lock_path, only=None): + """Rewrite images.lock from the registries. only: repo-relative files; then only + the references they source are resolved and checked (k3s-images.txt's when the + Dockerfile is one), and every other entry is kept as images.lock has it.""" + found, k3s_tag = collect(root) + k3s = k3s_source(k3s_tag) + named = {os.path.normpath(p).replace(os.sep, "/") for p in only or ()} + lock = read_lock(lock_path) if only is not None else None + old = {(e["name"], e["tag"]): e for e in lock["images"]} if lock else {} + if only is None or "Dockerfile" in named: + refs = [(ref, None) for ref in k3s_images(k3s_tag)] + else: + if lock["k3s_tag"] != k3s_tag: + raise ValueError("the Dockerfile's K3S_TAG is %s, images.lock is for %s: name the Dockerfile in --only" + % (k3s_tag, lock["k3s_tag"])) + refs = [(label(e["name"], e["tag"]), e["digest"]) for e in lock["images"] if k3s in e["sources"]] + for ref, locked in refs: name, tag, digest = parse_ref(ref) - e = found.setdefault((name, tag), {"digest": digest, "sources": set()}) - e["sources"].add(k3s_source(k3s_tag)) + e = found.setdefault((name, tag), {"digest": digest or locked, "sources": set()}) + e["sources"].add(k3s) images = [] for (name, tag), e in sorted(found.items(), key=lambda kv: (kv[0][0], kv[0][1] or "")): digest = e["digest"] - if digest is None: + files = {s.split(" ", 1)[0] for s in e["sources"]} | ({"Dockerfile"} if k3s in e["sources"] else set()) + if only is not None and not files & named: + # Not a file --only names: kept as images.lock has it, unresolved. A + # reference the lock lacks, or pins another digest, is a change to that + # file, so its file must be named. + if (name, tag) not in old: + raise ValueError("%s (from %s): not in images.lock; name its file in --only" + % (label(name, tag), ", ".join(sorted(e["sources"])))) + if digest is not None and digest != old[(name, tag)]["digest"]: + raise ValueError("%s (from %s): pins %s, images.lock has %s; name its file in --only" + % (label(name, tag), ", ".join(sorted(e["sources"])), digest, + old[(name, tag)]["digest"])) + digest = old[(name, tag)]["digest"] + elif digest is None: digest = registry_digest(name, tag) elif tag is not None and registry_digest(name, tag) != digest: raise ValueError("%s:%s: the repo pins %s but the registry serves %s" @@ -522,20 +638,29 @@ def update(root, lock_path): def main(argv): here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ap = argparse.ArgumentParser(prog="images-lock", description=__doc__.split("\n")[0]) - ap.add_argument("command", choices=("check", "update", "pins", "repin", "digest")) + ap.add_argument("command", choices=("check", "update", "pins", "repin", "drift", "digest")) ap.add_argument("files", nargs="*", help="repin: only these repo-relative files; digest: the references") ap.add_argument("--root", default=here) ap.add_argument("--exclude", action="append", default=[], metavar="GLOB", help="repin: leave files whose repo-relative path matches GLOB alone") + ap.add_argument("--only", nargs="+", metavar="FILE", + help="update: resolve and check only the references these repo-relative files source") args = ap.parse_args(argv[1:]) if args.files and args.command not in ("repin", "digest"): ap.error("only repin and digest take arguments") if args.exclude and args.command != "repin": ap.error("only repin takes --exclude") + if args.only and args.command != "update": + ap.error("only update takes --only") lock_path = os.path.join(args.root, "images.lock") try: if args.command == "update": - update(args.root, lock_path) + update(args.root, lock_path, args.only) + return 0 + if args.command == "drift": + moved = [c for c in repin(args.root, [], write=False) if c[5]] + for path, line, _, new, ref, old in sorted(moved): + print("%s:%d: %s %s -> %s" % (path, line, ref, old, new)) return 0 if args.command == "digest": for ref in args.files: @@ -545,8 +670,8 @@ def main(argv): print(registry_digest(name, tag)) return 0 if args.command == "repin": - for c in repin(args.root, args.files, args.exclude): - print(c) + for path, line, was, new, _, _ in repin(args.root, args.files, args.exclude): + print("%s:%d: %s -> %s" % (path, line, was, new)) return 0 problems = pins(args.root) if args.command == "pins" else check(args.root, lock_path) except (ValueError, OSError, KeyError) as e: diff --git a/scripts/k3d-gpu b/scripts/k3d-gpu index 4134a048..160043b0 100755 --- a/scripts/k3d-gpu +++ b/scripts/k3d-gpu @@ -5959,6 +5959,16 @@ amx_probe() { # Image refs are pasted into the pod overrides JSON, so only accept reference characters. valid_image() { [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9._/:@+-]*$ ]]; } +# image_id REF: name@sha256:<digest> for a REF that carries a digest, its tag dropped +# (the digest is the image), else REF. Cache keys (tune, autoquant) use it, so a tag +# written next to a pinned digest does not invalidate them. +image_id() { + local name=${1%@*} + [[ "$1" == *@sha256:* ]] || { printf '%s\n' "$1"; return; } + [[ "${name##*/}" != *:* ]] || name=${name%:*} + printf '%s@%s\n' "${name}" "${1##*@}" +} + cmd_test() { valid_image "${TEST_IMAGE}" || err "K3D_GPU_TEST_IMAGE '${TEST_IMAGE}' is not a valid image reference" valid_image "${DEVICE_TEST_IMAGE}" || err "K3D_GPU_DEVICE_TEST_IMAGE '${DEVICE_TEST_IMAGE}' is not a valid image reference" @@ -7547,7 +7557,7 @@ AGENT_SEL="" # "<device> <resource>", set by agent_select # llama.cpp server on Vulkan, the engine plan() may pick for a GPU cell other # than OVMS: ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960, the multi-arch # index built from release v0.8.0 (tag b11960, commit 8642541b255bfb5dc39cd351a178be09013a55d6). -LLAMA_VULKAN_IMAGE="${K3D_GPU_LLAMA_VULKAN_IMAGE:-ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e}" +LLAMA_VULKAN_IMAGE="${K3D_GPU_LLAMA_VULKAN_IMAGE:-ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e}" # The llama.cpp agent backend: release v0.8.0 (tag b11960, commit # 8642541b255bfb5dc39cd351a178be09013a55d6), the latest release on 2026-10-28; its image # tags are server-intel-b11960 (SYCL, Level Zero and compute-runtime 26.18 inside) for an @@ -7559,7 +7569,7 @@ LLAMA_FULL_IMAGE="${K3D_GPU_LLAMA_FULL_IMAGE:-ghcr.io/ggml-org/llama.cpp:full-b1 AGENT_STORAGE="${K3D_GPU_AGENT_STORAGE:-20Gi}" AGENT_TIMEOUT="${K3D_GPU_AGENT_TIMEOUT:-1800}" # seconds: first run downloads the model # openvino/model_server:2026.4.1-gpu (CPU, GPU and NPU plugins, compute-runtime inside). -AGENT_OVMS_IMAGE="openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" +AGENT_OVMS_IMAGE="openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" # Image, model, parsers and API of AGENT_BACKEND (on a Gaudi when AGENT_HPU is set). agent_config() { @@ -7572,13 +7582,13 @@ agent_config() { # (PyTorch 2.13.0), the release of the baked gaudi-device-plugin. Tag # 1.24.2-481 of vault.habana.ai/gaudi-docker/1.24.2/ubuntu24.04/habanalabs/ # vllm-0.30.0-ptupstream-2.13.0, pulled anonymously. - AGENT_DEFAULT_IMAGE="vault.habana.ai/gaudi-docker/1.24.2/ubuntu24.04/habanalabs/vllm-0.30.0-ptupstream-2.13.0@sha256:5d064465d751a73a729e7ecdc32c0ba5de0df1d305debb60a3d9a85b4fd3a967" + AGENT_DEFAULT_IMAGE="vault.habana.ai/gaudi-docker/1.24.2/ubuntu24.04/habanalabs/vllm-0.30.0-ptupstream-2.13.0:1.24.2-481@sha256:5d064465d751a73a729e7ecdc32c0ba5de0df1d305debb60a3d9a85b4fd3a967" elif [ "${AGENT_BACKEND}" = vllm ]; then # vllm/vllm-openai-xpu:v0.32.0 (Level Zero 1.32 and compute-runtime 26.27 inside). - AGENT_DEFAULT_IMAGE="vllm/vllm-openai-xpu@sha256:20509fabe50dda62c09c0786d1a6effa299d3dbdddcc49331b93e65144652a8c" + AGENT_DEFAULT_IMAGE="vllm/vllm-openai-xpu:v0.32.0@sha256:20509fabe50dda62c09c0786d1a6effa299d3dbdddcc49331b93e65144652a8c" else # intel/llm-scaler-vllm:1.5 = 0.26.0-b2 (vLLM 0.26 baseline, compute-runtime 26.14). - AGENT_DEFAULT_IMAGE="intel/llm-scaler-vllm@sha256:52218ad85513ab6686d4c090c83c2bd8c5b02423c63aa4dabd41837fe641fe3b" + AGENT_DEFAULT_IMAGE="intel/llm-scaler-vllm:1.5@sha256:52218ad85513ab6686d4c090c83c2bd8c5b02423c63aa4dabd41837fe641fe3b" fi # Qwen3-4B is 8 GB in float16/bfloat16: it fits a 12 GB Arc B580 with room # for the KV cache. @@ -8840,7 +8850,7 @@ AGENT_VLM_DEFAULT_MODEL="OpenVINO/Qwen3-VL-4B-Instruct-int4-ov" AGENT_VLM_NPU_MODEL="OpenVINO/Phi-3.5-vision-instruct-fp16-ov" AGENT_STT_MODEL="${K3D_GPU_AGENT_STT_MODEL:-OpenVINO/whisper-large-v3-turbo-fp16-ov}" # intel/dlstreamer:2026.2.0-ubuntu24 (GStreamer va plugin, OpenVINO GPU/NPU runtimes inside). -AGENT_VIDEO_IMAGE="${K3D_GPU_AGENT_VIDEO_IMAGE:-intel/dlstreamer@sha256:3848efe52c047cf6b5961eb070deaf9eca53bd5328f0d819fbb68ad56c65fb19}" +AGENT_VIDEO_IMAGE="${K3D_GPU_AGENT_VIDEO_IMAGE:-intel/dlstreamer:2026.2.0-ubuntu24@sha256:3848efe52c047cf6b5961eb070deaf9eca53bd5328f0d819fbb68ad56c65fb19}" # Open Model Zoo person-vehicle-bike-detection-2004 FP16 IR and the DL Streamer # sample clip, pinned by URL and checksum. AGENT_VIDEO_MODEL_URL="https://storage.openvinotoolkit.org/repositories/open_model_zoo/2023.0/models_bin/1/person-vehicle-bike-detection-2004/FP16/person-vehicle-bike-detection-2004" @@ -9815,7 +9825,7 @@ tune_fingerprint_inputs() { echo "intel_gpu=${INTEL_DRIVERS[*]+${INTEL_DRIVERS[*]}}" echo "npu=${NPU_IDS[*]+${NPU_IDS[*]}}" echo "kernel=$(kernel_release | cut -d. -f1-2)" - echo "agent_image=${K3D_GPU_AGENT_IMAGE:-${AGENT_OVMS_IMAGE}}" + echo "agent_image=${K3D_GPU_AGENT_IMAGE:-$(image_id "${AGENT_OVMS_IMAGE}")}" echo "agent_model=${TUNE_MODEL}" } @@ -11515,11 +11525,11 @@ autoquant_run() { [ -f "${dir}/autoquant.py" ] || return 1 command -v python3 >/dev/null 2>&1 || return 1 if [ -n "${facts}" ]; then - python3 "${dir}/autoquant.py" "$@" --cache "$(autoquant_cache)" --runtime "${AGENT_OVMS_IMAGE}" < "${facts}" + python3 "${dir}/autoquant.py" "$@" --cache "$(autoquant_cache)" --runtime "$(image_id "${AGENT_OVMS_IMAGE}")" < "${facts}" else mapfile -t host_args < <(autoquant_host_args) facts_json "${dir}" | python3 "${dir}/autoquant.py" "$@" --cache "$(autoquant_cache)" \ - --runtime "${AGENT_OVMS_IMAGE}" ${host_args[@]+"${host_args[@]}"} + --runtime "$(image_id "${AGENT_OVMS_IMAGE}")" ${host_args[@]+"${host_args[@]}"} fi } diff --git a/share/agent-chat-ui.yml b/share/agent-chat-ui.yml index 4284e599..59740508 100644 --- a/share/agent-chat-ui.yml +++ b/share/agent-chat-ui.yml @@ -62,7 +62,7 @@ spec: containers: - name: open-webui # ghcr.io/open-webui/open-webui:v0.12.2 - image: ghcr.io/open-webui/open-webui@sha256:10e6a78ba2d172493460a9e8a92c496aaa7a64dc4d26d622a2e95e0c736081c9 + image: ghcr.io/open-webui/open-webui:v0.12.2@sha256:10e6a78ba2d172493460a9e8a92c496aaa7a64dc4d26d622a2e95e0c736081c9 imagePullPolicy: IfNotPresent # The image runs as root and writes under /app/backend at runtime. securityContext: diff --git a/share/telemetry/intel-gpu-exporter.yml b/share/telemetry/intel-gpu-exporter.yml index 8d387522..bc880048 100644 --- a/share/telemetry/intel-gpu-exporter.yml +++ b/share/telemetry/intel-gpu-exporter.yml @@ -91,7 +91,7 @@ spec: type: RuntimeDefault containers: - name: drm-exporter - image: "ghcr.io/home-operations/drm-exporter@sha256:9b5424634b74f0c6905109fc46f90a7f569ab7bde1211d3ac5f9c32a17895e60" + image: "ghcr.io/home-operations/drm-exporter:0.3.4@sha256:9b5424634b74f0c6905109fc46f90a7f569ab7bde1211d3ac5f9c32a17895e60" imagePullPolicy: IfNotPresent securityContext: privileged: true diff --git a/tests/agent_kubeconform.sh b/tests/agent_kubeconform.sh index 285bc391..c9ca190d 100755 --- a/tests/agent_kubeconform.sh +++ b/tests/agent_kubeconform.sh @@ -10,8 +10,9 @@ set -euo pipefail K3D_GPU="$(cd "$(dirname "$0")/.." && pwd)/scripts/k3d-gpu" # Kubernetes version of the k3s node image. K8S_VERSION=$(sed -n 's/^ARG K3S_TAG="v\([0-9.]*\)-k3s.*/\1/p' "$(dirname "$0")/../Dockerfile") -# ghcr.io/yannh/kubeconform:v0.8.0 -KUBECONFORM_IMAGE="ghcr.io/yannh/kubeconform@sha256:faffaf43f95aa6425306e1ab8d6fcad72acb9049158f38e574c085ea1ec0f64e" +# The pinned kubeconform image, where the CI jobs read it too. +KUBECONFORM_IMAGE=$(sed -n 's/^KUBECONFORM_IMAGE=//p' "$(dirname "$0")/../hack/ci-tools.env") +: "${KUBECONFORM_IMAGE:?hack/ci-tools.env has no KUBECONFORM_IMAGE}" WORK=$(mktemp -d) # A measured autoquant pick on this dev box (EXPERIMENTS.md H27) must not leak into goldens. diff --git a/tests/audit_test.sh b/tests/audit_test.sh index b6d4a900..48697099 100755 --- a/tests/audit_test.sh +++ b/tests/audit_test.sh @@ -82,8 +82,9 @@ lock_drift "a bumped image under share/telemetry" \ lock_drift "a bumped plain image pin in the launcher" \ "not in images.lock: docker.io/openvino/model_server:2099.1.0-gpu (from scripts/k3d-gpu NPU_UMD_IMAGE)" \ "sed -i 's|^NPU_UMD_IMAGE=.*|NPU_UMD_IMAGE=\"openvino/model_server:2099.1.0-gpu\"|' scripts/k3d-gpu" -lock_drift "a moved backend digest in the launcher" "vllm/vllm-openai-xpu: the repo pins sha256:$(printf 'e%.0s' {1..64})" \ - "sed -i 's|vllm/vllm-openai-xpu@sha256:[0-9a-f]*|vllm/vllm-openai-xpu@sha256:$(printf 'e%.0s' {1..64})|' scripts/k3d-gpu" +XPU_TAG=$(sed -n 's|.*AGENT_DEFAULT_IMAGE="vllm/vllm-openai-xpu:\([^@]*\)@.*|\1|p' "${REPO}/scripts/k3d-gpu") +lock_drift "a moved backend digest in the launcher" "vllm/vllm-openai-xpu:${XPU_TAG}: the repo pins sha256:$(printf 'e%.0s' {1..64})" \ + "sed -i 's|\(vllm/vllm-openai-xpu:[^@]*\)@sha256:[0-9a-f]*|\1@sha256:$(printf 'e%.0s' {1..64})|' scripts/k3d-gpu" lock_drift "a changed pinned digest" "the repo pins sha256:$(printf 'f%.0s' {1..64})" \ "sed -i 's/node-feature-discovery:v0.20.1@sha256:[0-9a-f]*/node-feature-discovery:v0.20.1@sha256:$(printf 'f%.0s' {1..64})/' share/node-feature-discovery.yml" lock_drift "a k3s bump" "images.lock is for k3s v1.37.1-k3s1, the Dockerfile pins v1.38.0-k3s1" \ @@ -162,7 +163,7 @@ f = "crictl-k3d-gpu-server-0.json"; d = json.load(open(f)) d["images"].append({"id": "sha256:" + "9" * 64, "repoTags": TAGS, "repoDigests": DIGESTS, "pinned": False}) json.dump(d, open(f, "w"))' img_edit() { local s=${PY_IMG/TAGS/$1}; echo "${s/DIGESTS/$2}"; } -OWUI_DIGEST=$(sed -nE 's/^ +image: ghcr\.io\/open-webui\/open-webui@(sha256:[0-9a-f]{64})$/\1/p' "${REPO}/share/agent-chat-ui.yml") +OWUI_DIGEST=$(sed -nE 's/^ +image: ghcr\.io\/open-webui\/open-webui(:[^@]+)?@(sha256:[0-9a-f]{64})$/\2/p' "${REPO}/share/agent-chat-ui.yml") chat_ui_edit() { # chat_ui_edit <WEBUI_AUTH value as a Python literal> printf '%s\n' "$(img_edit '[]' "[\"ghcr.io/open-webui/open-webui@${OWUI_DIGEST}\"]")" \ 'w = json.load(open("workloads.json"))' \ diff --git a/tests/chat_ui_test.sh b/tests/chat_ui_test.sh index 94f1a5c6..45fb286f 100755 --- a/tests/chat_ui_test.sh +++ b/tests/chat_ui_test.sh @@ -37,14 +37,14 @@ expect_env() { [ -f "${MANIFEST}" ] || { echo "FAIL manifest missing: ${MANIFEST}"; exit 1; } -# Image pinned by digest, never by a moving tag. -has "image pinned by digest" '^ +image: ghcr\.io/open-webui/open-webui@sha256:[0-9a-f]{64}$' +# Image pinned by digest under its version tag, never by a moving tag alone. +has "image pinned by digest" '^ +image: ghcr\.io/open-webui/open-webui:v[0-9.]+@sha256:[0-9a-f]{64}$' lacks "no tag-only image" '^ +image: [^@]+:[^@]+$' # The exact index digest of ghcr.io/open-webui/open-webui:v0.12.2, and the # version the header comment names. A bump changes all three together. OWUI_VERSION=v0.12.2 OWUI_DIGEST=sha256:10e6a78ba2d172493460a9e8a92c496aaa7a64dc4d26d622a2e95e0c736081c9 -has "image is ${OWUI_VERSION} digest" "^ +image: ghcr\.io/open-webui/open-webui@${OWUI_DIGEST}$" +has "image is ${OWUI_VERSION} digest" "^ +image: ghcr\.io/open-webui/open-webui:${OWUI_VERSION//./\\.}@${OWUI_DIGEST}$" has "header names ${OWUI_VERSION}" "^# Source: https://github\.com/open-webui/open-webui ${OWUI_VERSION//./\\.}\." # E-core placement through the NRI balloons label, sized by a CPU request. diff --git a/tests/ci_wiring_test.sh b/tests/ci_wiring_test.sh index 6426f9b7..4db5362a 100755 --- a/tests/ci_wiring_test.sh +++ b/tests/ci_wiring_test.sh @@ -156,5 +156,84 @@ else fail "release does not read UBUNTU_2404_IMAGE from the Dockerfile for the 24.04 variant" fi +# No version literal in a workflow: every tool pin lives in hack/ci-tools.env (or +# another file outside .github/workflows/), where a token without the `workflows` +# permission can move it. Outside `uses:` lines (action SHAs) and comments, a +# workflow names no version, ==version, checksum or digest, except the numbers below, +# which name no pin. +if out=$(python3 - "${ROOT}" 2>&1 <<'PY' +import glob, re, sys +root = sys.argv[1] +ALLOWED = [ + (r"(?i)ubuntu[ :]?2[46]\\?\.04|\*\*2[46]\.04\*\*", "the node image's Ubuntu variants this repo publishes"), + (r"kubeconform 1\.34/1\.35", "the Kubernetes versions tests/kserve_test.sh validates, in a job name"), + (r"e\.g\. 0\.1\.[0-9]+", "an example value in an input description"), +] +VERSION = re.compile(r"(?<![\w.-])v?[0-9]+\.[0-9]+(?:\.[0-9]+)*(?![\w.])|==\s*v?[0-9]|" + r"(?<![0-9a-f])[0-9a-f]{40}(?![0-9a-f])|(?<![0-9a-f])[0-9a-f]{64}(?![0-9a-f])") +bad = [] +for path in sorted(glob.glob(root + "/.github/workflows/*.yml")): + for n, line in enumerate(open(path), 1): + s = re.sub(r"(^|\s)#.*$", "", line.rstrip("\n")) + if re.match(r"^\s*(-\s+)?uses:", s): + continue + for a, _ in ALLOWED: + s = re.sub(a, "", s) + for m in VERSION.finditer(s): + bad.append("%s:%d: %s" % (path[len(root) + 1:], n, m.group(0))) +print("\n".join(bad)) +sys.exit(1 if bad else 0) +PY +); then + ok "no version literal in .github/workflows/ outside uses: lines (tool pins live in hack/ci-tools.env)" +else + fail "version literals in workflows; move them to hack/ci-tools.env"; sed 's/^/ | /' <<< "${out}" +fi + +# A job that reads a hack/ci-tools.env key loads the file into GITHUB_ENV in a step +# before the first step that uses it, and nothing else defines the key; every key +# is used; the kubeconform test scripts read KUBECONFORM_IMAGE from the file. +if out=$(python3 - "${ROOT}" 2>&1 <<'PY' +import glob, re, sys, yaml +root = sys.argv[1] +keys = [l.split("=", 1)[0] for l in open(root + "/hack/ci-tools.env") if re.match(r"^[A-Z0-9_]+=", l)] +LOAD = re.compile(r"""grep -E '\^\[A-Z0-9_\]\+=' hack/ci-tools\.env >> "\$GITHUB_ENV\"""") +bad, used = [], set() +def uses(text, key): + return re.search(r"\$\{\{\s*env\.%s\s*\}\}|\$\{?%s\b" % (key, key), text) is not None +for path in sorted(glob.glob(root + "/.github/workflows/*.yml")): + doc = yaml.safe_load(open(path)) + rel = path[len(root) + 1:] + for key in keys: + if key in (doc.get("env") or {}): + bad.append("%s: workflow env defines %s; load it from hack/ci-tools.env" % (rel, key)) + for job, spec in (doc.get("jobs") or {}).items(): + steps = spec.get("steps") or [] + loaded = next((i for i, st in enumerate(steps) if LOAD.search(st.get("run") or "")), None) + for key in keys: + if key in (spec.get("env") or {}): + bad.append("%s %s: job env defines %s; load it from hack/ci-tools.env" % (rel, job, key)) + first = next((i for i, st in enumerate(steps) if uses(yaml.safe_dump(st), key)), None) + if first is None: + continue + used.add(key) + if loaded is None or loaded >= first: + bad.append("%s %s: step %d uses %s before a step loads hack/ci-tools.env" % (rel, job, first, key)) +for t in ("kubeconform.sh", "agent_kubeconform.sh", "share_kubeconform.sh"): + text = open(root + "/tests/" + t).read() + code = re.sub(r"(?m)^\s*#.*$", "", text) + if "hack/ci-tools.env" not in code or re.search(r"KUBECONFORM_IMAGE=[\"']?[a-z]", code): + bad.append("tests/%s: read KUBECONFORM_IMAGE from hack/ci-tools.env, with no literal of its own" % t) + used.add("KUBECONFORM_IMAGE") +bad += ["hack/ci-tools.env: %s is used by no workflow" % k for k in keys if k not in used] +print("\n".join(bad)) +sys.exit(1 if bad else 0) +PY +); then + ok "jobs load hack/ci-tools.env before using its keys; every key is used; kubeconform scripts read it" +else + fail "hack/ci-tools.env wiring"; sed 's/^/ | /' <<< "${out}" +fi + echo "${PASS} passed, ${FAILS} failed" [ "${FAILS}" -eq 0 ] diff --git a/tests/console/audit/chat-ui-noauth.golden b/tests/console/audit/chat-ui-noauth.golden index bb79565d..c7400842 100644 --- a/tests/console/audit/chat-ui-noauth.golden +++ b/tests/console/audit/chat-ui-noauth.golden @@ -1,7 +1,7 @@ -audit FAIL: 1 of 12 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 12 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/chat-ui.golden b/tests/console/audit/chat-ui.golden index 0e4baf57..b49dbceb 100644 --- a/tests/console/audit/chat-ui.golden +++ b/tests/console/audit/chat-ui.golden @@ -1,7 +1,7 @@ -audit PASS: 0 of 12 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit PASS: 0 of 12 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/default-json.golden b/tests/console/audit/default-json.golden index 50d812bf..d134bfe5 100644 --- a/tests/console/audit/default-json.golden +++ b/tests/console/audit/default-json.golden @@ -15,7 +15,7 @@ "subject": "docker.io/intel/intel-gpu-plugin:0.37.1" }, { - "detail": "sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0", + "detail": "sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0", "fix": null, "rule": "image", "status": "pass", @@ -73,12 +73,12 @@ ], "cluster": "gpu", "lock": { - "images": 55, + "images": 54, "k3s_tag": "v1.37.1-k3s1" }, "schema": "k3d-gpu.audit/v1", "summary": [ - "audit PASS: 0 of 10 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1)" + "audit PASS: 0 of 10 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1)" ], "verdict": "pass" } diff --git a/tests/console/audit/default.golden b/tests/console/audit/default.golden index 9dd398e8..61669446 100644 --- a/tests/console/audit/default.golden +++ b/tests/console/audit/default.golden @@ -1,7 +1,7 @@ -audit PASS: 0 of 10 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit PASS: 0 of 10 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/image-digest-moved.golden b/tests/console/audit/image-digest-moved.golden index 60b32c8f..ae7bb42e 100644 --- a/tests/console/audit/image-digest-moved.golden +++ b/tests/console/audit/image-digest-moved.golden @@ -1,8 +1,8 @@ -audit FAIL: 1 of 10 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 10 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) FAIL image docker.io/intel/intel-gpu-plugin:0.37.1: docker.io/intel/intel-gpu-plugin is sha256:2222222222222222222222222222222222222222222222222222222222222222, images.lock pins sha256:004641b2547d58f1eadce58473fa160ad71377de1eb588cf1f82832387bf3e33 (tag 0.37.1); on k3d-gpu-server-0 fix: redeploy from the pinned digest, or bump the pin with scripts/images-lock update -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/image-no-digest.golden b/tests/console/audit/image-no-digest.golden index 33263aa9..b2430703 100644 --- a/tests/console/audit/image-no-digest.golden +++ b/tests/console/audit/image-no-digest.golden @@ -1,9 +1,9 @@ -audit FAIL: 1 of 11 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 11 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) FAIL image docker.io/library/app:dev: no registry digest, so it cannot match images.lock; on k3d-gpu-server-0 fix: remove it, or load the image from a registry reference that images.lock pins ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/image-not-in-lock.golden b/tests/console/audit/image-not-in-lock.golden index e529e48c..c0d9d3a6 100644 --- a/tests/console/audit/image-not-in-lock.golden +++ b/tests/console/audit/image-not-in-lock.golden @@ -1,9 +1,9 @@ -audit FAIL: 1 of 11 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 11 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) FAIL image docker.io/bitnami/redis:7.4: not in images.lock: docker.io/bitnami/redis; on k3d-gpu-server-0 fix: remove the workload that runs it, or pin it in the repo and run scripts/images-lock update ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/ipv6-unauth.golden b/tests/console/audit/ipv6-unauth.golden index 0f8f1f4c..3710cf04 100644 --- a/tests/console/audit/ipv6-unauth.golden +++ b/tests/console/audit/ipv6-unauth.golden @@ -1,9 +1,9 @@ -audit FAIL: 1 of 11 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 11 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) FAIL listener [::]:8000 vllm (EAT chart, no auth): unauthenticated listener off loopback: no authentication; off loopback, auth none (fixture: EAT#9 layout) fix: bind it to 127.0.0.1, or put OIDC in front of it (k3d-gpu plan --bind picks oidc off loopback) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/kubeconfig-0644.golden b/tests/console/audit/kubeconfig-0644.golden index d02bffb1..2921e599 100644 --- a/tests/console/audit/kubeconfig-0644.golden +++ b/tests/console/audit/kubeconfig-0644.golden @@ -1,7 +1,7 @@ -audit FAIL: 1 of 10 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 10 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/loopback-published.golden b/tests/console/audit/loopback-published.golden index fee6dd21..c2c87a52 100644 --- a/tests/console/audit/loopback-published.golden +++ b/tests/console/audit/loopback-published.golden @@ -1,8 +1,8 @@ -audit PASS: 0 of 11 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit PASS: 0 of 11 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok listener 127.0.0.1:8000 published node port 30080/tcp: loopback, auth unknown (docker port 30080/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/runtime-fetch.golden b/tests/console/audit/runtime-fetch.golden index 49d6db75..c6734f5a 100644 --- a/tests/console/audit/runtime-fetch.golden +++ b/tests/console/audit/runtime-fetch.golden @@ -1,7 +1,7 @@ -audit FAIL: 1 of 11 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 11 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/runtime-pull.golden b/tests/console/audit/runtime-pull.golden index e96d3d8a..3de318b3 100644 --- a/tests/console/audit/runtime-pull.golden +++ b/tests/console/audit/runtime-pull.golden @@ -1,7 +1,7 @@ -audit FAIL: 2 of 12 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 2 of 12 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/audit/unauth-published.golden b/tests/console/audit/unauth-published.golden index 57c09f13..9a68e42b 100644 --- a/tests/console/audit/unauth-published.golden +++ b/tests/console/audit/unauth-published.golden @@ -1,9 +1,9 @@ -audit FAIL: 1 of 11 checks failed (images.lock: 55 images, k3s v1.37.1-k3s1) +audit FAIL: 1 of 11 checks failed (images.lock: 54 images, k3s v1.37.1-k3s1) FAIL listener 0.0.0.0:8000 published node port 30080/tcp: unauthenticated listener off loopback: no authentication this audit can establish; off loopback, auth unknown (docker port 30080/tcp on k3d-gpu-server-0) fix: bind it to 127.0.0.1, or put OIDC in front of it (k3d-gpu plan --bind picks oidc off loopback) ok listener 0.0.0.0:39651 kube-apiserver: off loopback, auth client-cert (docker port 6443/tcp on k3d-gpu-server-0) ok image docker.io/intel/intel-gpu-plugin:0.37.1: sha256:004641b2547d in images.lock (share/intel-gpu-plugin.yml, share/intel-gpu-wsl-plugin.yml); on k3d-gpu-server-0 -ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE); on k3d-gpu-server-0 +ok image docker.io/openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58: sha256:a928903fe429 in images.lock (scripts/k3d-gpu AGENT_OVMS_IMAGE, scripts/k3d-gpu NPU_UMD_IMAGE, share/npu-embeddings.yml); on k3d-gpu-server-0 ok image docker.io/rancher/local-path-provisioner:v0.0.37: sha256:e757967a5ec3 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-coredns-coredns:1.14.7: sha256:7efd3c635b03 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 ok image docker.io/rancher/mirrored-metrics-server:v0.9.0: sha256:d9862115e7c7 in images.lock (k3s-images.txt v1.37.1-k3s1); on k3d-gpu-server-0 diff --git a/tests/console/green-report.html b/tests/console/green-report.html index 44fee0e8..671c4152 100644 --- a/tests/console/green-report.html +++ b/tests/console/green-report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETIBT</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETSS</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.GFNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.SHA</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.VAES</th><td>true</td></tr><tr><th>gpu.intel.com/media-vcs</th><td>2</td></tr><tr><th>gpu.intel.com/media-vecs</th><td>1</td></tr><tr><th>gpu.intel.com/xmx</th><td>false</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>054c57a66f59acbf44280b23d693d17a634b1a9ab433ea016de6812ffd8909da</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>8a92631ab5d6f1a56235ddda444ca29625889d0ffc494f0fd8f617c060aba869</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/console/test_console.py b/tests/console/test_console.py index 65fda26e..3b5aad17 100755 --- a/tests/console/test_console.py +++ b/tests/console/test_console.py @@ -267,6 +267,17 @@ def test_pin_bump_expires_cells(self): self.assertEqual(got["decision"], "unmeasured") self.assertTrue(all("engine pin moved" in " ".join(c["reasons"]) for c in got["considered"])) + def test_tag_beside_the_pinned_digest_keeps_cells(self): + # The release pins name:tag@sha256:X; a cell measured on name@sha256:X ran that image. + name, digest = self.matrix["rows"][0]["engine"]["image"].split("@") + facts = copy.deepcopy(self.facts) + facts["release"]["pins"]["ovms"] = "%s:2026.4.1-gpu@%s" % (name, digest) + self.assertEqual(planmod.plan(facts, self.green())["cell"], "raptorlake-s.cpu-pcores.ovms.qwen3-4b-int4") + facts["release"]["pins"]["ovms"] = "%s:2026.4.1-gpu@sha256:%s" % (name, "0" * 64) + self.assertEqual(planmod.plan(facts, self.green())["decision"], "unmeasured") + self.assertEqual(planmod.image_id("localhost:5000/a/b:1@sha256:" + "1" * 64), "localhost:5000/a/b@sha256:" + "1" * 64) + self.assertEqual(planmod.image_id("localhost:5000/a/b:1"), "localhost:5000/a/b:1") + def test_community_cell_is_amber_and_never_picked(self): m = self.green() for r in m["rows"]: diff --git a/tests/console_test.sh b/tests/console_test.sh index d22f178f..44acc707 100755 --- a/tests/console_test.sh +++ b/tests/console_test.sh @@ -203,7 +203,7 @@ pins() { python3 -c 'import json,sys; p=json.load(sys.stdin)["release"]["pins"]; ovms_pin=$(run facts --json 2>/dev/null | pins) vllm_pin=$(K3D_GPU_AGENT_BACKEND=vllm K3D_GPU_AGENT_IMAGE=example/vllm@sha256:0 run facts --json 2>/dev/null | pins) over_pin=$(K3D_GPU_AGENT_IMAGE=example/ovms@sha256:1 run facts --json 2>/dev/null | pins) -if [[ "${ovms_pin}" == "openvino/model_server@sha256:"*" ghcr.io/ggml-org/llama.cpp@sha256:"* ]] \ +if [[ "${ovms_pin}" == "openvino/model_server:"*"@sha256:"*" ghcr.io/ggml-org/llama.cpp:"*"@sha256:"* ]] \ && [ "${vllm_pin}" = "${ovms_pin}" ] && [ "${over_pin}" = "example/ovms@sha256:1 ${ovms_pin#* }" ]; then ok "facts pins: OVMS and llama.cpp digests; a vLLM backend leaves the OVMS pin alone" else diff --git a/tests/detect_test.sh b/tests/detect_test.sh index 94554468..ef80d697 100755 --- a/tests/detect_test.sh +++ b/tests/detect_test.sh @@ -2576,7 +2576,7 @@ agent_says "default model gets the qwen3 reasoning parser" "${r}" 0 '--reasoning agent_says "an empty K3D_GPU_AGENT_TOOL_PARSER turns the parser off" "${r}" 0 'tool_parser' negate \ STUB_ALLOC='cpu 8' K3D_GPU_AGENT_TOOL_PARSER= agent_says "the model cache lives on the models volume" "${r}" 0 '--cache_dir=/models/\.ov_cache' "" STUB_ALLOC='cpu 8' -agent_says "the server image is pinned by digest" "${r}" 0 'image: openvino/model_server@sha256:[0-9a-f]{64}' "" STUB_ALLOC='cpu 8' +agent_says "the server image is pinned by digest" "${r}" 0 'image: openvino/model_server:[^@ ]+@sha256:[0-9a-f]{64}' "" STUB_ALLOC='cpu 8' agent_says "server logs at INFO by default" "${r}" 0 '--log_level=INFO$' "" STUB_ALLOC='cpu 8' agent_says "K3D_GPU_AGENT_LOG_LEVEL=DEBUG reaches the server" "${r}" 0 '--log_level=DEBUG$' "" \ STUB_ALLOC='cpu 8' K3D_GPU_AGENT_LOG_LEVEL=DEBUG @@ -3267,11 +3267,11 @@ pci "${r}" 0000:03:00.0 0x8086 0xe20b xe; render "${r}" 0000:03:00.0 128 gid=$(stat -c %g "${r}/dev/dri/renderD128") XE1=$'cpu 16\ngpu.intel.com/xe 1' agent_says "vllm backend serves from the upstream vLLM XPU image, pinned by digest" "${r}" 0 \ - 'image: vllm/vllm-openai-xpu@sha256:[0-9a-f]{64}$' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=vllm + 'image: vllm/vllm-openai-xpu:[^@ ]+@sha256:[0-9a-f]{64}$' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=vllm agent_says "llm-scaler backend serves from Intel's llm-scaler image, pinned by digest" "${r}" 0 \ - 'image: intel/llm-scaler-vllm@sha256:[0-9a-f]{64}$' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=llm-scaler + 'image: intel/llm-scaler-vllm:[^@ ]+@sha256:[0-9a-f]{64}$' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=llm-scaler agent_says "the backend name is case-insensitive" "${r}" 0 \ - 'vllm/vllm-openai-xpu@' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=vLLM + 'vllm/vllm-openai-xpu:[^@ ]+@' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=vLLM agent_says "an unknown K3D_GPU_AGENT_BACKEND is rejected" "${r}" fail \ "unknown K3D_GPU_AGENT_BACKEND 'tgi'" "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=tgi agent_says "vllm pod requests one gpu.intel.com/xe" "${r}" 0 \ @@ -3356,7 +3356,7 @@ agent_says "vllm on a Battlemage host prints no hardware warning" "${r}" 0 \ agent_says "vllm on a hybrid node sits on P-cores" "${r}" 0 \ 'k3d-gpu.io/cores: performance' "" STUB_ALLOC="${XE1}" K3D_GPU_AGENT_BACKEND=vllm STUB_NODE_LABELS=k3d-gpu.io/hybrid-cpu=true agent_says "the OVMS default still owns the agent when no backend is set" "${r}" 0 \ - 'image: openvino/model_server@' "" STUB_ALLOC="${XE1}" + 'image: openvino/model_server:[^@ ]+@' "" STUB_ALLOC="${XE1}" agent_says "the agent line names the GPU resource it requests" "${r}" 0 \ 'on GPU \(gpu\.intel\.com/xe\)' "" STUB_ALLOC="${XE1}" STUB_NODE_LABELS=gpu.intel.com/xmx=true agent_says "vllm auto runs on the Intel GPU even where OVMS would pick the CPU (no XMX)" "${r}" 0 \ @@ -3455,7 +3455,7 @@ agent_says "llm-scaler auto skips the Gaudi for the Intel GPU" "${r}" 0 \ agent_says "an explicit vllm backend runs on the Gaudi" "${r}" 0 \ '^ +habana\.ai/gaudi: 1$' "" STUB_ALLOC="${HPU_ALLOC}" K3D_GPU_AGENT_BACKEND=vllm agent_says "the vLLM image is Intel's vllm-gaudi 0.30.0 build pinned by digest" "${r}" 0 \ - 'image: vault\.habana\.ai/gaudi-docker/1\.24\.2/ubuntu24\.04/habanalabs/vllm-0\.30\.0-ptupstream-2\.13\.0@sha256:[0-9a-f]{64}$' "" \ + 'image: vault\.habana\.ai/gaudi-docker/1\.24\.2/ubuntu24\.04/habanalabs/vllm-0\.30\.0-ptupstream-2\.13\.0:[^@ ]+@sha256:[0-9a-f]{64}$' "" \ STUB_ALLOC="${HPU_ALLOC}" agent_says "K3D_GPU_AGENT_IMAGE overrides the Gaudi image" "${r}" 0 \ 'image: example/vllm-gaudi:dev$' "" STUB_ALLOC="${HPU_ALLOC}" K3D_GPU_AGENT_IMAGE=example/vllm-gaudi:dev @@ -3567,7 +3567,7 @@ addon_says "stt NPU pod joins the accel node's group" "${r}" 0 ovms-stt \ addon_says "video GPU pod joins the render group" "${r}" 0 video-analytics \ "supplementalGroups: \[${rgid}\]" "" STUB_ALLOC="${SHARED}" addon_says "add-ons run the OVMS image" "${r}" 0 ovms-stt \ - 'image: openvino/model_server@sha256:[0-9a-f]{64}' "" STUB_ALLOC="${SHARED}" + 'image: openvino/model_server:[^@ ]+@sha256:[0-9a-f]{64}' "" STUB_ALLOC="${SHARED}" addon_says "one unshared iGPU, no XMX: the LLM is on CPU and video gets the iGPU" "${r}" 0 video-analytics \ 'k3d-gpu.io/agent-device: GPU' "" STUB_ALLOC="${ONE}" addon_says "one unshared iGPU, no XMX: vlm falls back to CPU" "${r}" 0 ovms-vlm \ @@ -3658,7 +3658,7 @@ addon_says "vllm on 2 of 3 GPU units leaves video the third" "${r}" 0 video-anal addon_says "... and vlm none" "${r}" 0 ovms-vlm \ '--target_device=CPU$' "" STUB_ALLOC="${XE3}" K3D_GPU_AGENT_BACKEND=vllm K3D_GPU_AGENT_GPUS=2 addon_says "an add-on next to vllm runs the OVMS image, not vLLM's" "${r}" 0 ovms-vlm \ - 'image: openvino/model_server@sha256:[0-9a-f]{64}' "" STUB_ALLOC="${XE3}" K3D_GPU_AGENT_BACKEND=vllm K3D_GPU_AGENT_GPUS=2 + 'image: openvino/model_server:[^@ ]+@sha256:[0-9a-f]{64}' "" STUB_ALLOC="${XE3}" K3D_GPU_AGENT_BACKEND=vllm K3D_GPU_AGENT_GPUS=2 addon_says "a Gaudi LLM leaves the Intel GPU to video" "${r}" 0 video-analytics \ 'k3d-gpu.io/agent-device: GPU' "" STUB_ALLOC=$'cpu 32\nhabana.ai/gaudi 1\ngpu.intel.com/i915 1' @@ -3708,7 +3708,7 @@ addon_says "CPU video leaves decoder ranks alone" "${r}" 0 video-analytics \ addon_says "CPU video requests no GPU" "${r}" 0 video-analytics \ 'gpu\.intel\.com' negate STUB_ALLOC='cpu 32' addon_says "the DL Streamer image is pinned by digest" "${r}" 0 video-analytics \ - 'image: intel/dlstreamer@sha256:[0-9a-f]{64}' "" STUB_ALLOC="${SHARED}" + 'image: intel/dlstreamer:[^@ ]+@sha256:[0-9a-f]{64}' "" STUB_ALLOC="${SHARED}" addon_says "the model is checked against its sha256" "${r}" 0 video-analytics \ 'sha256sum -c' "" STUB_ALLOC="${SHARED}" addon_says "the sample clip is fetched by default" "${r}" 0 video-analytics \ diff --git a/tests/fixtures/alderlake-n/facts.json b/tests/fixtures/alderlake-n/facts.json index 84e6cc79..2fb68a26 100644 --- a/tests/fixtures/alderlake-n/facts.json +++ b/tests/fixtures/alderlake-n/facts.json @@ -317,8 +317,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/alderlake-n/report.html b/tests/fixtures/alderlake-n/report.html index c10c381e..c33e18c1 100644 --- a/tests/fixtures/alderlake-n/report.html +++ b/tests/fixtures/alderlake-n/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETIBT</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.GFNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.SHA</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.VAES</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>false</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>2b930cbab2911420b480d2b923c3dbb95c88201b46725e043e68ed9cf13952c1</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>6526f9de70feefdcb01adbe44b60479a20e2efb66e77fb8cee36fc2733fe5499</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/arrowlake-h/facts.json b/tests/fixtures/arrowlake-h/facts.json index 6d5f132f..73d6d6d7 100644 --- a/tests/fixtures/arrowlake-h/facts.json +++ b/tests/fixtures/arrowlake-h/facts.json @@ -415,8 +415,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/arrowlake-h/report.html b/tests/fixtures/arrowlake-h/report.html index 2c67e60a..d22a34c2 100644 --- a/tests/fixtures/arrowlake-h/report.html +++ b/tests/fixtures/arrowlake-h/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.GFNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.SHA</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.VAES</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>37xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>8532aa7e7bd7b67ce97a3755cb09013288658046f8157b5bc36f252af64c7ccb</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>43ae038c6efdd1e41e8f635c74b586cb3ed0ff09ffe6a9b3c1d4a62798e9511e</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/arrowlake-s/facts.json b/tests/fixtures/arrowlake-s/facts.json index 4a35f064..b4259a98 100644 --- a/tests/fixtures/arrowlake-s/facts.json +++ b/tests/fixtures/arrowlake-s/facts.json @@ -418,8 +418,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/arrowlake-s/report.html b/tests/fixtures/arrowlake-s/report.html index a30d9b97..3c031041 100644 --- a/tests/fixtures/arrowlake-s/report.html +++ b/tests/fixtures/arrowlake-s/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>gpu.intel.com/media-vcs</th><td>2</td></tr><tr><th>gpu.intel.com/media-vecs</th><td>1</td></tr><tr><th>gpu.intel.com/xmx</th><td>false</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>37xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>ddb625631dd4de5919780361ffa6fd879f38947329be0186865e98b877ba1b74</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>b156c482a75ef930c97f31dff50c0468caacf22380df2e5d1cfa89c1efe62503</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/arrowlake-u/facts.json b/tests/fixtures/arrowlake-u/facts.json index 186481b6..4493a9df 100644 --- a/tests/fixtures/arrowlake-u/facts.json +++ b/tests/fixtures/arrowlake-u/facts.json @@ -317,8 +317,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/arrowlake-u/report.html b/tests/fixtures/arrowlake-u/report.html index ebbcdbcd..50c728df 100644 --- a/tests/fixtures/arrowlake-u/report.html +++ b/tests/fixtures/arrowlake-u/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>false</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>d40f851699cb171012acbe6a5511e9a1d83ccefbe2a36546c5ccedefeab67325</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>d7ec644aa6f89fbd206297293f7aa5ee3bfb043d076ee47615ebdd48bdebec43</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/battlemage-b580/facts.json b/tests/fixtures/battlemage-b580/facts.json index bb98b189..a6c3cc55 100644 --- a/tests/fixtures/battlemage-b580/facts.json +++ b/tests/fixtures/battlemage-b580/facts.json @@ -288,8 +288,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/battlemage-b580/report.html b/tests/fixtures/battlemage-b580/report.html index ea7a601a..0fdbc562 100644 --- a/tests/fixtures/battlemage-b580/report.html +++ b/tests/fixtures/battlemage-b580/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>ec0ab6e41e8662ac11862a2beaa021cdecc4d15db3861849e7e4113d0eadc6c6</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>41aac371300c61925593ac6215959ee6896388694ba99a90ede273902f91d5ff</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/battlemage-pro-b60/facts.json b/tests/fixtures/battlemage-pro-b60/facts.json index c2824e9a..cddbb3f0 100644 --- a/tests/fixtures/battlemage-pro-b60/facts.json +++ b/tests/fixtures/battlemage-pro-b60/facts.json @@ -297,8 +297,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/battlemage-pro-b60/report.html b/tests/fixtures/battlemage-pro-b60/report.html index 3dd4cac5..c37cb6ba 100644 --- a/tests/fixtures/battlemage-pro-b60/report.html +++ b/tests/fixtures/battlemage-pro-b60/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>26a17082890ca4743e794b8d4bf1bae8458d83debe6d1d73af5a20328958d1e2</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>d9283c3923e2a30aa3eff1bac81b1ba1720b1d652594c9f33a4ea8596288d889</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/battlemage-pro-b70-k616/facts.json b/tests/fixtures/battlemage-pro-b70-k616/facts.json index a766e272..7b5cc607 100644 --- a/tests/fixtures/battlemage-pro-b70-k616/facts.json +++ b/tests/fixtures/battlemage-pro-b70-k616/facts.json @@ -233,8 +233,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/battlemage-pro-b70-k616/report.html b/tests/fixtures/battlemage-pro-b70-k616/report.html index f8d261ef..fc9c066f 100644 --- a/tests/fixtures/battlemage-pro-b70-k616/report.html +++ b/tests/fixtures/battlemage-pro-b70-k616/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>bd676f6c5297b327cac5efe2eaa30cb446ac281ca3bad518f044341cff2021df</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>46a4c46208c352a55f3cca0dd29e066a41ed0b5f4a247a3007169b9ef176d768</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/dg2-a770-i915/facts.json b/tests/fixtures/dg2-a770-i915/facts.json index 5fb730a7..ca67ab92 100644 --- a/tests/fixtures/dg2-a770-i915/facts.json +++ b/tests/fixtures/dg2-a770-i915/facts.json @@ -288,8 +288,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/dg2-a770-i915/report.html b/tests/fixtures/dg2-a770-i915/report.html index 076b2472..134e9fd3 100644 --- a/tests/fixtures/dg2-a770-i915/report.html +++ b/tests/fixtures/dg2-a770-i915/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>a6da1f00739e5d05847c5b0c2eb579e51746510b28e1a3ae7cce7aaa3986672b</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>a6d9142d067e033cadbf23097f11bc33b4dd654693459545ce809fd2fa2d13f1</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/flex140-unbound/facts.json b/tests/fixtures/flex140-unbound/facts.json index 11b628ef..ce059f3c 100644 --- a/tests/fixtures/flex140-unbound/facts.json +++ b/tests/fixtures/flex140-unbound/facts.json @@ -233,8 +233,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/flex140-unbound/report.html b/tests/fixtures/flex140-unbound/report.html index 7129f903..e71a0b93 100644 --- a/tests/fixtures/flex140-unbound/report.html +++ b/tests/fixtures/flex140-unbound/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>c983e76aa4c8929ee6a71d53ddfc8948ac59b6fc37f793fdd64ca51d18e6e216</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>fe892d98c00884c4a8ef0df72929bce8c8ca2f6a0fffef04e0bdebb011de1c1d</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/gaudi3-hls/facts.json b/tests/fixtures/gaudi3-hls/facts.json index 50e62ac8..df64ce9d 100644 --- a/tests/fixtures/gaudi3-hls/facts.json +++ b/tests/fixtures/gaudi3-hls/facts.json @@ -327,8 +327,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/gaudi3-hls/report.html b/tests/fixtures/gaudi3-hls/report.html index a7d864cd..3d4049e5 100644 --- a/tests/fixtures/gaudi3-hls/report.html +++ b/tests/fixtures/gaudi3-hls/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>d65aee54a4011e536eeb8b16d997fa9d72363403f7e2c1c0ad07d4e41801032f</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>bbc164e3895e64e1c9066b2f4a8452fa6aa204b422098996701948d88f75b07a</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/gaudi3-no-driver/facts.json b/tests/fixtures/gaudi3-no-driver/facts.json index 25ae844e..12088b8c 100644 --- a/tests/fixtures/gaudi3-no-driver/facts.json +++ b/tests/fixtures/gaudi3-no-driver/facts.json @@ -307,8 +307,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/gaudi3-no-driver/report.html b/tests/fixtures/gaudi3-no-driver/report.html index 5fc1fe7d..00bc0bf8 100644 --- a/tests/fixtures/gaudi3-no-driver/report.html +++ b/tests/fixtures/gaudi3-no-driver/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>966833f96e2ede1ebed22a48779de01a462adfc3b037b09ea56b4f0699a9e159</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>01a8a00a37f7cd1d4ad889c95910330ceb3dfd371a0cef33f528de6af52885b6</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/lunarlake-k6.8/facts.json b/tests/fixtures/lunarlake-k6.8/facts.json index fdef4a29..54ee8867 100644 --- a/tests/fixtures/lunarlake-k6.8/facts.json +++ b/tests/fixtures/lunarlake-k6.8/facts.json @@ -324,8 +324,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/lunarlake-k6.8/report.html b/tests/fixtures/lunarlake-k6.8/report.html index cdbd5047..312d03e2 100644 --- a/tests/fixtures/lunarlake-k6.8/report.html +++ b/tests/fixtures/lunarlake-k6.8/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>40xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>668d338fa97bc522d22f9f4894e9172986f6b4b63619c1175c85f796a6291e55</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>dfae7bb57ced7214194884a9768bcc777c0fd1306319485eea5a39dbbdb9b245</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/lunarlake/facts.json b/tests/fixtures/lunarlake/facts.json index 8c0e46f2..f252df0e 100644 --- a/tests/fixtures/lunarlake/facts.json +++ b/tests/fixtures/lunarlake/facts.json @@ -391,8 +391,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/lunarlake/report.html b/tests/fixtures/lunarlake/report.html index 76df00e1..c1d7d299 100644 --- a/tests/fixtures/lunarlake/report.html +++ b/tests/fixtures/lunarlake/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>40xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>6dad913789b0eb1ae10cfd3bf161eb4f53ed8d940817d6b7b04c40a4f7b91987</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>f096c935cade5df6cda8396e12ff05988855227ced8d620ffa302505aef37f03</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/meteorlake/facts.json b/tests/fixtures/meteorlake/facts.json index 8298d157..b774d530 100644 --- a/tests/fixtures/meteorlake/facts.json +++ b/tests/fixtures/meteorlake/facts.json @@ -241,8 +241,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/meteorlake/report.html b/tests/fixtures/meteorlake/report.html index 6e5dcbf6..a1eae70d 100644 --- a/tests/fixtures/meteorlake/report.html +++ b/tests/fixtures/meteorlake/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>5260ecb4bda99ec68bf1b2e9f63726d38b140d66ed4fa430322da3bada11e1a4</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>52b3b8c246142445cff5399c86ff5322df56f11066f9dd3dd3db638db0c0935d</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/novalake/facts.json b/tests/fixtures/novalake/facts.json index 31d1d456..a02f37d0 100644 --- a/tests/fixtures/novalake/facts.json +++ b/tests/fixtures/novalake/facts.json @@ -255,8 +255,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/novalake/report.html b/tests/fixtures/novalake/report.html index 62248b68..50c9bd0d 100644 --- a/tests/fixtures/novalake/report.html +++ b/tests/fixtures/novalake/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>cac5ad3082233ecad4b069c780e046f78b273b5921f4df98667ccf8a49bb2a2f</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>ca0f14dec060ec358a7c030c94c142ce1a325fea6a844221087de1be7520cfab</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/npu-unserved/facts.json b/tests/fixtures/npu-unserved/facts.json index a32050ff..1c9e2756 100644 --- a/tests/fixtures/npu-unserved/facts.json +++ b/tests/fixtures/npu-unserved/facts.json @@ -399,8 +399,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/npu-unserved/report.html b/tests/fixtures/npu-unserved/report.html index 0e262939..78b96bf5 100644 --- a/tests/fixtures/npu-unserved/report.html +++ b/tests/fixtures/npu-unserved/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>e28d58bbb5d22637ec1745bc1341da9fb24e2bdfb60dc70318a4f251565083a7</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>5a3fe962d31ce27f3942ff3ae1c5de9c3e21c28ff7a620a8cf962d273429c70c</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/pantherlake-k6.14/facts.json b/tests/fixtures/pantherlake-k6.14/facts.json index 9b6d2d87..89f4ac0e 100644 --- a/tests/fixtures/pantherlake-k6.14/facts.json +++ b/tests/fixtures/pantherlake-k6.14/facts.json @@ -333,8 +333,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/pantherlake-k6.14/report.html b/tests/fixtures/pantherlake-k6.14/report.html index 56a9665e..931514cc 100644 --- a/tests/fixtures/pantherlake-k6.14/report.html +++ b/tests/fixtures/pantherlake-k6.14/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>50xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>6f47e914244eb2826ee5b52720ab279f6a388cf90e7fff5d53720cd07500e5e7</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>55ba54ab0682106a92da68a7415fffa8fd462b26d8ab4457c135ec42f69f7525</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/pantherlake/facts.json b/tests/fixtures/pantherlake/facts.json index 50ee9b46..c08b37b3 100644 --- a/tests/fixtures/pantherlake/facts.json +++ b/tests/fixtures/pantherlake/facts.json @@ -424,8 +424,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/pantherlake/report.html b/tests/fixtures/pantherlake/report.html index 9e4d05ab..851e5d8d 100644 --- a/tests/fixtures/pantherlake/report.html +++ b/tests/fixtures/pantherlake/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>gpu.intel.com/xmx</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>50xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>996be87a6dd3893eb0f3480f94908259a065a7bd013ae42a58b0a4733464e975</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>40bf73af9d959da029449659c0ed2054d9cb58e95b2bbb375a33b1db7eb99ffe</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/pvc-max1550-unbound/facts.json b/tests/fixtures/pvc-max1550-unbound/facts.json index 9e8af6fb..b56544ef 100644 --- a/tests/fixtures/pvc-max1550-unbound/facts.json +++ b/tests/fixtures/pvc-max1550-unbound/facts.json @@ -233,8 +233,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/pvc-max1550-unbound/report.html b/tests/fixtures/pvc-max1550-unbound/report.html index 819c6e1f..d2055c4a 100644 --- a/tests/fixtures/pvc-max1550-unbound/report.html +++ b/tests/fixtures/pvc-max1550-unbound/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>c83b978ce6ddd252191e63c6ea1df8c1455fed1ba485a0d8a6d14b78398a4be5</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>f5750c1f5808ab83092c439b7d0ac4f3792354191daee7741d9a374c4eab3c0e</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/qat-420xx/facts.json b/tests/fixtures/qat-420xx/facts.json index b7a29ed2..4d4d66ab 100644 --- a/tests/fixtures/qat-420xx/facts.json +++ b/tests/fixtures/qat-420xx/facts.json @@ -301,8 +301,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/qat-420xx/report.html b/tests/fixtures/qat-420xx/report.html index 5f392888..b739860f 100644 --- a/tests/fixtures/qat-420xx/report.html +++ b/tests/fixtures/qat-420xx/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>69cda846109fa15861ae5e640ba9c0a7f2b165b9e58c86d3223a179e38bc5e2c</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>130f537378904603987e7b41b24350e61d7f2b56bf9cc8ca0457b5c622200688</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/raptorlake-nvidia/facts.json b/tests/fixtures/raptorlake-nvidia/facts.json index 5c048252..e7512d79 100644 --- a/tests/fixtures/raptorlake-nvidia/facts.json +++ b/tests/fixtures/raptorlake-nvidia/facts.json @@ -937,8 +937,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/raptorlake-nvidia/report.html b/tests/fixtures/raptorlake-nvidia/report.html index b7eb7297..b70e8a4f 100644 --- a/tests/fixtures/raptorlake-nvidia/report.html +++ b/tests/fixtures/raptorlake-nvidia/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETIBT</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETSS</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.GFNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.SHA</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.VAES</th><td>true</td></tr><tr><th>gpu.intel.com/media-vcs</th><td>2</td></tr><tr><th>gpu.intel.com/media-vecs</th><td>1</td></tr><tr><th>gpu.intel.com/xmx</th><td>false</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>054c57a66f59acbf44280b23d693d17a634b1a9ab433ea016de6812ffd8909da</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>8a92631ab5d6f1a56235ddda444ca29625889d0ffc494f0fd8f617c060aba869</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/raptorlake-stale-guc/facts.json b/tests/fixtures/raptorlake-stale-guc/facts.json index 3f09d63a..c8cc730d 100644 --- a/tests/fixtures/raptorlake-stale-guc/facts.json +++ b/tests/fixtures/raptorlake-stale-guc/facts.json @@ -943,8 +943,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/raptorlake-stale-guc/report.html b/tests/fixtures/raptorlake-stale-guc/report.html index 61a0710c..4ea32aea 100644 --- a/tests/fixtures/raptorlake-stale-guc/report.html +++ b/tests/fixtures/raptorlake-stale-guc/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETIBT</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETSS</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.GFNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.SHA</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.VAES</th><td>true</td></tr><tr><th>gpu.intel.com/media-vcs</th><td>2</td></tr><tr><th>gpu.intel.com/media-vecs</th><td>1</td></tr><tr><th>gpu.intel.com/xmx</th><td>false</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>49896a2c68669c84b35febc66b3c9735917e1344d96ad42ea06855f87c6b1377</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>7bbafa05717868924d5e68b0ae240a2efe5ab4fb09fa73b48fe27f231e67f740</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/wildcatlake/facts.json b/tests/fixtures/wildcatlake/facts.json index 60d1aff5..9e35a9b3 100644 --- a/tests/fixtures/wildcatlake/facts.json +++ b/tests/fixtures/wildcatlake/facts.json @@ -324,8 +324,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/wildcatlake/report.html b/tests/fixtures/wildcatlake/report.html index 93b338d3..1b6a7ddd 100644 --- a/tests/fixtures/wildcatlake/report.html +++ b/tests/fixtures/wildcatlake/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/hybrid-cpu</th><td>true</td></tr><tr><th>k3d-gpu.io/npu.gen</th><td>50xx</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>7df3056a544fc9016caf3591f3cfe7b7c6ffae5519eb37bc53e8961a54706252</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>16c68833adddf2734068c678805419b819deaed384b0598edd2f5602b682b721</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-6741p/facts.json b/tests/fixtures/xeon-6741p/facts.json index e9b7a53d..8641f05c 100644 --- a/tests/fixtures/xeon-6741p/facts.json +++ b/tests/fixtures/xeon-6741p/facts.json @@ -700,8 +700,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-6741p/report.html b/tests/fixtures/xeon-6741p/report.html index 1e949e2b..b614fa92 100644 --- a/tests/fixtures/xeon-6741p/report.html +++ b/tests/fixtures/xeon-6741p/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXFP16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETIBT</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.CETSS</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.GFNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.SHA</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.VAES</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>60ec986919cd95d11ba63f06ba046f83e5aadaba18131fd1fa8afcf64dffb295</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>a39b2508326465278d26c28571ec420c579ca0ade89e110ddded9a0e1e83c1cd</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-dmr/facts.json b/tests/fixtures/xeon-dmr/facts.json index 73e79d8f..7b23e33e 100644 --- a/tests/fixtures/xeon-dmr/facts.json +++ b/tests/fixtures/xeon-dmr/facts.json @@ -357,8 +357,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-dmr/report.html b/tests/fixtures/xeon-dmr/report.html index e47fa548..517ce512 100644 --- a/tests/fixtures/xeon-dmr/report.html +++ b/tests/fixtures/xeon-dmr/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXCOMPLEX</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXFP16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/numa-nodes</th><td>6</td></tr><tr><th>k3d-gpu.io/snc</th><td>3</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>cda4f02db0079e13cec29eb5fcaf42f2097d3697cd5e8aee855f3052ddacf2e2</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>b61e0e9220b03e588f201ae54b1019d72bd3121ea4e50374abf7356c104abdb9</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-gnr/facts.json b/tests/fixtures/xeon-gnr/facts.json index bf3a407f..1dc6a79b 100644 --- a/tests/fixtures/xeon-gnr/facts.json +++ b/tests/fixtures/xeon-gnr/facts.json @@ -341,8 +341,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-gnr/report.html b/tests/fixtures/xeon-gnr/report.html index 2a593561..1551373c 100644 --- a/tests/fixtures/xeon-gnr/report.html +++ b/tests/fixtures/xeon-gnr/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXFP16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/numa-nodes</th><td>6</td></tr><tr><th>k3d-gpu.io/snc</th><td>3</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>922bb3ba1b40901aad7237fbdefc6fa9e2bf084813ee38858255d60084cb9e3f</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>2d02c65b8ecc0936f170fb4575b6691284f141594b094c41ce88b41a4d9d3d1e</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-gnrd/facts.json b/tests/fixtures/xeon-gnrd/facts.json index 742481e6..420b201c 100644 --- a/tests/fixtures/xeon-gnrd/facts.json +++ b/tests/fixtures/xeon-gnrd/facts.json @@ -283,8 +283,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-gnrd/report.html b/tests/fixtures/xeon-gnrd/report.html index acbdf474..3dfbe8d2 100644 --- a/tests/fixtures/xeon-gnrd/report.html +++ b/tests/fixtures/xeon-gnrd/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXCOMPLEX</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXFP16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>f85133b66c65ba456cfa2e54ffebb478b900b5fde57bcf0b88a45897c2f66d30</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>f0bb962cb22f61a68bb3c603ed64a8572d55991b48f216809ad2b570500ce16a</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-max-hbm-cache/facts.json b/tests/fixtures/xeon-max-hbm-cache/facts.json index ec317f91..97a657bb 100644 --- a/tests/fixtures/xeon-max-hbm-cache/facts.json +++ b/tests/fixtures/xeon-max-hbm-cache/facts.json @@ -289,8 +289,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-max-hbm-cache/report.html b/tests/fixtures/xeon-max-hbm-cache/report.html index ca45f5c0..9f1af6b7 100644 --- a/tests/fixtures/xeon-max-hbm-cache/report.html +++ b/tests/fixtures/xeon-max-hbm-cache/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/numa-nodes</th><td>4</td></tr><tr><th>k3d-gpu.io/snc</th><td>4</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>e91bd3cfb1e9fbe450ee21042931894e3f99659092ebb1cd615b8bdc0071995a</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>d4d51a38a63e6a24ee95c8c87fcfcefcebdb2ba9d2f5222c5adae42481e0479d</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-max-hbm-devdax/facts.json b/tests/fixtures/xeon-max-hbm-devdax/facts.json index 0b1d1a25..1ef147ad 100644 --- a/tests/fixtures/xeon-max-hbm-devdax/facts.json +++ b/tests/fixtures/xeon-max-hbm-devdax/facts.json @@ -280,8 +280,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-max-hbm-devdax/report.html b/tests/fixtures/xeon-max-hbm-devdax/report.html index 12534665..d0a254d7 100644 --- a/tests/fixtures/xeon-max-hbm-devdax/report.html +++ b/tests/fixtures/xeon-max-hbm-devdax/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/numa-nodes</th><td>4</td></tr><tr><th>k3d-gpu.io/snc</th><td>4</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>3c25569f4da4ea55051255d0126460d91ccefb9a433edbfcfca7ec129fd85589</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>f55ed5113868a06c3c470168045fa839e713f32236f751885f51e771ae9983cc</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-max-hbm/facts.json b/tests/fixtures/xeon-max-hbm/facts.json index df2a2584..dfc4a61e 100644 --- a/tests/fixtures/xeon-max-hbm/facts.json +++ b/tests/fixtures/xeon-max-hbm/facts.json @@ -280,8 +280,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-max-hbm/report.html b/tests/fixtures/xeon-max-hbm/report.html index b4d9222f..aef54b95 100644 --- a/tests/fixtures/xeon-max-hbm/report.html +++ b/tests/fixtures/xeon-max-hbm/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/numa-nodes</th><td>8</td></tr><tr><th>k3d-gpu.io/snc</th><td>4</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>652e964a1a73480e6c1d2b64e770173433890efe380a4637590fb35918a9540f</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>5da89e8f3ff16239433d1a8870f863c37dd1390ec27ce45231f3715a28f84110</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-spr-accel/facts.json b/tests/fixtures/xeon-spr-accel/facts.json index fc30d70c..49b2180c 100644 --- a/tests/fixtures/xeon-spr-accel/facts.json +++ b/tests/fixtures/xeon-spr-accel/facts.json @@ -350,8 +350,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-spr-accel/report.html b/tests/fixtures/xeon-spr-accel/report.html index caaaeeac..e4512d0e 100644 --- a/tests/fixtures/xeon-spr-accel/report.html +++ b/tests/fixtures/xeon-spr-accel/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXBF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXINT8</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AMXTILE</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512BF16</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512F</th><td>true</td></tr><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVX512VNNI</th><td>true</td></tr><tr><th>k3d-gpu.io/numa-nodes</th><td>2</td></tr><tr><th>k3d-gpu.io/snc</th><td>1</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>d575e4efe5d42a9f117402d4ba92c9408fca8aa178ee492fe7a00068fbb4190f</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>dbed407a6bf091e750310daa94c35a7c5cf59d51a071c87d1a6b25a31a27f9d2</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/fixtures/xeon-srf/facts.json b/tests/fixtures/xeon-srf/facts.json index 53c7abf0..8eb18ba4 100644 --- a/tests/fixtures/xeon-srf/facts.json +++ b/tests/fixtures/xeon-srf/facts.json @@ -264,8 +264,8 @@ "release": { "k3d_gpu_version": "<version>", "pins": { - "llama.cpp": "ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", - "ovms": "openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" + "llama.cpp": "ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e", + "ovms": "openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58" } }, "schema": "k3d-gpu.facts/v1", diff --git a/tests/fixtures/xeon-srf/report.html b/tests/fixtures/xeon-srf/report.html index 8d37a6f9..8bf558fa 100644 --- a/tests/fixtures/xeon-srf/report.html +++ b/tests/fixtures/xeon-srf/report.html @@ -67,9 +67,9 @@ <h2>Detected</h2> <h2>Node labels</h2> <div class="wrap"><table><tbody><tr><th>feature.node.kubernetes.io/cpu-cpuid.AVXVNNI</th><td>true</td></tr></tbody></table></div> <h2>Release pins</h2> -<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> +<div class="wrap"><table><tbody><tr><th>llama.cpp</th><td>ghcr.io/ggml-org/llama.cpp:server-vulkan-b11960@sha256:c9aba1a673382ad356a72fb63e6cd73e8c021797e904b6c099bfe130217df62e</td></tr><tr><th>ovms</th><td>openvino/model_server:2026.4.1-gpu@sha256:a928903fe429f1e3e27107cc45932d30d95febfd2d1159dc50869c3eac76fc58</td></tr></tbody></table></div> <footer> -<p>Rendered from facts/v1, sha256 of its canonical bytes <code>99404eb97e388bf6f49faa070d8723f9ebdfa30643b43b51a12cd2d30b6fbbaa</code>, and plan/v1.</p> +<p>Rendered from facts/v1, sha256 of its canonical bytes <code>b29b9d9777f36e1775f4870c3e88a8f0256d99d0a2ceb833fde27183a3ec1534</code>, and plan/v1.</p> <p>This page runs no script and loads nothing: its Content-Security-Policy is <code>default-src 'none'</code>. The links are references to follow, not resources it fetches.</p> <p><a href="https://github.com/88plug/k3d-gpu" rel="noreferrer">https://github.com/88plug/k3d-gpu</a></p> </footer></main></body></html> diff --git a/tests/image_pins_test.sh b/tests/image_pins_test.sh index a30ac4a4..a3d3e6b5 100755 --- a/tests/image_pins_test.sh +++ b/tests/image_pins_test.sh @@ -39,10 +39,14 @@ else fail "images-lock pins on the repo"; sed 's/^/ | /' <<< "${out}"; fi # scratch: a root holding the images-lock tool and an allow file with one entry. scratch() { - rm -rf "${WORK}/r"; mkdir -p "${WORK}/r/scripts" "${WORK}/r/share" + rm -rf "${WORK}/r"; mkdir -p "${WORK}/r/scripts" "${WORK}/r/share" "${WORK}/r/hack" "${WORK}/r/tests" cp "${REPO}/scripts/images-lock" "${WORK}/r/scripts/" + cp "${REPO}/hack/pin-classes" "${WORK}/r/hack/" + cp "${REPO}/tests/ledger_drift_check.py" "${WORK}/r/tests/" cp -r "${REPO}/console" "${WORK}/r/" printf 'share/local.yml app/local:dev built on the host, never pulled\n' > "${WORK}/r/images.unpinned" + mkdir -p "${WORK}/r/pins" + printf 'console/matrix.v1.json historical the measured cells (digest-only records)\n' > "${WORK}/r/pins/classes" printf -- '- image: app/local:dev\n' > "${WORK}/r/share/local.yml" } @@ -90,8 +94,14 @@ pins_case "name:tag@sha256 references" ok \ "printf -- '- image: redis:7.4@${D1}\nFROM debian:trixie-slim@${D2}\n' > share/a.yml" pins_case "a FROM whose ARG default is pinned" ok \ "printf 'ARG UBUNTU_IMAGE=\"ubuntu:26.04@${D1}\"\nFROM \${UBUNTU_IMAGE}\n' > Dockerfile" -pins_case "digest-only references" ok \ +pins_case "a digest-only reference in an ours file" "scripts/run:1: openvino/model_server@${D1} has no tag" \ "printf 'AGENT_OVMS_IMAGE=\"openvino/model_server@${D1}\"\n' > scripts/run" +pins_case "a digest-only reference in an eat-stock file" "integrations/eat/run:1: intel/x@${D1} has no tag" \ + "mkdir -p integrations/eat; printf 'integrations/eat/** eat-stock EAT\n' >> pins/classes; printf 'X_IMAGE=intel/x@${D1}\n' > integrations/eat/run" +pins_case "a digest-only reference images.unpinned allows" ok \ + "printf 'AGENT_OVMS_IMAGE=\"openvino/model_server@${D1}\"\n' > scripts/run; printf 'scripts/run openvino/model_server@* a test input\n' >> images.unpinned" +pins_case "digest-only references in ledger and historical files (records)" ok \ + "mkdir -p bench/h bench/l; printf 'bench/h/** historical done\nbench/l/** ledger H1 amend=X a row\n' >> pins/classes; printf 'IMG=openvino/model_server@${D1}\n' | tee bench/h/run.sh > bench/l/run.sh" pins_case "variables" ok \ "printf -- '- image: \${AGENT_IMAGE}\n- image: \"@VLLM_IMAGE@\"\n- image: {image}\n' > share/a.yml" pins_case "a CRD schema image: key" ok \ @@ -235,6 +245,179 @@ if out=$("${WORK}/r/scripts/images-lock" check --exclude 'x' --root "${WORK}/r" elif grep -qF -- '--exclude' <<< "${out}"; then ok "--exclude is a repin option only" else fail "check --exclude failed for another reason"; sed 's/^/ | /' <<< "${out}"; fi +# ---- repin and drift leave ledger and historical paths (pins/classes) alone ---- +# pyrun <python>: run python with `m` (scratch images-lock) loaded, the registry stubbed +# by STUB (a dict name:tag -> digest, else D2), and argv[1:] = root, D1, D2. +pyrun() { + python3 - "${WORK}/r" "${D1}" "${D2}" <<EOF +import importlib.machinery, importlib.util, sys +root, d1, d2 = sys.argv[1:] +loader = importlib.machinery.SourceFileLoader("images_lock", root + "/scripts/images-lock") +spec = importlib.util.spec_from_loader("images_lock", loader) +m = importlib.util.module_from_spec(spec) +loader.exec_module(m) +STUB = {} +m.registry_digest = lambda name, tag: STUB.get("%s:%s" % (name, tag), d2) +$1 +EOF +} +scratch +mkdir -p "${WORK}/r/pins" "${WORK}/r/bench/h" "${WORK}/r/bench/l" +printf 'bench/h/** historical a finished run\nbench/l/** ledger H1 amend=X a row\nhistorical-ref scripts/mixed.sh keep-me\n' >> "${WORK}/r/pins/classes" +for f in bench/h/run.sh bench/l/run.sh scripts/mixed.sh share/a.yml; do + printf -- '- image: alpine:3.22@%s\n' "${D1}" > "${WORK}/r/${f}" +done +echo "keep-me" >> "${WORK}/r/scripts/mixed.sh" +cp -r "${WORK}/r" "${WORK}/before" +drift_out=$(pyrun 'sys.exit(m.main(["images-lock", "drift", "--root", root]))' 2>&1); rc=$? +if [ "${rc}" -eq 0 ] && grep -qxF "share/a.yml:1: alpine:3.22 ${D1} -> ${D2}" <<< "${drift_out}" \ + && ! grep -qE 'bench/|mixed' <<< "${drift_out}" && diff -r -x __pycache__ "${WORK}/before" "${WORK}/r" > /dev/null; then + ok "drift lists a moved pin, writes nothing and skips ledger, historical and historical-ref files" +else fail "drift (exit ${rc})"; sed 's/^/ | /' <<< "${drift_out}"; fi +repin_out=$(pyrun 'sys.exit(m.main(["images-lock", "repin", "--root", root]))' 2>&1); rc=$? +if [ "${rc}" -eq 0 ] && [ "$(cat "${WORK}/r/share/a.yml")" = "- image: alpine:3.22@${D2}" ] \ + && cmp -s "${WORK}/before/bench/h/run.sh" "${WORK}/r/bench/h/run.sh" \ + && cmp -s "${WORK}/before/bench/l/run.sh" "${WORK}/r/bench/l/run.sh" \ + && cmp -s "${WORK}/before/scripts/mixed.sh" "${WORK}/r/scripts/mixed.sh"; then + ok "repin leaves ledger, historical and historical-ref files untouched and moves the rest" +else fail "repin touched a ledger or historical file (exit ${rc})"; sed 's/^/ | /' <<< "${repin_out}"; fi +out=$(pyrun 'sys.exit(m.main(["images-lock", "repin", "--root", root, "bench/l/run.sh"]))' 2>&1) +if cmp -s "${WORK}/before/bench/l/run.sh" "${WORK}/r/bench/l/run.sh"; then ok "repin FILE skips a ledger file named on the command line" +else fail "repin FILE rewrote a ledger file"; sed 's/^/ | /' <<< "${out}"; fi + +# ---- update --only FILE...: one drifted digest elsewhere does not block a bump ---- +scratch +printf 'ARG K3S_TAG=v1.37.1-k3s1\nFROM debian:trixie-slim@%s\n' "${D1}" > "${WORK}/r/Dockerfile" +: > "${WORK}/r/scripts/k3d-gpu" +printf -- '- image: quay.io/a/a:1@%s\n' "${D1}" > "${WORK}/r/share/a.yml" +printf -- '- image: quay.io/b/b:1@%s\n' "${D1}" > "${WORK}/r/share/b.yml" +rm "${WORK}/r/share/local.yml"; : > "${WORK}/r/images.unpinned" +# update_case NAME WANT_RC GREP [ARGS...]: `update ARGS` in the scratch root. Every +# registry stub serves D1 except the name:tag refs DRIFT lists (they serve D2); +# k3s_images is stubbed and prints when it is called. GREP '!re' wants no match. +update_case() { + local name=$1 want=$2 pat=$3 out rc hit + shift 3 + out=$(update_args "$@" 2>&1); rc=$? + case "${pat}" in + "") hit=0 ;; + !*) ! grep -qE -- "${pat#!}" <<< "${out}"; hit=$? ;; + *) grep -qE -- "${pat}" <<< "${out}"; hit=$? ;; + esac + if [ "${rc}" = "${want}" ] && [ "${hit}" = 0 ]; then ok "${name}" + else fail "${name} (exit ${rc}, want ${want})"; sed 's/^/ | /' <<< "${out}"; fi +} +update_args() { + DRIFT="${DRIFT:-}" python3 - "${WORK}/r" "${D1}" "${D2}" "$@" <<'EOF' +import importlib.machinery, importlib.util, os, sys +root, d1, d2 = sys.argv[1:4] +loader = importlib.machinery.SourceFileLoader("images_lock", root + "/scripts/images-lock") +spec = importlib.util.spec_from_loader("images_lock", loader) +m = importlib.util.module_from_spec(spec) +loader.exec_module(m) +drift = os.environ.get("DRIFT", "").split() +m.registry_digest = lambda name, tag: d2 if "%s:%s" % (name, tag) in drift else d1 +def k3s(tag): + print("k3s-images.txt fetched for " + tag) + return ["docker.io/rancher/mirrored-pause:3.6"] +m.k3s_images = k3s +sys.exit(m.main(["images-lock", "update", "--root", root] + sys.argv[4:])) +EOF +} +update_case "update writes images.lock (all registries agree)" 0 "k3s-images.txt fetched for v1.37.1-k3s1" +cp "${WORK}/r/images.lock" "${WORK}/lock.before" +DRIFT="quay.io/b/b:1" update_case "update without --only fails on a drifted digest anywhere" 2 \ + "quay.io/b/b:1: the repo pins ${D1} but the registry serves ${D2}" +DRIFT="quay.io/b/b:1" update_case "update --only share/a.yml ignores the drift in share/b.yml" 0 "" --only share/a.yml +if cmp -s "${WORK}/lock.before" "${WORK}/r/images.lock"; then ok "update --only keeps the entries of files it does not name" +else fail "update --only rewrote other entries"; diff "${WORK}/lock.before" "${WORK}/r/images.lock" | sed 's/^/ | /'; fi +DRIFT="quay.io/b/b:1" DRIFT="quay.io/b/b:1" update_case "update --only does not fetch k3s-images.txt unless the Dockerfile is named" 0 \ + "!k3s-images.txt fetched" --only share/a.yml +DRIFT="quay.io/b/b:1" update_case "update --only share/b.yml still validates the file it names" 2 \ + "quay.io/b/b:1: the repo pins ${D1} but the registry serves ${D2}" --only share/b.yml +DRIFT="quay.io/b/b:1" update_case "update --only Dockerfile refreshes k3s-images.txt" 0 "k3s-images.txt fetched" --only Dockerfile +printf -- '- image: quay.io/c/c:1@%s\n' "${D1}" > "${WORK}/r/share/c.yml" +update_case "update --only resolves a new reference of a named file" 0 "" --only share/c.yml +if grep -qF '"name": "quay.io/c/c"' "${WORK}/r/images.lock"; then ok "update --only adds the named file's new reference" +else fail "update --only did not add quay.io/c/c"; fi +printf -- '- image: quay.io/d/d:1@%s\n' "${D1}" > "${WORK}/r/share/d.yml" +update_case "update --only refuses a reference images.lock lacks from a file it does not name" 2 \ + "quay.io/d/d:1 .*share/d.yml.* not in images.lock" --only share/c.yml +rm "${WORK}/r/share/d.yml" +printf -- '- image: quay.io/a/a:1@%s\n' "${D2}" > "${WORK}/r/share/a.yml" +update_case "update --only refuses a digest images.lock does not hold, from a file it does not name" 2 \ + "quay.io/a/a:1 .*share/a.yml.* pins ${D2}, images.lock has ${D1}" --only share/c.yml +printf -- '- image: quay.io/a/a:1@%s\n' "${D1}" > "${WORK}/r/share/a.yml" +printf -- '- image: quay.io/d/d:1@%s\n' "${D1}" > "${WORK}/r/share/d.yml" +sed -i 's/v1.37.1-k3s1/v1.37.2-k3s1/' "${WORK}/r/Dockerfile" +update_case "update --only refuses a moved K3S_TAG unless the Dockerfile is named" 2 "K3S_TAG" --only share/d.yml + +# ---- http(): 429 and 5xx retried, Retry-After honoured (capped), other statuses not ---- +if out=$(python3 - "${REPO}" 2>&1 <<'EOF' +import http.server, importlib.machinery, importlib.util, sys, threading, urllib.error, urllib.request +loader = importlib.machinery.SourceFileLoader("images_lock", sys.argv[1] + "/scripts/images-lock") +spec = importlib.util.spec_from_loader("images_lock", loader) +m = importlib.util.module_from_spec(spec) +loader.exec_module(m) +PLAN, SLEPT = [], [] +class H(http.server.BaseHTTPRequestHandler): + def do_HEAD(self): + code, retry = PLAN.pop(0) if PLAN else (200, None) + self.send_response(code) + if retry is not None: + self.send_header("Retry-After", retry) + if code == 200: + self.send_header("Docker-Content-Digest", "sha256:" + "3" * 64) + self.end_headers() + def log_message(self, *a): + pass +srv = http.server.HTTPServer(("127.0.0.1", 0), H) +threading.Thread(target=srv.serve_forever, daemon=True).start() +url = "http://127.0.0.1:%d/v2/x/y/manifests/1" % srv.server_port +m.time.sleep = SLEPT.append +def get(): + with m.http(urllib.request.Request(url, method="HEAD")) as r: + return r.headers["Docker-Content-Digest"] +def case(plan, slept, ok=True): + PLAN[:] = plan + SLEPT.clear() + try: + got = get() + except urllib.error.HTTPError as e: + got = e.code + assert (got == "sha256:" + "3" * 64) == ok, (plan, got) + assert SLEPT == slept, (plan, SLEPT, slept) + return got +case([(429, "7")], [7]) +case([(503, None), (502, None)], [2, 4]) +case([(429, "500")], [120]) +case([(429, "Wed, 21 Oct 2015 07:28:00 GMT")], [0]) +assert case([(429, "1")] * 5, [1, 1, 1, 1], ok=False) == 429 +assert case([(404, None)], [], ok=False) == 404 +print("ok") +EOF +); then ok "http() retries 429/5xx up to 5 attempts, honours Retry-After (seconds or date, capped at 120 s), not 404" +else fail "http() retry"; sed 's/^/ | /' <<< "${out}"; fi + +# ---- the repo: pins and repin read the CI tool pins (hack/ci-images.env, hack/ci-tools.env) ---- +if out=$(python3 - "${REPO}" 2>&1 <<'EOF' +import importlib.machinery, importlib.util, sys +root = sys.argv[1] +loader = importlib.machinery.SourceFileLoader("images_lock", root + "/scripts/images-lock") +spec = importlib.util.spec_from_loader("images_lock", loader) +m = importlib.util.module_from_spec(spec) +loader.exec_module(m) +files = m.scanned_files(root) +for f, want in (("hack/ci-images.env", "rhysd/actionlint:"), ("hack/ci-tools.env", "ghcr.io/yannh/kubeconform:")): + refs = [r for _, r, _ in m.image_refs(m.read_text(root, f), f)] if f in files else [] + assert any(r.startswith(want) and m.is_pinned(r) for r in refs), (f, refs) + assert not m.frozen(root, f), f +# pins/classes quotes finished runs' refs (historical-ref): repin must never move them. +assert "pins/classes" not in files and "images.unpinned" not in files +EOF +); then ok "pins and repin read hack/ci-images.env and hack/ci-tools.env (tagged, pinned, never frozen), not pins/classes" +else fail "images-lock does not scan the CI tool pins"; sed 's/^/ | /' <<< "${out}"; fi + # ---- the repo: no workflow file holds a pin the nightly would have to move ---- # repin-images runs `repin --exclude '.github/workflows/*'`, so a name:tag@sha256 # written in a workflow would never move again. Workflows read their image pins diff --git a/tests/kubeconform.sh b/tests/kubeconform.sh index 4fb835dc..d380e2e5 100755 --- a/tests/kubeconform.sh +++ b/tests/kubeconform.sh @@ -11,7 +11,9 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -KUBECONFORM_IMAGE="ghcr.io/yannh/kubeconform:v0.8.0@sha256:faffaf43f95aa6425306e1ab8d6fcad72acb9049158f38e574c085ea1ec0f64e" +# The pinned kubeconform image, where the CI jobs read it too. +KUBECONFORM_IMAGE=$(sed -n 's/^KUBECONFORM_IMAGE=//p' "${ROOT}/hack/ci-tools.env") +: "${KUBECONFORM_IMAGE:?hack/ci-tools.env has no KUBECONFORM_IMAGE}" WORK=$(mktemp -d) trap 'rm -rf "${WORK}"' EXIT diff --git a/tests/pin_classes_test.py b/tests/pin_classes_test.py new file mode 100644 index 00000000..9d2f7cba --- /dev/null +++ b/tests/pin_classes_test.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +"""Tests for hack/pin-classes: the pins/classes parser, first-match order, the +effective class against a fixture ledger, pin tokens, the CLI, and the real repo +(coverage passes, every historical-ref string is present, the finished runners +resolve to historical and the pending rows' runners to arm). + +Usage: python3 tests/pin_classes_test.py +""" + +import importlib.machinery +import importlib.util +import os +import pathlib +import subprocess +import sys +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parent.parent +TOOL = ROOT / "hack" / "pin-classes" +_loader = importlib.machinery.SourceFileLoader("pin_classes", str(TOOL)) +pc = importlib.util.module_from_spec(importlib.util.spec_from_loader("pin_classes", _loader)) +_loader.exec_module(pc) + +LEDGER = """# Experiments + +## Hypotheses + +| # | Hypothesis | Prediction | Verdict | +|---|------------|------------|---------| +| H1 | one | p | PENDING | +| H2 | two | p | **REFUTED** | +| H3 | three | p | SUPPORTED | +""" + +CLASSES = """# a comment +bench/x/old.sh historical the first run's runner +bench/x/** ledger H1,H2 amend=Fam H1 and H2's runner +bench/done/** ledger H2,H3 amend= finished rows +bench/new/** ledger H7 amend=H7 a row not in the table yet +bench/tool.py ours +integrations/eat/** eat-stock EAT's values +historical-ref bench/x/run.sh OLD=foo/bar:1@sha256:aaaa +""" + +DIGEST = "sha256:" + "a" * 64 + + +def write(root, files): + for rel, text in files.items(): + p = pathlib.Path(root, rel) + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(text) + + +def cli(*args, root=ROOT): + p = subprocess.run([sys.executable, str(TOOL), "--root", str(root)] + list(args), capture_output=True, text=True) + return p.returncode, p.stdout + p.stderr + + +class Parse(unittest.TestCase): + def test_kinds_and_first_match(self): + lines, refs = pc.parse(CLASSES) + self.assertEqual([ln.kind for ln in lines], ["historical", "ledger", "ledger", "ledger", "ours", "eat-stock"]) + self.assertEqual(pc.match(lines, "bench/x/old.sh").kind, "historical") # before bench/x/** + self.assertEqual(pc.match(lines, "bench/x/run.sh").rows, ("H1", "H2")) + self.assertEqual(pc.match(lines, "bench/x/run.sh").aliases, ("Fam",)) + self.assertEqual(pc.match(lines, "bench/done/a/b.py").aliases, ()) + self.assertEqual(pc.match(lines, "bench/x/deep/er.sh").glob, "bench/x/**") + self.assertIsNone(pc.match(lines, "bench/xy/run.sh")) + self.assertEqual([(r.glob, r.ref) for r in refs], [("bench/x/run.sh", "OLD=foo/bar:1@sha256:aaaa")]) + + def test_errors(self): + for bad in ("bench/a.sh frozen", "bench/a.sh ledger H1", "bench/a.sh ledger X1 amend=A", + "historical-ref bench/a.sh", "bench/a.sh"): + with self.subTest(bad=bad), self.assertRaises(ValueError): + pc.parse(bad + "\n") + + def test_historical_ref_keeps_spaces(self): + _, refs = pc.parse('historical-ref bench/h.py "k": "v w"\n') + self.assertEqual(refs[0].ref, '"k": "v w"') + + +class Effective(unittest.TestCase): + def setUp(self): + self.d = tempfile.TemporaryDirectory() + write(self.d.name, {"EXPERIMENTS.md": LEDGER}) + self.ledger = pc.read_ledger(self.d.name) + self.lines, _ = pc.parse(CLASSES) + + def tearDown(self): + self.d.cleanup() + + def eff(self, path, text=None): + return pc.effective(self.lines, self.ledger, path, text)[0] + + def test_pending_row_is_arm(self): + self.assertEqual(self.eff("bench/x/run.sh"), "arm") + + def test_verdicts_only_is_historical(self): + self.assertEqual(self.eff("bench/done/run.sh"), "historical") + + def test_verdict_landing_flips_to_historical(self): + write(self.d.name, {"EXPERIMENTS.md": LEDGER.replace("| p | PENDING |", "| p | REFUTED |")}) + ledger = pc.read_ledger(self.d.name) + self.assertEqual(pc.effective(self.lines, ledger, "bench/x/run.sh")[0], "historical") + + def test_rows_not_in_table_yet_are_ours(self): + self.assertEqual(self.eff("bench/new/run.sh"), "ours") + + def test_plain_kinds_and_defaults(self): + self.assertEqual(self.eff("bench/x/old.sh"), "historical") + self.assertEqual(self.eff("bench/tool.py"), "ours") + self.assertEqual(self.eff("integrations/eat/v.yaml"), "eat-stock") + self.assertEqual(self.eff("scripts/k3d-gpu", "IMAGE=foo/bar:1@" + DIGEST), "ours") + self.assertEqual(self.eff("bench/other/run.sh", "echo hello"), "ours") + self.assertEqual(self.eff("bench/other/run.sh", "IMG=ghcr.io/foo/bar:1.2"), "unclassified") + self.assertEqual(self.eff("oracle/x.py", 'P = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678"'), "unclassified") + + +class Tokens(unittest.TestCase): + def test_tokens(self): + cases = { + "image: ghcr.io/foo/bar:v1.2@" + DIGEST: ["ghcr.io/foo/bar:v1.2@" + DIGEST], + "FROM python:3.14-slim AS b": ["python:3.14-slim"], + "image: busybox:1.37": ["busybox:1.37"], + "x = 'registry.ollama.ai/library/qwen3:4b-q4_K_M'": ["registry.ollama.ai/library/qwen3:4b-q4_K_M"], + "pip install vllm==0.24.0 torch==2.9.0+cpu": ["vllm==0.24.0", "torch==2.9.0+cpu"], + "REV = 'a1b2c3d4e5f60718293a4b5c6d7e8f9012345678'": ["a1b2c3d4e5f60718293a4b5c6d7e8f9012345678"], + "KUBECTL_VERSION=v1.37.2 # newest": ["KUBECTL_VERSION=v1.37.2"], + "B1_TAG=v0.28.0@" + DIGEST: ["B1_TAG=v0.28.0@" + DIGEST, "v0.28.0@" + DIGEST], + "manifest " + DIGEST: [DIGEST], + # not pins: comments, awk, sysfs, command substitutions, plain variables + "# image: foo/bar:1.0": [], + "awk 'NR==2'": [], + 'z = "class/powercap/intel-rapl:0"': [], + "RAPL_IMAGE=$(sed -n 's/x//p' f)": [], + 'K3D_GPU_IMAGE="${IMAGE}" K3D_GPU_VENDORS=none': [], + "IMAGE=${1:?usage: probe.sh IMAGE}": [], + '-v "${OUT}/x.yaml:/app/config.yaml:ro"': [], + } + for line, want in cases.items(): + with self.subTest(line=line): + self.assertEqual(pc.pin_tokens(line), want) + + def test_overridable_default_is_the_same_pin(self): + ref = "cryptoandcoffee/k3d-gpu:0.1.63@" + DIGEST + self.assertEqual(pc.pin_tokens("K3D_GPU_IMAGE=" + ref + " \\")[0], + pc.pin_tokens(" K3D_GPU_IMAGE=${H57D_IMAGE:-" + ref + "} \\")[0]) + + def test_prose_holds_none(self): + self.assertEqual(pc.text_tokens("bench/x/README.md", "image: foo/bar:1"), []) + + +class Cli(unittest.TestCase): + def setUp(self): + self.d = tempfile.TemporaryDirectory() + write(self.d.name, {"EXPERIMENTS.md": LEDGER, "pins/classes": CLASSES, + "bench/x/run.sh": "OLD=foo/bar:1@sha256:aaaa\n", + "bench/x/old.sh": "echo\n", "bench/done/run.sh": "echo\n", + "bench/new/run.sh": "echo\n", "bench/tool.py": "x = 1\n", + "integrations/eat/v.yaml": "tag: 1\n"}) + + def tearDown(self): + self.d.cleanup() + + def test_class_and_effective(self): + self.assertEqual(cli("class", "bench/x/run.sh", root=self.d.name), (0, "ledger\n")) + self.assertEqual(cli("effective", "bench/x/run.sh", root=self.d.name), (0, "arm\n")) + self.assertEqual(cli("effective", "./bench/done/run.sh", root=self.d.name), (0, "historical\n")) + + def test_excludes(self): + rc, out = cli("excludes", root=self.d.name) + self.assertEqual((rc, out.split()), (0, ["bench/x/old.sh", "bench/x/**", "bench/done/**", "bench/new/**"])) + + def test_coverage(self): + rc, out = cli("coverage", root=self.d.name) + self.assertEqual(rc, 1, out) # H7 is not in the table + self.assertIn("bench/new/** names H7", out) + write(self.d.name, {"pins/classes": CLASSES.replace("ledger H7 amend=H7", "ledger H1 amend=H7")}) + self.assertEqual(cli("coverage", root=self.d.name)[0], 0) + write(self.d.name, {"bench/loose/run.sh": "IMG=ghcr.io/foo/bar:1.2@" + DIGEST + "\n"}) + rc, out = cli("coverage", root=self.d.name) + self.assertEqual(rc, 1) + self.assertIn("bench/loose/run.sh: holds pin tokens", out) + write(self.d.name, {"bench/loose/run.sh": "echo\n", "bench/x/run.sh": "OLD=foo/bar:2\n"}) + rc, out = cli("coverage", root=self.d.name) + self.assertEqual(rc, 1) + self.assertIn("historical-ref 'OLD=foo/bar:1@sha256:aaaa' is missing from bench/x/run.sh", out) + + def test_usage(self): + self.assertEqual(cli("class", root=self.d.name)[0], 2) + + +class RealRepo(unittest.TestCase): + """The committed pins/classes against the committed ledger.""" + + @classmethod + def setUpClass(cls): + cls.lines, cls.refs = pc.load(str(ROOT / "pins" / "classes")) + cls.ledger = pc.read_ledger(ROOT) + + def eff(self, path): + return pc.effective(self.lines, self.ledger, path, pc.read_text(str(ROOT), path))[0] + + def test_coverage_passes(self): + rc, out = cli("coverage") + self.assertEqual(rc, 0, out) + + def test_every_historical_ref_present(self): + self.assertTrue(self.refs) + for r in self.refs: + with self.subTest(ref=r.ref): + self.assertIn(r.ref, (ROOT / r.glob).read_text()) + + def test_finished_records_are_historical(self): + for path in ("console/matrix.v1.json", "oracle/seed/build.py"): + with self.subTest(path=path): + self.assertEqual(cli("effective", path)[1].strip(), "historical") + + # A runner or record and a row its ledger line must list. The class each resolves to + # comes from today's ledger, so a verdict landing (or a row re-opened) moves it with + # no change here: an arm while a listed row is PENDING, historical once none is. + LEDGER_PATHS = { + "bench/amx-autoquant/probe.sh": "H27", "bench/apb/arm.sh": "H8", "bench/apb/vllm-cuda.yaml": "H8", + "bench/apb/h7.sh": "H7", "bench/dra_round.sh": "H16", "bench/noisy.sh": "H26", + "bench/rdt-isolate/run.sh": "H26", "bench/thermal-resource/probe.sh": "H23", + "bench/prefill-guard/run.sh": "H5", "bench/h54gpu/h54b.sh": "H54b", "bench/h54/diag/b1g-stall.sh": "H54a", + "bench/eatflow/bfcl-constraints.txt": "H40", "bench/eatflow/coding-constraints.txt": "H40", + "bench/eatflow/results/2026-10-17/A1/commit.txt": "H40", "bench/h57d/results/2026-10-21/verdict.json": "H57da", + "bench/h54/h54.py": "H54q", "bench/h57d/h57d.py": "H57dp", "bench/wildct/hold.sh": "H18w", + "bench/eatflow/live.sh": "H53", "bench/eatflow/crescent/setup.sh": "H53", "bench/eatflow/agent.lock": "H53", + "bench/flashnext/h60.sh": "H60", "bench/admission/run.sh": "H55", "bench/h59/h59.py": "H59", + "bench/apb/h7x.sh": "H7x", "bench/xhost/hold.sh": "H7x", "bench/load/load.py": "H18w", + "bench/zero-copy-models/probe.sh": "H28w", "bench/hetero-spec/h34.sh": "H34w", + } + + def test_ledger_paths_follow_the_ledger(self): + for path, row in self.LEDGER_PATHS.items(): + with self.subTest(path=path): + self.assertTrue((ROOT / path).is_file(), path) + ln = pc.match(self.lines, path) + self.assertEqual(ln.kind, "ledger") + self.assertIn(row, ln.rows) + want = "arm" if "PENDING" in [self.ledger[r] for r in ln.rows] else "historical" + self.assertEqual(cli("effective", path)[1].strip(), want) + + def test_a_verdict_landing_flips_a_real_line_with_no_reclassification(self): + # The real pins/classes against a copy of today's ledger: a line with a PENDING row + # resolves to arm, and to historical once that row's verdict lands. + ln = next((ln for ln in self.lines if ln.kind == "ledger" and "PENDING" in + [self.ledger.get(r) for r in ln.rows]), None) + if ln is None: + self.skipTest("no ledger line has a PENDING row today") + self.assertEqual(pc.resolve(ln, self.ledger), "arm") + landed = dict(self.ledger, **{r: "INCONCLUSIVE" for r in ln.rows if self.ledger.get(r) == "PENDING"}) + self.assertEqual(pc.resolve(ln, landed), "historical") + + def test_shipped_bench_files_and_eat_are_ours_or_eat_stock(self): + for path in ("bench/vs.py", "bench/llm_load.py", "bench/energy.sh", "bench/agent_trace.py", + "bench/agent_trace_gen.py", "bench/apb/apb.py", "bench/apb/capacity.py", "bench/competitors.lock", + "oracle/bundle.py", "scripts/k3d-gpu", "share/agent-chat-ui.yml"): + with self.subTest(path=path): + self.assertEqual(self.eff(path), "ours") + for path in ("integrations/eat/vllm-values.yaml", "examples/kuberay/raycluster-hybrid.yaml", + "examples/agents/litellm-gateway/agent.py", "contrib/eat/hybrid-values.yaml", + "integrations/eas/pins.json"): + with self.subTest(path=path): + self.assertEqual(self.eff(path), "eat-stock") + + def test_every_ledger_row_is_in_the_table(self): + for ln in self.lines: + for r in ln.rows: + with self.subTest(glob=ln.glob, row=r): + self.assertIn(r, self.ledger) + + +if __name__ == "__main__": + os.chdir(ROOT) + unittest.main(verbosity=1) diff --git a/tests/pin_guard_test.sh b/tests/pin_guard_test.sh new file mode 100755 index 00000000..da4b6679 --- /dev/null +++ b/tests/pin_guard_test.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash +# hack/pin-guard against throwaway git repos. Each case branches off one base commit +# (a small ledger, a pins/classes file and a few runners), makes one change, commits +# it and runs the guard with --base main. Commit dates are fixed (the base is +# 2026-10-29), so the amendment date rule (no earlier than the base's date minus one +# day) is deterministic. +# +# Usage: tests/pin_guard_test.sh + +set -uo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +GUARD=${GUARD:-${ROOT}/hack/pin-guard} +T=$(mktemp -d "${TMPDIR:-/tmp}/pin_guard_test.XXXXXX") +trap 'rm -rf "$T"' EXIT +PASS=0 FAILS=0 +ok() { PASS=$((PASS + 1)); echo "ok $1"; } +fail() { FAILS=$((FAILS + 1)); echo "FAIL $1"; } + +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 +export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t +export GIT_AUTHOR_DATE=2026-10-29T12:00:00-07:00 GIT_COMMITTER_DATE=2026-10-29T12:00:00-07:00 +unset GITHUB_ACTIONS GITHUB_HEAD_REF GITHUB_REF_NAME + +D1=sha256:1111111111111111111111111111111111111111111111111111111111111111 +D2=sha256:2222222222222222222222222222222222222222222222222222222222222222 +OLD_REF="foo/old:1.0@${D1}" + +R="$T/repo" +git init -q -b main "$R" +mkdir -p "$R/bench/h54/results" "$R/bench/wildct" "$R/bench/old" "$R/bench/refuted" "$R/bench/reuse" "$R/pins" "$R/scripts" +cat > "$R/EXPERIMENTS.md" <<'EOF' +# Experiments + +## Hypotheses + +| # | Hypothesis | Prediction | Verdict | +|---|------------|------------|---------| +| H54a | a | p | INCONCLUSIVE | +| H54q | q | p | PENDING | +| H18w | w | p | PENDING | +| H52b | b | p | REFUTED | +| H60 | f | p | PENDING | + +## Runs + +### H54a run (Xeon 6741P, 2026-10-21) + +done +EOF +cat > "$R/pins/classes" <<EOF +bench/old/** historical a finished run +bench/h54/** ledger H54a,H54q amend=H54 the H54 runner +bench/wildct/** ledger H18w amend=Wildct the wildct runner +bench/refuted/** ledger H52b amend=Crescent a finished row +bench/reuse/** ledger H52b,H99 amend=Crescent a finished row's runner a new row reuses +bench/tool.py ours +historical-ref bench/h54/arm.sh ${OLD_REF} +EOF +printf '#!/bin/sh\nIMG=foo/bar:1.0@%s\nOLD=%s\necho run\n' "$D1" "$OLD_REF" > "$R/bench/h54/arm.sh" +printf 'image: foo/res:1.0@%s\n' "$D1" > "$R/bench/h54/results/r.txt" +printf '#!/bin/sh\nRAPL_IMAGE=python:3.13-slim@%s\n' "$D1" > "$R/bench/wildct/hold.sh" +printf '#!/bin/sh\nIMG=foo/old:1.0@%s\n' "$D1" > "$R/bench/old/run.sh" +printf 'echo\n' > "$R/bench/old/two words.sh" +printf '#!/bin/sh\nIMG=foo/ref:1.0@%s\necho a\n' "$D1" > "$R/bench/refuted/run.sh" +printf '#!/bin/sh\nIMG=foo/reuse:1.0@%s\n' "$D1" > "$R/bench/reuse/run.sh" +printf 'IMG = "foo/tool:1.0@%s"\n' "$D1" > "$R/bench/tool.py" +printf '#!/bin/sh\nIMG=foo/k:1.0@%s\n' "$D1" > "$R/scripts/k" +mkdir -p "$R/.github/workflows"; echo "name: CI" > "$R/.github/workflows/ci.yml" +git -C "$R" add -A && git -C "$R" commit -qm base + +# case NAME WANT_RC [GUARD ARGS...]: run the guard on branch NAME (made by the caller). +guard() { (cd "$R" && "$GUARD" --base main "$@") > "$T/out" 2>&1; echo $?; } +branch() { git -C "$R" switch -q -C "$1" main; } +commit() { git -C "$R" add -A && git -C "$R" commit -qm "$1"; } +amend() { # append a heading line to EXPERIMENTS.md + printf '\n%s\n\nbody\n' "$1" >> "$R/EXPERIMENTS.md" +} +bump() { sed -i "s#$1#$2#" "$R/$3"; } +expect() { # expect DESC WANT_RC [GREP] [GUARD ARGS...] + local desc=$1 want=$2 pat=${3:-} rc + shift 3 + rc=$(guard "$@") + if [ "$rc" = "$want" ] && { [ -z "$pat" ] || grep -qE -- "$pat" "$T/out"; }; then + ok "$desc" + else + fail "$desc (rc ${rc}, want ${want}${pat:+, /${pat}/})"; sed 's/^/ /' "$T/out" + fi +} + +branch hist-edit; bump "foo/old:1.0" "foo/old:2.0" bench/old/run.sh; commit x +expect "a historical edit fails" 1 "bench/old/run.sh: historical record modified" + +branch hist-del; git -C "$R" rm -q bench/old/run.sh; commit x +expect "a historical deletion fails" 1 "historical record deleted" + +branch hist-mv; git -C "$R" mv bench/old/run.sh bench/old/run2.sh; commit x +expect "a historical rename fails" 1 "historical record renamed" + +branch hist-space; echo "echo again" >> "$R/bench/old/two words.sh"; commit x +expect "a historical file whose name has a space is still guarded" 1 "bench/old/two words.sh: historical record modified" + +branch hist-ref; bump "OLD=foo/old:1.0" "OLD=foo/old:2.0" bench/h54/arm.sh +amend "### H54 amendment Q9: the old arm (2026-10-29)"; commit x +expect "a historical-ref change in a mixed file fails (even with an amendment)" 1 "historical-ref 'foo/old:1.0@" + +branch ref-new; printf 'NEW=%s\n' "$OLD_REF" >> "$R/bench/tool.py"; commit x +expect "a historical-ref string in another file is not counted" 0 "" + +branch arm-none; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh; commit x +expect "an arm pin change without an amendment fails" 1 "bench/h54/arm.sh: arm pin change .* for PENDING H54q with no amendment" + +branch arm-row; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H54 amendment Q9: foo/bar 1.1 for every arm (2026-10-29)"; commit x +expect "an arm pin change with 'H54 amendment Q9: ... (today)' passes" 0 "^ok" + +branch arm-row-id; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H54q amendment Q9: foo/bar 1.1 (2026-10-28)"; commit x +expect "an amendment naming the PENDING row, dated the day before the base, passes" 0 "^ok" + +branch arm-form; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H54 Amendment Q9 (foo/bar 1.1, 2026-10-29)"; commit x +expect "a heading not in the '<names> amendment <ID>: <title> (date)' form is no amendment" 1 "no amendment" + +branch arm-host; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H54 Amendment Q9: foo/bar 1.1 (Xeon 6741P, 2026-10-29)"; commit x +expect "an amendment whose date follows a host in its parentheses, 'Amendment' capitalised, passes" 0 "^ok" + +branch arm-alias; bump "python:3.13-slim@${D1}" "python:3.14-slim@${D2}" bench/wildct/hold.sh +amend "### Wildct amendment P2: python 3.14 for the RAPL reader (2026-10-29)"; commit x +expect "an alias heading (Wildct amendment P2) passes" 0 "^ok" + +branch arm-other; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H60 amendment V9: another row (2026-10-29)"; commit x +expect "an amendment naming a different row fails" 1 "arm pin change .* no amendment" + +branch arm-stale; bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H54 amendment Q9: foo/bar 1.1 (2026-10-27)"; commit x +expect "a stale-dated amendment fails" 1 "is dated before 2026-10-28" + +branch arm-refuted; bump "foo/ref:1.0@${D1}" "foo/ref:1.1@${D2}" bench/refuted/run.sh +amend "### Crescent amendment E9: a newer ref (2026-10-29)"; commit x +expect "an arm change for a row now REFUTED (now historical) fails" 1 "bench/refuted/run.sh: historical record modified" + +branch arm-nonpin; bump "echo run" "echo run twice" bench/h54/arm.sh; commit x +expect "a non-pin runner edit in an arm path passes" 0 "^ok" + +branch arm-results; bump "foo/res:1.0@${D1}" "foo/res:1.1@${D2}" bench/h54/results/r.txt; commit x +expect "a pin change under results/ passes" 0 "^ok" + +branch arm-new; printf 'IMG=foo/new:1.0@%s\n' "$D1" > "$R/bench/h54/new.sh"; commit x +expect "a new file in an arm path passes (it moves no pin a row has)" 0 "^ok" + +branch started; sed -i 's/^### H54a run/### H54q run/' "$R/EXPERIMENTS.md"; commit started +git -C "$R" switch -q -c started-pr +bump "foo/bar:1.0@${D1}" "foo/bar:1.1@${D2}" bench/h54/arm.sh +amend "### H54 amendment Q9: foo/bar 1.1 (2026-10-29)"; commit x +rc=$(cd "$R" && "$GUARD" --base started > "$T/out" 2>&1; echo $?) +if [ "$rc" = 0 ] && grep -q '^::warning::bench/h54/arm.sh: H54q has a run heading' "$T/out"; then + ok "an amendment to a row that has started warns but passes" +else + fail "an amendment to a row that has started warns but passes (rc ${rc})"; sed 's/^/ /' "$T/out" +fi + +branch ours; bump "foo/tool:1.0" "foo/tool:2.0" bench/tool.py; bump "foo/k:1.0" "foo/k:2.0" scripts/k; commit x +expect "an ours edit passes" 0 "^ok" + +branch landing; bump "| H52b | b | p | REFUTED |" "| H52b | b | p | INCONCLUSIVE |" EXPERIMENTS.md +bump "echo a" "echo b" bench/refuted/run.sh; commit x +expect "a verdict landing that edits its finished row's runner (no pin) passes" 0 "^ok" + +branch prereg; printf '| H99 | n | p | PENDING |\n' > "$T/row" +sed -i "/^| H60 /r $T/row" "$R/EXPERIMENTS.md" +amend "### H99 pre-registration: a new row on the old runner (2026-10-29)" +bump "foo/reuse:1.0" "foo/reuse:2.0" bench/reuse/run.sh; commit x +expect "a pre-registration that reuses a finished row's runner sets its pins" 0 "^ok" + +# A new row reusing a finished line's runner adds itself to that line in the same diff. +newrow() { printf '| %s | n | p | PENDING |\n' "$1" > "$T/row"; sed -i "/^| H60 /r $T/row" "$R/EXPERIMENTS.md"; } +branch prereg-line; newrow H98; amend "### H98 pre-registration: H52b's runner again (2026-10-29)" +sed -i 's|^bench/refuted/\*\* .*|bench/refuted/** ledger H52b,H98 amend=Crescent a finished row, reused by H98|' "$R/pins/classes" +bump "foo/ref:1.0" "foo/ref:1.1" bench/refuted/run.sh; commit x +expect "a pre-registration that adds its row to a finished line in the same diff sets its pins" 0 "^ok" + +branch prereg-noline; newrow H98; amend "### H98 pre-registration: H52b's runner again (2026-10-29)" +bump "foo/ref:1.0" "foo/ref:1.1" bench/refuted/run.sh; commit x +expect "a pre-registration whose row the line does not list leaves the record historical" 1 "bench/refuted/run.sh: historical record modified" + +branch prereg-nohead; newrow H98 +sed -i 's|^bench/refuted/\*\* .*|bench/refuted/** ledger H52b,H98 amend=Crescent a finished row, reused by H98|' "$R/pins/classes" +bump "foo/ref:1.0" "foo/ref:1.1" bench/refuted/run.sh; commit x +expect "a new row on the line with no pre-registration heading leaves the record historical" 1 "bench/refuted/run.sh: historical record modified" + +# A PENDING row's amendment that moves its arm onto a finished line's runner joins the line. +joined() { # joined BRANCH [AMENDMENT HEADING] + branch "$1" + sed -i 's|^bench/refuted/\*\* .*|bench/refuted/** ledger H52b,H60 amend=Crescent a finished row, reused by H60|' "$R/pins/classes" + [ -z "${2:-}" ] || amend "$2" + bump "foo/ref:1.0" "foo/ref:1.1" bench/refuted/run.sh; commit x +} +joined joined-amend "### H60 amendment V9: H52b's runner for the second arm (2026-10-29)" +expect "a PENDING row's amendment that adds it to a finished line in the same diff sets its pins" 0 "^ok" +joined joined-none +expect "a PENDING row added to a finished line with no amendment leaves the record historical" 1 "bench/refuted/run.sh: historical record modified" +joined joined-stale "### H60 amendment V9: H52b's runner for the second arm (2026-10-20)" +expect "a stale amendment that adds a PENDING row to a finished line fails" 1 "is dated before 2026-10-28" + +branch reclass; sed -i 's|^bench/old/\*\* .*|bench/old/** ours now|' "$R/pins/classes" +bump "foo/old:1.0" "foo/old:2.0" bench/old/run.sh; commit x +expect "reclassifying a historical path in the same diff does not free it" 1 "bench/old/run.sh: historical record modified" + +branch loose; printf '#!/bin/sh\nIMG=ghcr.io/foo/loose:1.0@%s\n' "$D1" > "$R/bench/loose.sh"; commit x +expect "an unclassified bench pin fails" 1 "bench/loose.sh: holds pin tokens .* classify it in pins/classes" + +branch loose-nopin; printf '#!/bin/sh\necho hi\n' > "$R/bench/loose.sh"; commit x +expect "an unclassified bench file without pins passes" 0 "^ok" + +branch bot-gh; echo "on: push" >> "$R/.github/workflows/ci.yml"; commit x +expect "--autobump touching .github fails" 1 "\.github/workflows/ci.yml: an autobump branch never touches .github/" --autobump --branch autobump/k3s + +branch bot-exp; amend "### H54 amendment Q9: x (2026-10-29)"; commit x +expect "--autobump touching EXPERIMENTS.md fails" 1 "EXPERIMENTS.md: only an autobump/amend-\* branch" --autobump --branch autobump/k3s +expect "--autobump touching EXPERIMENTS.md passes on autobump/amend-*" 0 "^ok" --autobump --branch autobump/amend-H54q + +branch bot-amend-gh; echo "on: push" >> "$R/.github/workflows/ci.yml"; amend "### H54 amendment Q9: x (2026-10-29)"; commit x +expect "--autobump touching .github fails on autobump/amend-* too" 1 "never touches .github/" --autobump --branch autobump/amend-H54q + +branch bot-classes; echo "bench/x/** ours" >> "$R/pins/classes"; commit x +expect "--autobump touching pins/classes fails" 1 "never reclassifies pins" --autobump --branch autobump/k3s +expect "a human branch may touch .github and pins/classes" 0 "^ok" + +# The ledger's real headings parse to the names the guard matches rows and aliases by. +if out=$(python3 - "${ROOT}/EXPERIMENTS.md" "${GUARD}" 2>&1 <<'PY' +import importlib.machinery, importlib.util, sys +text = open(sys.argv[1]).read().splitlines() +loader = importlib.machinery.SourceFileLoader("pin_guard", sys.argv[2]) +g = importlib.util.module_from_spec(importlib.util.spec_from_loader("pin_guard", loader)) +loader.exec_module(g) +want = { + "### Crescent amendment E2:": {"crescent"}, + "### H54 amendment Q1:": {"h54"}, + "### Wildct amendment P1:": {"wildct"}, + "### H57dp amendment R2:": {"h57dp"}, + "### H60 amendment V1:": {"h60"}, + "### H47 and H49 amendment P1:": {"h47", "h49"}, + "### H30-H32 amendment A1:": {"h30", "h31", "h32"}, + "### H54b-H54c amendment G1:": {"h54b", "h54c"}, + "### Cross-host amendment K1:": {"cross-host"}, +} +for prefix, names in want.items(): + line = next(l for l in text if l.startswith(prefix)) + m = g.AMEND_RE.match(line) + assert m and g.DATE_RE.search(line), line + assert names <= g.names_of(m.group("names")), (line, g.names_of(m.group("names"))) +run = next(l for l in text if l.startswith("### H40-H48 run")) +assert "h43" in g.names_of(g.RUN_RE.match(run).group("names")), run +PY +); then ok "the ledger's real amendment and run headings parse to their rows and aliases" +else fail "real headings"; sed 's/^/ /' <<< "${out}"; fi + +branch usage; rc=$(cd "$R" && "$GUARD" --base no-such-ref > "$T/out" 2>&1; echo $?) +if [ "$rc" = 2 ]; then ok "an unknown base is a usage error (exit 2)"; else fail "an unknown base exits ${rc}, want 2"; fi + +echo "${PASS} passed, ${FAILS} failed" +[ "${FAILS}" -eq 0 ] diff --git a/tests/remote/chat-ui.golden b/tests/remote/chat-ui.golden index b723acff..bd300fb3 100644 --- a/tests/remote/chat-ui.golden +++ b/tests/remote/chat-ui.golden @@ -64,7 +64,7 @@ spec: containers: - name: open-webui # ghcr.io/open-webui/open-webui:v0.12.2 - image: ghcr.io/open-webui/open-webui@sha256:10e6a78ba2d172493460a9e8a92c496aaa7a64dc4d26d622a2e95e0c736081c9 + image: ghcr.io/open-webui/open-webui:v0.12.2@sha256:10e6a78ba2d172493460a9e8a92c496aaa7a64dc4d26d622a2e95e0c736081c9 imagePullPolicy: IfNotPresent # The image runs as root and writes under /app/backend at runtime. securityContext: diff --git a/tests/share_kubeconform.sh b/tests/share_kubeconform.sh index 46b59bd3..26abc59c 100755 --- a/tests/share_kubeconform.sh +++ b/tests/share_kubeconform.sh @@ -16,8 +16,9 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -# ghcr.io/yannh/kubeconform:v0.8.0 -KUBECONFORM_IMAGE="ghcr.io/yannh/kubeconform@sha256:faffaf43f95aa6425306e1ab8d6fcad72acb9049158f38e574c085ea1ec0f64e" +# The pinned kubeconform image, where the CI jobs read it too. +KUBECONFORM_IMAGE=$(sed -n 's/^KUBECONFORM_IMAGE=//p' "${ROOT}/hack/ci-tools.env") +: "${KUBECONFORM_IMAGE:?hack/ci-tools.env has no KUBECONFORM_IMAGE}" K8S_VERSION=$(sed -n 's/^ARG K3S_TAG="v\([0-9.]*\)-k3s.*/\1/p' "${ROOT}/Dockerfile") [ -n "${K8S_VERSION}" ] || { echo "no K3S_TAG version in Dockerfile" >&2; exit 1; } WORK=$(mktemp -d) diff --git a/tests/tune_test.sh b/tests/tune_test.sh index 6fb9f349..1d5e6ab1 100755 --- a/tests/tune_test.sh +++ b/tests/tune_test.sh @@ -91,6 +91,11 @@ a=$(fp "${h1}") b=$(fp "${h2}") c=$(fp "${h3}") d=$(fp "${h1}" K3D_GPU_AGENT_MOD [ "${a}" = "${b}" ] && ok "same hardware, same fingerprint" || bad "same hardware, same fingerprint" "${a} vs ${b}" [ "${a}" != "${c}" ] && ok "different E-core set, different fingerprint" || bad "E-core set ignored" [ "${a}" != "${d}" ] && ok "different model, different fingerprint" || bad "model ignored" +# The OVMS pin is written name:tag@sha256:...; the fingerprint names it by digest +# alone, as when the pin carried no tag, so saved profiles still apply. +in=$(run env K3D_GPU_SYSROOT="${h1}" "${K3D_GPU}" tune fingerprint | sed -n 's/^ agent_image=//p') +if [[ "${in}" =~ ^openvino/model_server@sha256:[0-9a-f]{64}$ ]]; then ok "fingerprint names the OVMS pin by digest alone" +else bad "fingerprint agent_image" "${in}"; fi echo "== profile application (what 'agent up' exports)" mkdir -p "${WORK}/profiles" From 8af6e5aae37b41f220ccdbb4f6862617bab559fe Mon Sep 17 00:00:00 2001 From: Andrew Mello <andrew@88plug.com> Date: Thu, 29 Oct 2026 22:35:58 -0700 Subject: [PATCH 2/3] pins/classes: shared runners list the rows that reuse them A ledger line listed only the rows whose ledger sections name its path, so a runner another row runs or imports turned historical when its own rows finished while the reusing row was still PENDING. #262 landed the H57dp and H57de verdicts and bench/h57d/** went historical while wildct's H57w rows still run it through bench/wildct/row.sh; the guard then refused #268's H57d amendment R2 edit of bench/h57d/arm.sh as a historical edit. A scan of every ledger runner's code for the bench/ paths it calls found the reuses, and each line now lists the reusing rows and their amendment prefix: bench/h57d (the H57w rows; h57d.py also H59), bench/h54/h54.py with its tests and GSM8K items (H59, H60), bench/prefill-guard's cpufreq.sh and prompt.txt (H55), crescent's serving.py (the cross-host rows, H60, H59), bench/xhost/fetch_models.py (the wildct rows), bench/load (the desktop, crescent and cross-host rows and H59w) and bench/shared-prefix (H59). The rest of each tree keeps its own rows, and the H57dp and H57de records get their own historical line now that the H57d tree is an arm again. tests/pin_classes_test.py checks each reuse: the reusing file still names the path, the path's line lists the row, and the path is an arm while the row is PENDING. --- pins/classes | 17 ++++++++++++---- tests/pin_classes_test.py | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/pins/classes b/pins/classes index fdd88aca..0b544238 100644 --- a/pins/classes +++ b/pins/classes @@ -43,6 +43,8 @@ bench/dra_round.sh ledger H16,H17 amend= the H16/H17 harne bench/noisy.sh ledger H3,H26 amend= H3 and H26's noisy neighbour bench/rdt-isolate/** ledger H26 amend= H26's runner bench/thermal-resource/** ledger H23 amend= H23's runner and records +bench/prefill-guard/cpufreq.sh ledger H5,H55 amend=H55 H5's cpufreq snapshot; H55's admission runner reuses it +bench/prefill-guard/prompt.txt ledger H5,H55 amend=H55 H5's prompt; H55's admission runner reuses it bench/prefill-guard/** ledger H5 amend= H5's stage-1 harness and records bench/h54gpu/** ledger H54b,H54c amend= H54b and H54c's runner and records bench/h54/diag/** ledger H54a amend=H54 H54 note N2, the B1g stall diagnostic of H54a @@ -58,6 +60,7 @@ bench/eatflow/results/*/H52b*/** ledger H52b amend=Crescent,H52 H52b's r bench/h54/results/2026-10-21/** ledger H54a,H54d amend=H54 H54a's run and H54d's not-run record bench/h54/results/*/B1g-stall-diag/** ledger H54a amend=H54 H54 note N2's records bench/h57d/results/2026-10-21/** ledger H57da,H57dm,H57db amend=H57d the H57d run +bench/h57d/results/2026-10-29/** ledger H57dp,H57de amend=H57d the H57dp and H57de run bench/hetero-spec/results/** ledger H22,H34 amend=H34 the H22 and H34 runs bench/xhost/results/2026-10-20/** ledger H25x,H27x,H28x,H34x amend=Cross-host the first cross-host runs @@ -72,22 +75,28 @@ historical-ref bench/h54/h54.py "ovms_eat": "2025.4@sha256:c370720007564bbdcbca1 # --- Ledger rows: runners of PENDING rows and their finished siblings ---------------- bench/eatflow/agent.lock ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H50,H50o,H53,H56 amend=Crescent the agent lock: H40-H48, then crescent M1's Gemma 4 and H53's Qwen3.8 sampling bench/eatflow/live.sh ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the agent-flow runner (desktop rows, then crescent) +bench/eatflow/crescent/serving.py ledger H50,H50o,H51,H52,H52b,H53,H56,H58,H59,H7x,H25x,H27x,H28x,H34x,H60 amend=Crescent,H52,H59,Cross-host crescent's serving sampler; the cross-host holds and H59's crescent holds run it bench/eatflow/crescent/** ledger H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the crescent wrappers, setup and holds bench/eatflow/** ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the agent-flow harness (pairs, solo, BFCL, tasks) bench/apb/h7x* ledger H7x amend=Cross-host H7x's runner bench/apb/test_h7x.py ledger H7x amend=Cross-host H7x's runner tests +bench/xhost/fetch_models.py ledger H7x,H25x,H27x,H28x,H34x,H60,H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb,H59w amend=Cross-host,Wildct the cross-host model fetch; wildct's setup-models.sh runs it bench/xhost/** ledger H7x,H25x,H27x,H28x,H34x,H60 amend=Cross-host the cross-host workspace, lock and holds -bench/h54/** ledger H54a,H54q amend=H54 the H54 runner; H54q is PENDING -bench/h57d/** ledger H57da,H57dm,H57db,H57dp,H57de amend=H57d the H57d runner; H57dp and H57de are PENDING +bench/h54/h54.py ledger H54a,H54q,H59,H59d,H59w,H60 amend=H54,H59,H60 H54's scorer: H59's scorer imports it, H60's runner calls its void and gsm8k +bench/h54/test_h54.py ledger H54a,H54q,H59,H59d,H59w,H60 amend=H54,H59,H60 h54.py's tests +bench/h54/gsm8k-test-0-199.jsonl ledger H54a,H54q,H59,H59d,H59w,H60 amend=H54,H59,H60 the GSM8K items H54's, H59's and H60's quality holds score +bench/h54/** ledger H54a,H54q amend=H54 the H54 runner +bench/h57d/h57d.py ledger H57da,H57dm,H57db,H57dp,H57de,H57wa,H57wm,H57wb,H59,H59d,H59w amend=H57d,Wildct,H59 H57d's scorer: H59's scorer imports its load gate +bench/h57d/** ledger H57da,H57dm,H57db,H57dp,H57de,H57wa,H57wm,H57wb amend=H57d,Wildct the H57d runner; wildct's H57w rows run it (bench/wildct/row.sh h57w) bench/h59/** ledger H59,H59d,H59w amend=H59 H59's scorer bench/flashnext/** ledger H60 amend=H60 H60's runner bench/admission/** ledger H55 amend=H55 H55's SLO-gated admission harness bench/models/** ledger H50,H50o,H57da,H57dm,H57db,H57dp,H57de amend=Crescent,H57d the Gemma 4 QAT export and grid (crescent amendment M1, H57d) bench/wildct/** ledger H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb,H59w amend=Wildct the wildct client host's runner -bench/load/** ledger H1b,H20,H21,H28,H30,H31,H32,H49b,H56,H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb amend=Wildct the matched background-load harness (amendment 27) +bench/load/** ledger H1b,H20,H21,H28,H30,H31,H32,H40,H41,H42,H43,H44,H45,H46,H47,H47b,H48,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58,H7x,H25x,H27x,H28x,H34x,H60,H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb,H59w amend=Wildct,Crescent,H52,Cross-host the matched background-load harness (amendment 27); live.sh, crescent's and the cross-host holds and wildct's start it bench/sandbox_round.sh ledger H18,H18w amend=Wildct H18's sandbox round, replicated on wildct bench/zero-copy-models/** ledger H28,H28x,H28w amend=Wildct,Cross-host H28's probe, replicated cross-host and on wildct -bench/shared-prefix/** ledger H25,H25x,H25w amend=Wildct,Cross-host H25's probe, replicated cross-host and on wildct +bench/shared-prefix/** ledger H25,H25x,H25w,H59,H59d,H59w amend=Wildct,Cross-host,H59 H25's probe, replicated cross-host and on wildct; H59's prefix phase runs probe.py bench/hetero-spec/** ledger H22,H34,H34x,H34w amend=Wildct,Cross-host H22 and H34's probes; h34.sh replicated cross-host and on wildct bench/joule-pack/** ledger H21 amend=H21 H21's probe bench/phase-steer/** ledger H20 amend=H20 H20's probe diff --git a/tests/pin_classes_test.py b/tests/pin_classes_test.py index 9d2f7cba..d712aca0 100644 --- a/tests/pin_classes_test.py +++ b/tests/pin_classes_test.py @@ -245,6 +245,47 @@ def test_ledger_paths_follow_the_ledger(self): want = "arm" if "PENDING" in [self.ledger[r] for r in ln.rows] else "historical" self.assertEqual(cli("effective", path)[1].strip(), want) + # A runner one row wrote and another row's runner runs or imports: (path, the row that + # reuses it, the reusing file, the text in it that names the path). The path's line + # must list the reusing row, or the path turns historical when its own rows finish + # while the reusing row is still PENDING, and that row's amendments are refused. + SHARED_RUNNERS = ( + ("bench/h57d/hold.sh", "H57wa", "bench/wildct/row.sh", "bench/h57d/hold.sh"), + ("bench/h57d/convert.sh", "H57wm", "bench/wildct/row.sh", "bench/h57d/convert.sh"), + ("bench/h57d/h57d.py", "H59", "bench/h59/h59.py", "import h57d"), + ("bench/h54/h54.py", "H59", "bench/h59/h59.py", "import h54"), + ("bench/h54/h54.py", "H60", "bench/flashnext/h60.sh", '"${H54}/h54.py" void'), + ("bench/h54/gsm8k-test-0-199.jsonl", "H60", "bench/flashnext/h60.sh", "gsm8k-test-0-199.jsonl"), + ("bench/prefill-guard/cpufreq.sh", "H55", "bench/admission/run.sh", "bench/prefill-guard/cpufreq.sh"), + ("bench/prefill-guard/prompt.txt", "H55", "bench/admission/run.sh", "bench/prefill-guard/prompt.txt"), + ("bench/eatflow/crescent/serving.py", "H7x", "bench/xhost/hold.sh", "bench/eatflow/crescent/serving.py"), + ("bench/xhost/fetch_models.py", "H57wb", "bench/wildct/setup-models.sh", "bench/xhost/fetch_models.py"), + ("bench/load/load.py", "H53", "bench/eatflow/crescent/hold.sh", "load/load.py"), + ("bench/load/load.py", "H7x", "bench/xhost/hold.sh", "bench/load/load.py"), + ("bench/load/load.py", "H59w", "bench/wildct/hold.sh", "load.py"), + ) + + def test_shared_runners_list_the_rows_that_reuse_them(self): + for path, row, user, needle in self.SHARED_RUNNERS: + with self.subTest(path=path, row=row): + self.assertIn(needle, (ROOT / user).read_text(), "%s no longer names %s" % (user, path)) + self.assertIn(row, pc.match(self.lines, user).rows) + ln = pc.match(self.lines, path) + self.assertEqual(ln.kind, "ledger") + self.assertIn(row, ln.rows) + + def test_a_reused_runner_stays_an_arm_while_its_reuser_is_pending(self): + # The H57d runner's own rows have verdicts; wildct's H57w rows still run it. + for path, row, _, _ in self.SHARED_RUNNERS: + if self.ledger.get(row) == "PENDING": + with self.subTest(path=path, row=row): + self.assertEqual(self.eff(path), "arm") + # A finished run's records inside that runner tree stay historical. + for path in ("bench/h57d/results/2026-10-21/verdict.json", "bench/h57d/results/2026-10-29/verdict.json"): + with self.subTest(path=path): + self.assertTrue((ROOT / path).is_file(), path) + self.assertEqual(self.eff(path), "historical") + def test_a_verdict_landing_flips_a_real_line_with_no_reclassification(self): # The real pins/classes against a copy of today's ledger: a line with a PENDING row # resolves to arm, and to historical once that row's verdict lands. From eb001d88a73cb9c0c0302182c720b14d05421a58 Mon Sep 17 00:00:00 2001 From: Andrew Mello <andrew@88plug.com> Date: Sat, 31 Oct 2026 22:37:16 -0700 Subject: [PATCH 3/3] pins/classes: classify bench/h62 and list H62 on the eatflow lines it runs bench/h62 landed on main after this branch was cut, so hack/pin-classes coverage failed on the merged tree with three unclassified pin-bearing files (h62.py, qemu.Dockerfile, test_h62.py). bench/h62/** is now a ledger line for H62, and H62 joins the bench/eatflow live.sh, crescent and harness lines whose files its runner calls (pairs.py, live.sh, crescent/hold.sh and gpu-serving.sh). --- pins/classes | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pins/classes b/pins/classes index 0b544238..6dad2afd 100644 --- a/pins/classes +++ b/pins/classes @@ -74,10 +74,10 @@ historical-ref bench/h54/h54.py "ovms_eat": "2025.4@sha256:c370720007564bbdcbca1 # --- Ledger rows: runners of PENDING rows and their finished siblings ---------------- bench/eatflow/agent.lock ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H50,H50o,H53,H56 amend=Crescent the agent lock: H40-H48, then crescent M1's Gemma 4 and H53's Qwen3.8 sampling -bench/eatflow/live.sh ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the agent-flow runner (desktop rows, then crescent) +bench/eatflow/live.sh ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58,H62 amend=Crescent,H52,H62 the agent-flow runner (desktop rows, then crescent) bench/eatflow/crescent/serving.py ledger H50,H50o,H51,H52,H52b,H53,H56,H58,H59,H7x,H25x,H27x,H28x,H34x,H60 amend=Crescent,H52,H59,Cross-host crescent's serving sampler; the cross-host holds and H59's crescent holds run it -bench/eatflow/crescent/** ledger H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the crescent wrappers, setup and holds -bench/eatflow/** ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58 amend=Crescent,H52 the agent-flow harness (pairs, solo, BFCL, tasks) +bench/eatflow/crescent/** ledger H50,H50o,H51,H52,H52b,H53,H56,H58,H62 amend=Crescent,H52,H62 the crescent wrappers, setup and holds +bench/eatflow/** ledger H40,H41,H42,H43,H44,H45,H46,H47,H48,H47b,H49,H49b,H50,H50o,H51,H52,H52b,H53,H56,H58,H62 amend=Crescent,H52,H62 the agent-flow harness (pairs, solo, BFCL, tasks) bench/apb/h7x* ledger H7x amend=Cross-host H7x's runner bench/apb/test_h7x.py ledger H7x amend=Cross-host H7x's runner tests bench/xhost/fetch_models.py ledger H7x,H25x,H27x,H28x,H34x,H60,H18w,H25w,H28w,H34w,H52w,H57wa,H57wm,H57wb,H59w amend=Cross-host,Wildct the cross-host model fetch; wildct's setup-models.sh runs it @@ -89,6 +89,7 @@ bench/h54/** ledger H54a,H54q amend=H54 the H54 runn bench/h57d/h57d.py ledger H57da,H57dm,H57db,H57dp,H57de,H57wa,H57wm,H57wb,H59,H59d,H59w amend=H57d,Wildct,H59 H57d's scorer: H59's scorer imports its load gate bench/h57d/** ledger H57da,H57dm,H57db,H57dp,H57de,H57wa,H57wm,H57wb amend=H57d,Wildct the H57d runner; wildct's H57w rows run it (bench/wildct/row.sh h57w) bench/h59/** ledger H59,H59d,H59w amend=H59 H59's scorer +bench/h62/** ledger H62 amend=H62 H62's runner (the EAS and EAT 2.0 VM pairs on crescent) bench/flashnext/** ledger H60 amend=H60 H60's runner bench/admission/** ledger H55 amend=H55 H55's SLO-gated admission harness bench/models/** ledger H50,H50o,H57da,H57dm,H57db,H57dp,H57de amend=Crescent,H57d the Gemma 4 QAT export and grid (crescent amendment M1, H57d)