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 81cd1259..e5370c2d 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 @@ -292,11 +295,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 \ @@ -448,14 +450,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 \ @@ -484,3 +485,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..6dad2afd --- /dev/null +++ b/pins/classes @@ -0,0 +1,118 @@ +# 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/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 +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/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 + +# 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,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,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 +bench/xhost/** ledger H7x,H25x,H27x,H28x,H34x,H60 amend=Cross-host the cross-host workspace, lock and holds +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/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) +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,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,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 +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..d712aca0 --- /dev/null +++ b/tests/pin_classes_test.py @@ -0,0 +1,321 @@ +#!/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) + + # 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. + 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"