Skip to content

Pin classes, pin-guard, scoped images-lock, tagged refs and tool pins out of workflows - #265

Open
88plug wants to merge 2 commits into
mainfrom
claude/fresh-pin-classes
Open

88plug wants to merge 2 commits into
mainfrom
claude/fresh-pin-classes

Conversation

@88plug

@88plug 88plug commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Every pin in the repo now has a class, a CI job enforces the ledger rules on every PR and push to main, scripts/images-lock stops rewriting ledger records and stops failing a bump over one unrelated drifted digest, every image reference the launcher, share/ and CI use shows its version, and no version literal is left in a workflow file.

This is the pin-classes part of the freshness CI/CD plan. It needs no owner action: no setting, App, secret or branch-protection change.

What changes

pins/classes (new). One line per path, first match wins, the same shape as images.unpinned:

  • <glob> ours|eat-stock|historical [reason]
  • <glob> ledger <rows csv> amend=<aliases csv> [reason]
  • historical-ref <path> <exact string>

An unmatched path is ours. An unmatched file under bench/ or oracle/ that holds a pin token is unclassified, and hack/pin-classes coverage fails on it. A ledger line is an arm while any listed row is PENDING in the Hypotheses table, read with ledger() from tests/ledger_drift_check.py imported by path. It is historical once every listed row has a verdict, so a verdict landing never needs a reclassification PR. Rows not yet in the table leave the line free, for a row being pre-registered.

hack/pin-classes (stdlib) has four commands:

  • class PATH
  • effective PATH
  • excludes
  • coverage: fails on unclassified pin-bearing files, missing historical-ref strings, ledger rows the table lacks, and lines that match no file.

hack/pin-guard --base REF [--head REF] [--autobump] [--branch B] [--classes FILE] (stdlib) checks merge-base(base, head)..head:

  • a. Historical path (explicit, or a ledger line whose rows all have verdicts in the base's ledger): any modification, deletion or rename fails.
  • b. historical-ref: each string occurs as often as at the merge-base.
  • c. Arm path outside */results/: the pin tokens are image refs, pkg==version, 40-hex strings, sha256: digests and *TAG|VERSION|REV|REVISION|COMMIT|IMAGE|DIGEST= assignments. When the multiset of tokens on removed lines differs from the added lines', the same diff must add an amendment heading.
    • The form is ### <names> amendment <ID>: <title> (YYYY-MM-DD). Amendment and (<host>, YYYY-MM-DD) are read the same way.
    • The names must include a PENDING row of the line or one of its aliases.
    • The date must be no earlier than the merge-base's commit date minus one day. The merge-base is used rather than main's tip, so a PR's verdict does not change as main moves.
    • A row with a ### <row or alias> run heading at the base gets a ::warning::.
  • d. --autobump: touching .github/ or pins/classes fails. Touching EXPERIMENTS.md fails unless the branch is autobump/amend-*.
  • e. A changed, unclassified, pin-bearing bench/ or oracle/ file fails with "classify it in pins/classes".

Classes come from the base's pins/classes, so a PR cannot reclassify a record and edit it in the same diff. A path the base leaves unmatched takes the head's line.

ci.yml job pin-guard sits at the end of the file, with timeout-minutes: 10, contents: read and fetch-depth: 0. It runs tests/pin_classes_test.py, tests/pin_guard_test.sh, hack/pin-classes coverage, then hack/pin-guard. Its base and head depend on the event:

Event Base Head
pull_request origin/${{ github.base_ref }} HEAD^2, the PR head inside the merge ref
workflow_dispatch origin/main HEAD
push HEAD^1 HEAD

--autobump is added when the head ref starts with autobump/. ci.yml has no workflow_dispatch trigger yet; the bot-pr-ci part adds it.

scripts/images-lock

  • repin and the new drift skip every ledger and historical path and every file a historical-ref names. The script stays stdlib-only, with no tomllib: it loads hack/pin-classes. Without pins/classes, as on an installed copy, nothing is skipped.
  • update --only FILE... resolves and checks only the references those files source, plus k3s-images.txt when the Dockerfile is one. Every other entry is kept as images.lock has it. It refuses a reference another file adds, or pins to a digest the lock does not hold, and asks for that file to be named. It also refuses a moved K3S_TAG unless the Dockerfile is named. Without --only it is unchanged.
  • drift prints path:line: name:tag <pinned> -> <registry> for each moved pin, writes nothing and exits 0.
  • http() retries 429 and 5xx up to 5 attempts. It honours Retry-After (seconds or an HTTP date, capped at 120 s) and otherwise waits 2, 4, 8 and 16 s. Any other status is raised at once.
  • pins fails on a digest-only ref (name@sha256:...) in an ours or eat-stock file unless images.unpinned lists it with a reason.

Tagged refs. Each tag was checked with scripts/images-lock digest and resolves to exactly the pinned digest today. scripts/images-lock drift over the whole repo reports no moved pin. No digest changed.

where was now
scripts/k3d-gpu LLAMA_VULKAN_IMAGE ghcr.io/ggml-org/llama.cpp@sha256:c9aba1a6... ...llama.cpp:server-vulkan-b11960@sha256:c9aba1a6...
scripts/k3d-gpu AGENT_OVMS_IMAGE openvino/model_server@sha256:a928903f... openvino/model_server:2026.4.1-gpu@sha256:a928903f...
scripts/k3d-gpu Gaudi vLLM vault.habana.ai/.../vllm-0.30.0-ptupstream-2.13.0@sha256:5d064465... ...:1.24.2-481@sha256:5d064465...
scripts/k3d-gpu vLLM XPU vllm/vllm-openai-xpu@sha256:20509fab... vllm/vllm-openai-xpu:v0.32.0@sha256:20509fab...
scripts/k3d-gpu llm-scaler intel/llm-scaler-vllm@sha256:52218ad8... intel/llm-scaler-vllm:1.5@sha256:52218ad8...
scripts/k3d-gpu AGENT_VIDEO_IMAGE intel/dlstreamer@sha256:3848efe5... intel/dlstreamer:2026.2.0-ubuntu24@sha256:3848efe5...
share/agent-chat-ui.yml ghcr.io/open-webui/open-webui@sha256:10e6a78b... ...open-webui:v0.12.2@sha256:10e6a78b...
share/telemetry/intel-gpu-exporter.yml ghcr.io/home-operations/drm-exporter@sha256:9b542463... ...drm-exporter:0.3.4@sha256:9b542463...
kubeconform (ci.yml, tests/agent_kubeconform.sh, tests/share_kubeconform.sh) ghcr.io/yannh/kubeconform@sha256:faffaf43... KUBECONFORM_IMAGE in hack/ci-tools.env: ...kubeconform:v0.8.0@sha256:faffaf43...

I traced every consumer of these refs. Behaviour is unchanged:

  • Manifests (image: lines): kubelet and containerd pull name:tag@sha256 by digest; the tag is only a label.
  • console/plan.py: k3d-gpu facts release.pins now shows the tagged ref. plan.py compares a cell's measured image with the release pin by image_id() (name plus digest, tag dropped). The historical console/matrix.v1.json cells, which are digest-only, still match; a different digest still expires them (new unit test).
  • Cache keys: the tune fingerprint's agent_image and autoquant's --runtime take image_id of the default OVMS pin, so both keep their old values. Overrides stay verbatim. A new tune test checks that the fingerprint still names openvino/model_server@sha256:....
  • Oracle bundles: bench/apb/capacity.py records the pod's image verbatim, and the bundle schema allows only name@sha256. oracle/project.py tool_block stores bundle.untagged() of the image, so bundles and comparison keys stay as before. oracle/validate.py V4 compares against untagged() release pins, so bundles from both older (digest-only) and newer (tagged) releases pass (new unit test). k3d-gpu share passes the launcher's pins to oracle/share.py, which takes the same V4 path.
  • images.lock: in scripts/images-lock collect, the OVMS digest-only entry and its tagged twin (from NPU_UMD_IMAGE and share/npu-embeddings.yml) are now one name:tag key. The lock therefore holds 54 images instead of 55, with every digest unchanged. That is why the audit goldens' summary line reads 54 and the OVMS line lists three sources. Audit verdicts are unchanged, because the audit matches by name and digest.

images.lock was regenerated with scripts/images-lock update --only scripts/k3d-gpu share/agent-chat-ui.yml share/telemetry/intel-gpu-exporter.yml, and re-running it with hack/ci-tools.env added leaves the lock byte-identical. The goldens were regenerated with each suite's own update path: tests/audit_test.sh --update, UPDATE_GOLDEN=1 tests/remote_test.py, tests/sim_matrix_test.sh --update and tests/report_html_test.sh --update. Their diffs hold only:

  • the tagged-ref lines;
  • the report's "sha256 of its canonical bytes" line, which hashes those facts;
  • the audit lock count above.

hack/ci-tools.env (new) holds PROMETHEUS_VERSION, PROMETHEUS_SHA256, KUBECONFORM_VERSION, KUBECONFORM_SHA256, KUBECONFORM_IMAGE, CHROME_VERSION, JSONSCHEMA_VERSION, HELM4_VERSION, SHELLCHECK_VERSION and PACKAGING_VERSION.

  • Each job that needs a value loads the file in its first step after checkout (grep -E '^[A-Z0-9_]+=' hack/ci-tools.env >> "$GITHUB_ENV") and reads ${{ env.X }} or $X.
  • tests/kubeconform.sh, tests/agent_kubeconform.sh and tests/share_kubeconform.sh read KUBECONFORM_IMAGE from the file and fail if it is missing.
  • scripts/images-lock scans the file as it scans hack/ci-images.env.

#238 merged before this branch was cut, so the file carries #238's values: Prometheus 3.15.1 with its checksum, and Chrome 156.0.8078.73. Every value is the latest stable release (checked today against GitHub releases and PyPI) except Chrome: Chrome for Testing stable moved to 156.0.8078.80 after #238. That bump is left to the freshness job, which can now move it with a GITHUB_TOKEN, so this PR changes no tool version.

Classification

Seeded as asked:

  • eat-stock: integrations/eat/**, examples/kuberay/**, examples/agents/litellm-gateway/**, contrib/eat/**. integrations/eas/** (new in batch: land #248, #251, #250, #249, #247, #243 #262, after the seed was written) joins them. Its pins.json freezes what EAS's own configs resolve to (the EAS, EI and EAT commits) and the bastion's tools, so a change to it is reviewed like EAT's.
  • ours: the shipped bench files (vs.py, llm_load.py, energy.sh, agent_trace.py, agent_trace_gen.py, apb/apb.py, apb/capacity.py), bench/competitors.lock, the rest of bench/apb/** (its tests and stage.py), and oracle/**.
  • historical: console/matrix.v1.json, oracle/seed/**, oracle/raw/**.
  • ledger: each line names every row that uses the path (from EXPERIMENTS.md), finished rows included, plus the heading prefixes its amendments use.

Where this differs from the seed, and why:

The bench paths that matched nothing before:

path class why
bench/hetero-spec/** ledger H22, H34, H34x, H34w; amend Wildct, Cross-host h34.sh ran H34 and H34x and runs H34w (PENDING) on wildct; probe.sh and analyze.py are H22's. An arm today.
bench/joule-pack/** ledger H21 H21's probe; INCONCLUSIVE, so historical
bench/models/** ledger H50, H50o, H57da, H57dm, H57db, H57dp, H57de; amend Crescent, H57d the Gemma 4 QAT export and grid (crescent M1, the H57d pre-registration); an arm while H57dp and H57de are PENDING
bench/phase-steer/** ledger H20 H20's probe; historical
bench/shared-prefix/** ledger H25, H25x, H25w; amend Wildct, Cross-host run.sh runs H25w (PENDING) on wildct; an arm
bench/sku-model/** ledger H24 H24's probe and records; historical
bench/tune-noise/** ledger H19 H19's runner; historical
bench/uncore-phase/** ledger H29 H29's probe; historical
bench/amx-autoquant/** ledger H27, H27x; amend Cross-host "H27 REFUTED; owner decision pending"; historical
bench/vs_matched.py ledger H30, H31, H32 the H30-H32 matched-load comparison; historical
bench/agent_trace_gen.py ours k3d-gpu capacity's trace generator; nfpm ships it

The other ledger lines, with their rows and aliases:

  • bench/h54/**: H54a and H54q; alias H54.
  • bench/h57d/**: the H57d rows and wildct's H57w rows; aliases H57d and Wildct.
  • bench/h59/**: H59, H59d and H59w.
  • bench/flashnext/**: H60.
  • bench/admission/**: H55.
  • bench/wildct/**: the eight wildct rows plus H59w; alias Wildct.
  • bench/sandbox_round.sh and bench/zero-copy-models/**: their own rows plus the wildct rows; alias Wildct.
  • bench/apb/h7x* and bench/xhost/**: H7x, the cross-host rows and H60; alias Cross-host.
  • bench/eatflow/live.sh, bench/eatflow/crescent/** and bench/eatflow/**: the H40-H49 desktop rows and the crescent rows H50-H58; aliases Crescent and H52.

Runners other rows reuse. A ledger line lists every row whose runner runs or imports the path, not only the rows whose ledger sections name it. A scan of every ledger runner's code for the bench/ paths it calls found these reuses, and each line now lists the reusing rows:

path reusing rows (aliases) through
bench/h57d/** H57wa, H57wm, H57wb (Wildct) bench/wildct/row.sh h57w runs H57d's hold.sh and convert.sh
bench/h57d/h57d.py also H59, H59d, H59w (H59) bench/h59/h59.py imports its load gate
bench/h54/h54.py, test_h54.py, gsm8k-test-0-199.jsonl H59, H59d, H59w, H60 (H59, H60) h59.py imports it; bench/flashnext/h60.sh runs its void and gsm8k
bench/prefill-guard/cpufreq.sh, prompt.txt H55 (H55) bench/admission/run.sh
bench/eatflow/crescent/serving.py H7x, the cross-host rows, H60, H59 (Cross-host, H59) bench/xhost/hold.sh; bench/h59/hold.sh in #268
bench/xhost/fetch_models.py the wildct rows (Wildct) bench/wildct/setup-models.sh
bench/load/** also the desktop and crescent rows, the cross-host rows and H59w (Crescent, H52, Cross-host) live.sh, crescent's hold.sh and slots.sh, xhost/hold.sh, wildct/hold.sh
bench/shared-prefix/** also H59, H59d, H59w (H59) bench/h59/run.py's prefix phase in #268 runs probe.py

The rest of each tree keeps its own rows. H54's runner scripts and H5's run.sh and records stay historical. bench/h57d/results/2026-10-29/**, the H57dp and H57de run, gets its own line, which resolves to historical now that the H57d tree is an arm again.

The miss showed up on #268. When #262 landed the H57dp and H57de verdicts, bench/h57d/** turned historical while wildct's H57w rows still run it, and the guard refused #268's H57d amendment R2 edit of bench/h57d/arm.sh as a historical edit.

One known limit: ledger() reads only H rows. The EAS rows #249 and #250 pre-register (F3, F4) are invisible to it, so a path owned only by an F row cannot be a ledger line yet. Their paths are under integrations/eat/ and integrations/eas/, both eat-stock, so nothing is lost today.

Guard rules the replay needed

Read literally, the spec's rules fail legitimate ledger history in four places. Each rule below is narrowed and has a test. The commits named are the ones that fail in the 80-commit replay when that rule alone is switched back off (re-measured with this pins/classes):

  1. A diff that records a run re-opens its finished line as an arm. It does so by adding a ### <row> run heading or rewriting the row's Hypotheses table line. Ten verdict commits of the matched-load and amendment-27 re-runs edited their probes while their rows still read the earlier run's verdict: H25 verdict under matched load: INCONCLUSIVE (throttle events unmatched) #102, H21 verdict under matched background load #118, H20 verdict under matched load: INCONCLUSIVE (throttle rate unmatched), REFUTED in direction #121, H29 verdict under matched load: INCONCLUSIVE (load unmatched, U pin not honoured), REFUTED in direction #122, H23 under matched load: INCONCLUSIVE (O0 budget gate at typical and heavy), DO-NOT-RE-ATTACK on this host #124, H24 under matched load: INCONCLUSIVE (load unmatched), REFUTED in direction #127, H25 verdict under amendment 27: SUPPORTED at typical (10.61x) #132, H32 verdict under amendment 27: SUPPORTED at typical (1.59x, CI 1.52-1.88) #133, H26 verdict under amendment 27: INCONCLUSIVE at typical (during busy unmatched; R 0.92, CI 0.14-2.09) #137, H20 verdict under amendment 27: INCONCLUSIVE at typical (pre swap unmatched; PS/SP net J/token 1.03, CI 0.52-1.20) #138.
  2. A pre-registration that reuses a finished row's runner sets the new row's pins. The row must be absent from the base's table, PENDING in the head's, and named by an added ### <names> pre-registration heading. This covers three commits: ledger(H50-H52): pre-register the Xeon 6741P campaign #152, ledger(H25x, H27x, H28x, H34x): pre-register cross-host replications on the Xeon 6741P #156 and ledger(H52w, H18w, H25w, H28w, H34w, H57wa-H57wb): pre-register the wildct client host (i7-13700) #181. With the pins/classes before the shared-runner lines it also covered ledger(H56): pre-register parallel slice holds on crescent; slot locks behind HOLD_SLOT (K=1 default) #168 and ledger(H57dp): amendment R2, the bench lock, the hybrid vendor, the quality records and the Reproduce header #215, which now pass without it.
  3. A new file in an arm path is not a pin change. It moves no pin a row already has, and its pins are guarded from then on. H40-H48 live run: H40 SUPPORTED, H48 REFUTED, H41/H42/H44/H46 INCONCLUSIVE #125 added bench/eatflow/live.sh with the H40-H48 run, and feat(bench): controlled background-load profile (busy-box methodology) #95 added bench/load/load.py before any row adopted it.
  4. A historical-ref string the merge-base does not hold yet passes, in the commit that records it: H40-H48 live run: H40 SUPPORTED, H48 REFUTED, H41/H42/H44/H46 INCONCLUSIVE #125, ledger(H54a): amendment R1, the H54a runner and the flag checks #169 and ledger: pre-run amendments for the pending crescent rows (E1, E2, D2, Q1) #225.

Two rules look ahead rather than back. Without them, a ledger workflow would need a separate pins/classes PR before it could reuse a finished runner:

  1. A pre-registration may add its new row to a finished runner's ledger line in the same diff. Classes otherwise come from the base, but here the head's line for that path counts. The new row must be absent from the base's table, PENDING in the head's, and named by an added pre-registration heading.
  2. An amendment may move a PENDING row's arm onto a finished runner. The diff adds the row to that runner's ledger line and adds a dated amendment naming the row, and the amendment is held to the usual date floor.

Neither frees a record without a ledger entry a reviewer sees, and each has passing and failing cases in tests/pin_guard_test.sh. This is near: once #267 lands H53's verdict, bench/eatflow/agent.lock resolves to historical, and H58 can reuse it only through rule 6.

Three more choices:

Retrospective replay

I ran hack/pin-guard --base <commit>^1 --head <commit> --classes pins/classes over the last 80 first-parent commits on main that touch bench/, from #94 (2026-10-17) to #262 (2026-10-29). Each commit is checked against its parent's ledger. All of them use this branch's pins/classes, since none had one. No commit on main has touched bench/ since #262, so the set is unchanged after the rebase. I re-ran it with the shared-runner lines and got the same result.

The 80 commits
# commit subject pin-guard
1 6659ace batch: land #248, #251, #250, #249, #247, #243 (#262) pass
2 012b4e9 Latest tooling pins (2026-10-29) and crescent amendment E4 (#238) fail (1): true positive: #238 moves pins in bench/amx-autoquant/probe.sh, the runner of REFUTED H27 and H27x (historical; owner decision pending), with no amendment
3 890d1d1 bench(wildct): smoke3's record, amendment R3, a pod wait that reads r... pass
4 d997ea8 ledger(H60): amendment V1, both arms on llama.cpp v0.8.0 (server-b119... pass
5 f53eac1 ledger(H52b): REFUTED on the Xeon 6741P after host-setup --apply; ame... pass
6 c4407a2 bench(wildct): smoke2's record, amendment R2, an empty pod list is no... pass
7 7946b85 ledger(H51): REFUTED on the Xeon 6741P; neither CPU stack serves one ... pass
8 76bbfbd batch r2: land #182, #236, #195, #224, #237, #226, #239, #230, #210, ... fail (2): true positive: #237 (landed in batch r2) edits bench/apb/arm.sh and bench/apb/h7.sh, the runners of finished H8 and H7
9 a4e7ca2 ledger(H60): pre-register Qwen3.8-Flash-Next from host DRAM on the Xe... pass
10 5cf2056 ledger(H57dp): amendment R2, the bench lock, the hybrid vendor, the q... pass
11 d1ac532 ledger(H59, H59d, H59w): pre-register engine choice per CPU class (#197) pass
12 54042ad results(H56): stage 1 scored INCONCLUSIVE; every W hold VOID; crescen... pass
13 952aa59 bench(h54): why B1g stalled (unscored diagnostic, H54 note N2) (#209) pass
14 55557d9 ledger(H28x): run 1 failed in setup; probe.sh creates WORK; amendment... pass
15 702c8d7 nightly: repin the moved digests and keep the digest PR out of .githu... fail (7): true positive: #220's class-blind repin rewrites bench/rdt-isolate/run.sh (historical) and moves arm pins in six files with no amendment
16 5cb3be3 k3d-gpu-mcp: read the per-cluster kubeconfig; repair the open C2 veri... pass
17 6810820 ledger: pre-run amendments for the pending crescent rows (E1, E2, D2,... pass
18 cec9c81 research: commit the 2026-10-20 model-card digest and dialectic recor... pass
19 6d5cab6 live.sh: probe the read-only bind at B1's real mountPath (#204) pass
20 91e958f results: H52b idxd round-trip pre-check; A1 QAT IR export; greedy_com... pass
21 cbbbf4c ledger(H57da, H57dm, H57db): i9-13900T Gemma 4 QAT run (#194) pass
22 ced8cd8 ledger(H54a, H54d): H54a INCONCLUSIVE (stock B1g stalled), H54d not r... pass
23 6edf1bb ledger(H54a): amendment R4, the void rule's CPU part reads the arm's ... pass
24 c76e22c ledger(H54a): amendment R3, an arm whose server stops answering after... pass
25 35fd4c6 docs: scope P/E placement to placement, not isolation; ledger(H55): p... pass
26 567a2bc ledger(H54a): amendment R2, drafter conversion, Vq-mtp draft length, ... pass
27 5c0da93 ledger(H25x): Xeon 6741P run, SUPPORTED (#177) pass
28 9f81e00 ledger(H54b): G1 revision 2, B2q first (#186) pass
29 c247f3f ledger(H52w, H18w, H25w, H28w, H34w, H57wa-H57wb): pre-register the w... pass
30 95665ef ledger(H56): amendment S0, stage 0 pooled over every campaign log (#185) pass
31 a26d7ca idxd-roundtrip.sh: snapshot survives a sysfs object with no driver li... pass
32 38f6b30 ledger(H56): pre-register parallel slice holds on crescent; slot lock... pass
33 2b826db ledger(H54b, H54c): amendment G1, the B70 runner (#166) pass
34 782a83c ledger(H53): pre-register EAT's banking flow on Arc Pro B70, block-le... pass
35 d079fa9 qat_grid: match a real optimum-intel Gemma 4 export; export gate venv... pass
36 0a3fcc1 crescent hold: each hold owns its process tree; graceful stop on sign... pass
37 458bca0 ledger(H57d): amendment R1, the desktop runner, read-only Od render a... pass
38 908cbce ledger(H54a): amendment R1, the H54a runner and the flag checks (#169) pass
39 a7b7b99 crescent: H50/H50o on Gemma 4 QAT (amendments M1, A3, A4), read-only ... pass
40 28e81fd ledger(H34x): Xeon 6741P run, REFUTED (#170) pass
41 fcfd7ac ledger(H27x): Xeon 6741P run, REFUTED (#159) pass
42 ef87f86 ledger(H25x, H27x, H28x, H34x): pre-register cross-host replications ... pass
43 248fc4d ledger(H52): Xeon 6741P run, REFUTED; xe media labels; amendments D1,... pass
44 fda704f ledger(H50-H52): pre-register the Xeon 6741P campaign (#152) pass
45 a32a970 ledger(H43, H45): runs on the i9-13900T, both REFUTED (#151) pass
46 690f924 ledger(H5): stage-1 run, REFUTED (#150) pass
47 5ea35b7 eatflow H43: code-mode reserve matches the block budget, amendment W2... pass
48 dc2e1aa eatflow: wire H43 and H45 into the live runner, amendment W1 (#148) pass (warning: amends a row with a run heading)
49 45862ee bench(prefill-guard): H5 stage-1 harness and amendment A1 (#147) pass
50 d2b6ce9 ledger: H47b and H49b runs, both INCONCLUSIVE by the L2 matching gate... pass
51 f31294a H47b/H49b pre-registration: load rule L2, pairs matched on idle pre-w... pass
52 b08e02c H49 verdict: INCONCLUSIVE (load rule L1 matched 2 of 24 pairs) (#139) pass
53 8adbe2b H20 verdict under amendment 27: INCONCLUSIVE at typical (pre swap unm... pass
54 f8369af H26 verdict under amendment 27: INCONCLUSIVE at typical (during busy ... pass
55 f058aff H28 verdict under amendment 27: SUPPORTED at typical (ready ratio 0.4... pass
56 4c314ff H47 verdict: INCONCLUSIVE (load rule L1 matched 13 of 24 pairs); gain... pass
57 7eb96a2 H32 verdict under amendment 27: SUPPORTED at typical (1.59x, CI 1.52-... pass
58 819680a H49 runner fix: re-resolve the DevUI entity after an agent restart; a... pass
59 12286d1 H25 verdict under amendment 27: SUPPORTED at typical (10.61x, CI 10.3... pass
60 bca4a51 H47/H49 runner: within-hold interleaved pairs, amendment P1 (#131) pass (warning: amends a row with a run heading)
61 523c23c bench/load: amendment 27, match covariates only; settle gap; void on ... pass
62 7119e5a H21 verdict under matched background load (#118) pass
63 5db3696 H24 under matched load: INCONCLUSIVE (load unmatched at typical and h... pass
64 c18fca6 H40-H48 live run: H40 SUPPORTED, H48 REFUTED, H41/H42/H44/H46 INCONCL... pass
65 734b60a H23 under matched load: INCONCLUSIVE (O0 budget gate at typical and h... pass
66 017143c H29 verdict under matched load: INCONCLUSIVE (load unmatched, U pin n... pass
67 3a139f1 H31-H32 under matched load (A2): H31 SUPPORTED, H32 INCONCLUSIVE (#120) pass
68 be0ea1a Pin every container image by digest, with a nightly repin PR (#123) fail (1): true positive: #123's first repin adds a digest to H26's finished runner
69 91c2b77 H20 verdict under matched load: INCONCLUSIVE (throttle rate unmatched... pass
70 5dae99b H19 verdict under matched load: SUPPORTED, confidence 0.25 (#119) pass
71 209809a H30-H32 verdicts under matched load: H30 SUPPORTED, H31/H32 INCONCLUS... pass
72 8e44844 H34 verdict: CPU Fast Draft SUPPORTED at typical matched load (#115) pass
73 efadce8 H27 verdict under matched load: REFUTED, int4 fails the quality gate ... pass
74 aa1fa7d feat: remote OpenAI-compatible endpoints (--remote) instead of a loca... pass
75 c95b168 H26 verdict under matched load: INCONCLUSIVE, background load unmatch... fail (1): true positive: H26's arm runner gains image: busybox:1.37 in the commit that records its run, with no amendment
76 416ac1c feat(bench): agent benchmark harness for EAT flows and BFCL S100, ref... pass
77 cab83d1 oracle: opt-in k3d-gpu share, CI validation, leaderboard seeded with ... pass
78 58c2ad1 H25 verdict under matched load: INCONCLUSIVE, throttle events unmatch... pass
79 402518e feat(bench): controlled background-load profile for busy-box probes (... pass
80 a968f98 fix(capacity): a step the budget cut short is unmeasured, not a fail ... pass

Open PRs. I ran the guard on every open PR the way CI will once this lands: base origin/main at dc8b86f, with this pins/classes.

Earlier runs, before #262 landed them, also passed #247 (H57dp and H57de), #243 (H54q), #249 and #250 (EAS F3 and F4). So the running crescent, wildct, desktop and H59 workflows stay green.

repin on the live registries. I ran this in a scratch copy of the branch (no .git link to this repo):

  1. As the branch stands, scripts/images-lock repin --exclude '.github/workflows/*' changes nothing: no pinned digest has moved today.
  2. To give the check teeth, I put the seven files nightly: repin the moved digests and keep the digest PR out of .github/workflows/ #220 rewrote back as they were before nightly: repin the moved digests and keep the digest PR out of .github/workflows/ #220, then repinned:
    • with pins/classes: 0 of the 7 files changed;
    • with pins/classes moved aside (the old, class-blind behaviour): 7 of 7 changed, 10 edits.

Tests

The new tests were written before the code they test, and every one is wired into ci.yml.

tests/pin_classes_test.py (job pin-guard) covers:

  • parsing, first-match order and parse errors;

  • the effective class against a fixture ledger: PENDING is arm, a verdict is historical, a verdict landing flips it, and rows not in the table are ours;

  • pin tokens, and the non-tokens: comments, awk, sysfs, command substitutions and plain variables;

  • the CLI;

  • the real repo:

    • coverage passes and every historical-ref string is present;
    • the finished runners resolve to historical and the pending rows' runners to arm;
    • the shipped bench files resolve to ours and the EAT paths to eat-stock;
    • every ledger row exists;
    • each shared runner's line lists the rows that reuse it, and the reusing file still names the path;
    • a reused runner is an arm while a reusing row is PENDING, and the H57d run records inside that tree stay historical.

    The last two tests were written first and fail on the previous pins/classes (21 subtests).

tests/pin_guard_test.sh (job pin-guard) builds real git fixture repos and runs 39 cases:

  • Historical paths: an edit, a deletion, a rename, and a file name with a space all fail. A historical-ref change in a mixed file fails even with an amendment.
  • Arm pin changes:
    • With no amendment, the change fails.
    • These amendments pass: H54 amendment Q9: ... (today); one naming the row and dated the day before the base; an alias heading, Wildct amendment P2; and H54 Amendment Q9: ... (Xeon 6741P, date).
    • These fail: an amendment naming another row; a stale-dated one; a heading not in the amendment form; and an arm change for a REFUTED row.
  • Changes that pass: a non-pin runner edit; a change under results/**; a started row (warns and passes); an ours edit.
  • --autobump: touching .github, EXPERIMENTS.md or pins/classes fails. EXPERIMENTS.md is allowed on autobump/amend-*, and .github is not.
  • Unclassified bench files: one holding a pin fails; one without a pin passes.
  • The replay rules: a new file, a verdict landing, a pre-registration reusing a finished runner, and reclassifying in the same diff.
  • Rules 5 and 6: a pre-registration or an amendment that adds its row to a finished line passes. It fails without the heading, without the line, or with a stale date.
  • The ledger's real headings: Crescent amendment E2, H54 amendment Q1, Wildct amendment P1, H57dp amendment R2, H60 amendment V1, H47 and H49, H30-H32, H54b-H54c and Cross-host.

A mutation run of hack/pin-guard disabled each of these in turn: rule a, the date floor, aliases, historical-ref, --autobump, unclassified files, the token compare and the results exemption. tests/pin_guard_test.sh failed every time.

tests/image_pins_test.sh (job images-lock), 63 cases:

  • repin and drift leave ledger, historical and historical-ref files untouched, even when they are named on the command line. drift lists the moved pin and writes nothing.
  • update --only:
    • ignores drift elsewhere and keeps the other entries byte for byte;
    • skips k3s-images.txt unless the Dockerfile is named;
    • still validates the named file and adds its new reference;
    • refuses an unnamed file's new reference, an unnamed file's re-pinned digest, and a moved K3S_TAG.
  • http(), against a local stub registry:
    • 429 with Retry-After: 7 sleeps 7 s;
    • two 5xx back off 2 s, then 4 s;
    • Retry-After: 500 is capped at 120 s;
    • an HTTP date is honoured;
    • five 429s give up;
    • a 404 is not retried.
  • pins: a digest-only ref fails in ours and eat-stock files, and passes in ledger and historical ones or when images.unpinned allows it.
  • Scan scope: hack/ci-images.env and hack/ci-tools.env are scanned; pins/classes is not.

tests/ci_wiring_test.sh (job detect-tests):

  • .github/workflows/*.yml holds no version literal, ==version, checksum or digest outside uses: lines and comments. The allowlist is the published ubuntu24.04/ubuntu26.04 variant names, the job name "kubeconform 1.34/1.35", and the e.g. 0.1.x input examples.
  • Every job that reads a hack/ci-tools.env key loads the file before first use and defines none of its own.
  • Every key is used.
  • The three kubeconform scripts read KUBECONFORM_IMAGE from the file.

Other new tests:

  • tests/console/test_console.py: a tagged release pin keeps a cell measured on the same digest green; another digest expires it.
  • oracle/test_oracle.py: a tagged release pin passes V4 for a bundle with the digest-only image, and script_pins and tool_block return name@sha256.
  • tests/tune_test.sh: the fingerprint names the OVMS pin by digest alone.

Results

This branch is rebased onto dc8b86f. Main brought in #266 (the llama.cpp fit) and #272 (#256's known-issues job and hack/known-issues, which reads pins/classes). Two conflicts came up, and both sides were kept in each:

  • ci.yml: the known-issues job, then pin-guard.
  • images.unpinned: both test entries.

After the rebase and the shared-runner fix, these ran again locally, all green:

  • tests/pin_classes_test.py (24 tests) and tests/pin_guard_test.sh (39/0);
  • hack/pin-classes coverage, scripts/images-lock check and scripts/images-lock pins;
  • tests/image_pins_test.sh (63/0) and tests/ci_wiring_test.sh;
  • claims, ledger drift and their own tests;
  • known-issues: fixed-in registry, version schemes and a ledger re-measure queue #256's pinver, known_issues and ledger_freshness tests, and hack/known-issues check;
  • the fit, llama.cpp, console, audit, console MCP, model optimizer, report, remote, chat UI, tune, EAT, EAS, detection, gate, release CAS, packaging, bench, hold and oracle suites;
  • the full simulated hardware matrix.

actionlint 1.7.12 and shellcheck v0.11.0 are clean. Each of the nine tagged refs still resolves to its pinned digest today. scripts/images-lock drift over the whole repo reports no moved pin.

The table below is from the earlier full run on main at 74ff63e, before the rebases.

The first CI run of this PR caught one problem. tests/pin_classes_test.py hard-coded which runners are arms, and #262 had just landed H54q's and H57dp's verdicts. The test now derives each expected class from today's ledger and checks a verdict landing on a real line. On the tree this branch makes when merged with #267 (H53's verdict), bench/eatflow/agent.lock resolves to historical, and every pin test passes with no change to pins/classes.

  • Every run: suite of ci.yml that needs no cluster: 72 runs, all green. Where a first run failed, the cause and the clean re-run are listed below.

    suite result
    tests/detect_test.sh 1185 passed, 0 failed
    tests/sim_matrix_test.sh 256/0
    tests/console_test.sh 61/0
    tests/audit_test.sh 49/0
    tests/console_mcp_test.sh ok
    tests/report_html_test.sh 48/0
    tests/remote_test.sh 114/0
    python3 tests/remote_test.py OK
    tests/chat_ui_test.sh 25/0
    tests/render_test.sh 16/0, with helm v3.22.1 from hack/install-helm
    tests/kserve_test.sh 42/0, with kubeconform v0.8.0 installed by the CI step from hack/ci-tools.env, checksum verified
    tests/telemetry_test.sh 27/0, with promtool 3.15.1 installed by the CI step from hack/ci-tools.env, checksum verified
    tests/kubeconform.sh, tests/agent_kubeconform.sh, tests/share_kubeconform.sh every resource valid, with KUBECONFORM_IMAGE from hack/ci-tools.env
    tests/image_pins_test.sh 63/0
    scripts/images-lock check, scripts/images-lock pins ok
    tests/ci_wiring_test.sh 74/0
    tests/llamacpp_test.sh 47/0
    tests/tune_test.sh 24/0, 1 skipped (the full tune, TUNE_E2E_FULL)
    python3 oracle/test_oracle.py, test_fuzz.py, test_adversarial.py, leaderboard.py --check OK
    tests/bench_test.sh 71/0
    every bench/**/test_*.py in ci.yml OK
    tests/hold_own_test.sh 63/0
    tests/wildct_hold_test.sh 41/0
    python3 tests/pin_classes_test.py OK
    tests/pin_guard_test.sh 39/0
    hack/pin-classes coverage ok
    bash tests/claims_check.sh 27 files clean (after the rebase)
    python3 tests/ledger_drift_check.py 42 files agree with 81 ledger rows (after the rebase)
    tests/claims_check_test.sh, tests/pe_premise_check*.sh, tests/ledger_drift_check_test.py green
    tests/packaging_test.sh 126/0
    tests/eas_test.sh, tests/eat_test.sh (after the rebase onto batch: land #248, #251, #250, #249, #247, #243 #262) 232/0, 84/0
    tests/host_setup_test.sh 269/0
    tests/quickstart_test.sh 174/0

    First-run failures:

    • oracle/test_adversarial.py timed out once on its 10 s Ctrl-C prompt while six suites ran in parallel. It passes alone (12 tests, OK).
    • tests/image_pins_test.sh failed its first parallel run for two reasons. The drift no-write check compared a __pycache__ the import wrote, so it now ignores __pycache__. Two update --only cases written before the code exposed that it still accepted an unnamed file's new digested reference, and reported a moved K3S_TAG without naming it. Both were fixed in scripts/images-lock, and the suite is 63/0.
  • actionlint 1.7.12 (the ACTIONLINT_IMAGE digest in hack/ci-images.env): clean.

  • shellcheck v0.11.0, -S warning as in CI, on every changed shell file including scripts/k3d-gpu: clean.

  • Python 3.12 (ubuntu-latest's version): tests/pin_classes_test.py and tests/pin_guard_test.sh pass in a python:3.12 container with -W error::DeprecationWarning.

  • Not run here: the Homebrew, nix, nfpm and Arch PKGBUILD packaging jobs and the agent example jobs. None of them touch what this PR changes; CI runs them on this PR.

@88plug
88plug force-pushed the claude/fresh-pin-classes branch from 3d8c362 to d2c5ca0 Compare October 30, 2026 00:53
… out of workflows

pins/classes gives every pin in the repo a class: ours (the default, always the
newest stable release), eat-stock (tracks Intel EAT's releases), ledger (a ledger
row's runner or records) or historical (a finished run's record). A ledger line is
an arm while any of its rows is PENDING in the EXPERIMENTS.md Hypotheses table and
historical once every row has a verdict, so a verdict landing needs no
reclassification. historical-ref lines hold finished runs' exact pins that live
runners keep. hack/pin-classes answers class, effective, excludes and coverage.

hack/pin-guard enforces the ledger rules on a diff: a historical record is never
modified, deleted or renamed; a historical-ref string keeps its count; an arm's pin
tokens change only with a dated amendment heading in the same diff that names a
PENDING row of the line or one of its aliases; an autobump branch touches neither
.github/ nor pins/classes, and EXPERIMENTS.md only on autobump/amend-*; an
unclassified pin-bearing file under bench/ or oracle/ fails. A new ci.yml job,
pin-guard, runs it with the tests and hack/pin-classes coverage.

scripts/images-lock: repin and the new drift subcommand leave ledger, historical
and historical-ref files alone; update --only FILE... resolves and checks only the
references those files source and keeps the other entries, so one moved digest
elsewhere no longer fails a bump; http() retries 429 and 5xx up to five times and
honours Retry-After; pins fails on a digest-only reference in an ours or eat-stock
file.

The digest-only refs in the launcher, share/ and CI now carry the tag that resolves
to the same digest (checked against each registry); no digest changed. Consumers
that compare or cache by image (plan cells, tune fingerprints, autoquant cache,
oracle bundles and release checks) compare name plus digest, so their behaviour is
unchanged. Tool versions the workflows installed inline (Prometheus, kubeconform,
Chrome for Testing, jsonschema, helm v4, shellcheck, packaging) move to
hack/ci-tools.env, which each job loads into GITHUB_ENV, so a token without the
workflows permission can move every pin.
A ledger line listed only the rows whose ledger sections name its path, so a
runner another row runs or imports turned historical when its own rows finished
while the reusing row was still PENDING. #262 landed the H57dp and H57de
verdicts and bench/h57d/** went historical while wildct's H57w rows still run
it through bench/wildct/row.sh; the guard then refused #268's H57d amendment R2
edit of bench/h57d/arm.sh as a historical edit.

A scan of every ledger runner's code for the bench/ paths it calls found the
reuses, and each line now lists the reusing rows and their amendment prefix:
bench/h57d (the H57w rows; h57d.py also H59), bench/h54/h54.py with its tests
and GSM8K items (H59, H60), bench/prefill-guard's cpufreq.sh and prompt.txt
(H55), crescent's serving.py (the cross-host rows, H60, H59),
bench/xhost/fetch_models.py (the wildct rows), bench/load (the desktop,
crescent and cross-host rows and H59w) and bench/shared-prefix (H59). The rest
of each tree keeps its own rows, and the H57dp and H57de records get their own
historical line now that the H57d tree is an arm again.

tests/pin_classes_test.py checks each reuse: the reusing file still names the
path, the path's line lists the row, and the path is an arm while the row is
PENDING.
@88plug
88plug force-pushed the claude/fresh-pin-classes branch from d2c5ca0 to 8af6e5a Compare October 30, 2026 05:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant