Repository navigation
models/pins.json + tools/pin_watch.py: which revision of each upstream model we depend on, checked daily - #1148
Conversation
05dc122 to
4721a57
Compare
|
Second commit ( A stick image records which snapshot it was built from —
For So the two chat models the image bakes pin to exactly the commits the image was built from — the first commit's pins were derived from the catalogue and the image cache was not consulted, which makes this a cross-check rather than a restatement. Also in this commit,
Note for the record: the c25 cache also shows the staged |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES — two items, both small; the record itself is right and I reproduced its central claim independently
1. The CI build is red on this head, at the check the body says is green
$ python3 tools/copyright.py --check
run: python3 tools/copyright.py --fix
missing copyright notice: docs/model-pins.md rc=1
$ gh run view 42085447379 --log-failed
build Copyright notice on every file ... python3 tools/copyright.py --check
build Copyright notice on every file missing copyright notice: docs/model-pins.md
build Copyright notice on every file ##[error]Process completed with exit code 1
docs/model-pins.md starts at # Model pins: …; every other new file here has the block, and docs/registry.md shows the house shape. python3 tools/copyright.py --fix then re-commit closes it. Why the body says rc 0 is the trap I have hit myself: copyright.py walks git-tracked files, so it passes in a tree where the new file is still untracked and only starts failing after the commit that adds it.
2. The daily check has three outcomes but two exit codes — an outage, and a deleted upstream, both look clean
check() folds every unreadable repo into notes and main() ends return 1 if changes else 0, so skips cannot affect the exit code. report() then prints the all-clear regardless of notes:
if not changes:
out += ["Every pinned upstream repo is at the revision `models/pins.json` records.", ""]
I demonstrated it twice rather than reading it:
$ HTTPS_PROXY=http://127.0.0.1:9 python3 tools/pin_watch.py --check --pins /tmp/two-pins.json
# Model pins: 0 of 2 moved (2026-11-05)
Every pinned upstream repo is at the revision `models/pins.json` records.
## Not checked
- …Apertus-8B-Instruct-2509-GGUF: could not be read (<urlopen error [Errno 111] Connection refused>)
- …BlackMamba-1.5B-GGUF: could not be read (<urlopen error [Errno 111] Connection refused>)
exit=0
$ # a repo that has been deleted or made private:
# Model pins: 0 of 3 moved (2026-11-05)
Every pinned upstream repo is at the revision `models/pins.json` records.
- unsloth/this-repo-does-not-exist-1148: could not be read (HTTP Error 404: Not Found) — the pin was not checked
exit=0
In pin-watch.yml both are moved=0 → the bump step is skipped → a green daily run. Two things make this worse than a missing feature:
- the summary line is an assertion that every pin was verified, printed in the run where none was — the same class as #1111's skip markers printing PASS, which this fleet has now fixed twice;
- a 404 is the event a pin watcher exists for (the vendor deleted, renamed or privatised what we depend on) and it is indistinguishable here from a transient blip —
docs/model-pins.mdsays "It exits non-zero when anything moved, so the run is visible even if the PR is not opened", which is true for moves and false for the case where nothing could be read at all.
Two small fixes, and the second needs no workflow change:
report(): withnotes, the first line must count what was actually checked (N of M checked, K not checked) and must not print the all-clear;main(): return 2 when nothing (or nothing but notes) was checked, and give a 404 its own state (upstream gone / private) rather than folding it into "could not be read".pin-watch.ymlalready fails on any rc other than 0/1 —if [ "$rc" -ne 0 ] && [ "$rc" -ne 1 ]; then exit "$rc"; fi— so 2 fails the job as it should, andcensus-watch.yml(#664) is the precedent for separating "the watcher found something" from "the watcher did not run".
What I verified, independently of the tool
The central claim reproduces from the Hub with my own instrument, which is the part that matters most here:
unsloth/Qwen3.8-Flash-Next-GGUF pinned commit d9620119… HF main d9620119… match=True
MTP/…Q8_0.gguf held cd87e5d1a4da… head 58a47d6a2163… size 4,137,429,120 / 4,137,429,120 same=False
UD-Q4_K_XL/…00001-of-00004 held 4448186216b3… head acc44040f74d… size 10,946,624 / 10,946,618 same=False
UD-Q4_K_XL/…00002-of-00004 held 3f342f1c1580… head 45792ae86907… size 49,859,583,136 / 49,720,539,552 same=False
1bit-MONSTER/Apertus-8B-Instruct-2509-GGUF commit 91b2ce8c… = main; its Q4_K_M oid identical clean
So the "head is still d9620119, but the bytes it serves are not the ones we hold" row is real, and the MTP row is the argument for the whole design: same size, different bytes — a size-and-name check would have called it clean. --check's structure (146 repos, 184 files, 134 with a template sha, 1 fileless entry) matches the body exactly, and ci.yml runs only the fixture self-test, which is the right split.
Two things I looked at in the tool that are right and easy to get wrong: a truncated metadata read is a hard error, not "no template" (if len(blob) < size: raise ValueError(... no complete GGUF metadata)) with doubling to a 192 MiB cap, so a partial fetch cannot masquerade as a template removal; and --bump refuses when a tracked file vanished upstream instead of silently dropping it.
Nit: --self-test prints 18 ok lines, not the 16 in the body.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
Full run over all 146 pins on this box, for the record — the claim holds, and it narrows the fix in item 2:
$ python3 tools/pin_watch.py --check
# Model pins: 1 of 146 moved (2026-11-05)
rc=1
moved: unsloth/Qwen3.8-Flash-Next-GGUF not checked: 0
The five file rows are the body's table character for character (MTP cd87e5d1a4da…/4,137,429,120 B vs 58a47d6a2163…/same size; shard 1 4448186216b3… 10,946,624 → acc44040f74d… 10,946,618; shards 2-4 as listed), and I had already reproduced those from the Hub API with my own script, so the tool's data and its report agree with an independent instrument.
What that adds to item 2: on a healthy run there is no ## Not checked section at all — notes is empty, so the false all-clear I demonstrated only ever appears in exactly the runs where something could not be read. That means the fix can be strict without any cost to a normal day:
notesnon-empty → the first line must beN of M checked, K not checked, never "Every pinned upstream repo is at the revision …";len(notes) == len(pins)(nothing checked) → exit 2, whichpin-watch.ymlalready turns into a failed job;- a 404 keeps its own state, because "the upstream is gone" is a finding, not a skip.
Since a clean day produces no notes, nothing about the everyday report changes.
|
CI is green at That step also exposed a defect in my own test, now fixed in Faking it takes the self-test to 84 ms and adds two assertions on the path the PR body renders (the upstream commit title reaches the change record and the report), so it is 20 assertions now rather than 16 — a fixture test should never be the thing that needs the network. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: CHANGES
Re-verdicted at e19c85e7. One of my two blockers is closed, the other is intact and the new commit did not touch it.
Closed: the copyright notice. tools/copyright.py --check -> rc 0 at this head (it was rc 1 on docs/model-pins.md at abd0c750, which is what reddened the Copyright notice on every file step).
Still open: a watcher whose "clean" and "could not check" are the same outcome, which is the failure this watcher exists for. Reproduced at this head, with a pins file whose only repository does not exist:
$ python3 tools/pin_watch.py --check --no-template --pins <one fake repo> --out report.md
# Model pins: 0 of 1 moved (2026-11-05)
Every pinned upstream repo is at the revision `models/pins.json` records.
## Not checked
- fake/definitely-not-a-model-1bit: could not be read (HTTP Error 404: Not Found) — the pin was not checked
$ echo $?
0
Exit 0, headline claiming every pin was verified, while the one pin was not checked at all. In pin-watch.yml that is moved=0 -> no PR -> a green daily run, so a vendor deleting, renaming or privatising a model we bake into a stick image is indistinguishable from a quiet day. main() still ends return 1 if changes else 0 (tools/pin_watch.py:531); the return 2 at :502 is the no-mode/help path, not this one. The fix I asked for stands and is small: first line counts what was checked, rc 2 when nothing could be checked (the workflow fails on anything outside 0/1), and 404 keeps its own state as --bump already does (:510-513, "gone upstream"). census-watch.yml (#664) is the precedent for the exit-code split.
What the new commit did fix, and it is a real fix: --self-test is network-free now — time python3 tools/pin_watch.py --self-test -> 0.057 s and 20 ok lines (the fixtures no longer reach commits_since()), so CI's fixture step no longer depends on Hugging Face being up. The body still says 16 and the commit message says 18 assertions; both are behind the 20 the tool prints at this head.
models/pins.json is unchanged between abd0c750 and this head, so the pin record I re-derived earlier stands: --check over all 146 pins gives 1 of 146 moved, rc 1, 0 not checked, with the five unsloth rows matching the body character for character (MTP cd87e5d1a4da… vs 58a47d6a2163… at the same 4,137,429,120 B; shard 1 4448186216b3… 10,946,624 -> acc44040f74d… 10,946,618 B). A healthy run prints no ## Not checked section at all, so the strict rule costs a normal day nothing.
Nothing else changed at this head, and the adoption-gate question from my earlier review is unchanged and still not a blocker.
|
CHANGES addressed at Your repro, now (a pins file whose only repository does not exist): All three things you asked for, and they are separate:
A healthy day costs nothing, as you predicted from
Body corrected too: the assertion counts you caught (20 at |
|
A third commit at The pin recorded
Now a Measured:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY
Both blockers are closed, and the second one is closed by the shapes I asked for rather than by a doc sentence.
Blocker 2 — the watcher can no longer call an unread repo a clean day. Re-run of my own repros at 5c2a3c3c:
- the only pinned repo answers 404: exit 1, first line
# Model pins: 1 of 1 moved (2026-11-05), and a dedicated**Gone upstream** — the Hub answers 404 for this repository, so every pin on it is unverified: a delete, a rename and a privatisation look exactly like thissection. It no longer reads as "every pin is at the revision". - the only pinned repo cannot be read for any other reason: exit 2 —
pin_watch: no pin could be checked (1 unreadable) — this is not a clean runon stderr, and the report's first line is# Model pins: 0 of 1 checked, 1 not checked, 0 movedwithNo pin moved among the 0 repo(s) that could be read; 1 could not be checked and are listed under Not checked — an unread repo is not a clean one. That is the codepin-watch.ymlfails on, andreport()now counts what it checked rather than asserting what it did not.
The new template defect is real and its two numbers reproduce. A container's template is the chat_template string, not the file, and the tool now hashes that:
FastFlowLM/Qwen3-0.6B-NPU2 tokenizer_config.json
file sha256 51015f78470ac7f7… <- the number it used to record
chat_template sha a136047ba140347b… <- what the engine's serve line names, now pinned
1bit-MONSTER/Qwen3.6-35B-A3B-NPU chat_template.jinja
raw file: 7764 B e84f32a23fdda276… <- == sha256sum of the file, as the commit says
its tokenizer_config.json carries no `chat_template` key -> pinned `null`, a measured "no template"
All three FastFlowLM NPU2 rows now carry a136047ba140347b…; the JINJA row carries e84f32a2… and 7764 B; and I confirmed by fetching the file that tokenizer_config.json for that container genuinely has no chat_template, so null is a measurement rather than the old "never looked".
--self-test: 35 ok lines, 0.05 s, still no network. tools/copyright.py --check rc 0. Full --check over every pin: 1 of 147 moved, rc 1, and the moved repo is the same unsloth/Qwen3.8-Flash-Next-GGUF with the same rows (MTP cd87e5d1a4da… vs 58a47d6a2163… at the same 4,137,429,120 B; shard 1 4448186216b3… 10,946,624 -> acc44040f74d… 10,946,618 B) — so the reading is unchanged by the template fix, which is what "the pin is only as good as the number it records" needs.
One thing to note for the merge, not a blocker: --check builds changes[...]["pinned"] on the 404 path, so a hand-written pins file without that key raises KeyError instead of a diagnostic. My first fixture was exactly that; real entries all carry pinned (schema commit/pinned/used_for/files), so this only bites a crafted file, and it fails loudly rather than quietly.
|
CI green at |
…pen PR would add The reviewer's blocker, and it is the same class as the text being fixed: the paragraph said the digests for the four rows "are already measured — models/pins.json records the template of every file we serve and publish", and `models/pins.json` does not exist in this tree (`ls` fails, `git cat-file -e origin/main:models/pins.json` fails). It is engine #1148, open, so a reader on `main` was sent to a path that is not there, and the "already measured" claim rested on an unmerged working tree — including the one digest that exists nowhere else yet (`short`'s 57f1fd00f0…). Now the paragraph says what a reader can check today: - three of the four rows are in `config/template-pins.json` **now** — zaya1-8b-q4_k_m.gguf b55ecfbbbc…, qwen3-coder-30b-a3b-instruct-q4_k_m.gguf 87710339d2…, qwen3.8-27b-ud-q4_k_xl.gguf 12827f24b7… — because they are the same files the load-time check already pins (verified against the tree at this head); - the fourth, `short`'s Qwen3-0.6B-Q8_0.gguf 57f1fd00f0…, is **not in any table in this tree yet**; it is measured, it is added by #1158, and it is recorded with every other served/published file in #1148's models/pins.json — said as such rather than implied to be here. The Q4NX sentence now links docs/npu-chat-template.md, which is in this tree (#1115). Nothing else in the paragraph changed: what runs today, what it catches, and what step 3 adds all verified in the review.
…m model we depend on, checked daily
The census counts what the engine can run and nothing recorded WHICH revision of an upstream model we
serve, ship or convert from, so unsloth's 2026-11-04 re-quantization of Qwen3.8-Flash-Next-GGUF ("Better
UD 3.1 quants"; UD-IQ4_XS went 3 shards -> 4) went unnoticed: the files we re-host are the 2026-11-02
ones, under unchanged names.
models/pins.json pins 146 repos (184 files): the laya-first serve targets, the stick catalogue's rows, our
published re-hosts, and the upstream repos behind them. Each entry records the commit, the files WE HOLD
(content identity + size + the sha256 of the chat template that file carries) and what uses it — so
"upstream moved" and "the bytes we hold are stale" are both checkable, the second one because a pin taken
after a vendor re-push would otherwise store our older copy under a newer commit.
tools/pin_watch.py --check (pin-watch.yml, daily 05:43 UTC) compares every pin with its upstream main,
writes a Markdown report and exits 1 when anything moved; for a moved file it re-reads the chat template
from the new revision (GGUF metadata prefix over an HTTP range request, no tensor) and names it as
unchanged / TEMPLATE CHANGED / added / removed. It opens one PR per changed repo, bumping only that
repo's entry with the report as the body; --bump refuses a pin whose tracked file vanished upstream
rather than dropping it, and nothing is adopted automatically.
First run against the committed file, the finding is the one that prompted this:
unsloth/Qwen3.8-Flash-Next-GGUF HEAD d9620119, our copies are not its bytes
UD-Q4_K_XL/…-00001-of-00004.gguf we hold 10,946,624 B 4448186216b3…, upstream 10,946,618 B acc44040f74d…
UD-Q4_K_XL/…-00003-of-00004.gguf we hold 49,376,141,504 B 56758f40269c…, upstream 32,803,030,176 B 8c76e79d444b…
templates unchanged (12827f24b742…, c3cf9e34abf4…) — a re-quantization, not a template change
--self-test: 16 assertions on fixtures (a GGUF metadata read with and without a template, a moved pin, a
stale copy at the same commit, an unreadable repo, the bump refusal), wired into ci.yml. The other 145
pins are clean today.
… own HF cache The "stage on the stick" half of the rule had no provenance: the image tree records which snapshot it was built from (~/stage-c25/hf/models--<org>--<name>/snapshots/<commit>), and that is a commit sha, not a guess. - `stabilityai/sd-turbo` was the one model the c25 image bakes that had no pin: added at the snapshot the cache names (`b261bac6fd2cf515557d5d0707481eafa0485ec2`), with its 5,214,561,328-byte safetensors hashed locally and found equal to the `lfs.oid` upstream serves at that commit (3f067a1b943cf162…). It is an image model and carries no chat template, so the pin records none. - The image's two chat models were already pinned from the catalogue, and the independent check agrees: both pins sit at exactly the commits the c25 cache names (`unsloth/Qwen3-4B-Instruct-2507-GGUF` @ a06e946b, `FastFlowLM/Qwen3-0.6B-NPU2` @ b5847992). 147 repos / 185 files. `--check` still reports the one stale pin (`unsloth/Qwen3.8-Flash-Next-GGUF`) and nothing else. docs/model-pins.md now says how a baked model is pinned and measures the coverage of the 154 staged files on the box: 11 match a pinned file by name, the rest are test vocabularies, conversion intermediates, or extra quant variants of repos that are pinned at revision level.
The fixture that exercises a moved pin reached `commits_since()`, which was not faked, so the test made a real Hugging Face call, got a 404 for `fake/moved`, and sat through the retry backoff: 33 s of wall clock for 0.08 s of CPU. Faking it takes the self-test to 84 ms and, while there, asserts the upstream commit title reaches both the change record and the report. 18 assertions, same coverage as before plus the commit-title path that the PR body renders.
The reviewer reproduced the failure this watcher exists to prevent: with a pins
file whose only repository does not exist, `--check` printed "0 of 1 moved" and
"Every pinned upstream repo is at the revision models/pins.json records", exited
0, and in pin-watch.yml that is `moved=0` -> no PR -> a green daily run. A vendor
deleting, renaming or privatising a model we bake into a stick image was
indistinguishable from a quiet day.
Three outcomes are now three outcomes (docs/model-pins.md carries the table):
- 404 is its own state: the repo is carried as `gone upstream` and reported as a
finding ("a delete, a rename and a privatisation look exactly like this"),
never as a skipped check. `--bump` refuses it with that sentence instead of a
traceback, and `--pr-body` returns the operator's decision text.
- the first line counts what was read whenever anything was not: "N of M
checked, K not checked, J moved", and the all-clear sentence is printed only
when nothing was left unread.
- exit 2 when no pin could be read at all (len(notes) == len(pins)): the
workflow fails on any code outside 0/1, so a Hub outage or a rate-limit is a
red job rather than a green one.
Before (the reviewer's repro, one fake repo):
# Model pins: 0 of 1 moved (2026-11-05)
Every pinned upstream repo is at the revision `models/pins.json` records.
## Not checked
- fake/…: could not be read (HTTP Error 404: Not Found)
rc=0
After:
# Model pins: 1 of 1 moved (2026-11-05)
## `fake/definitely-not-a-model-1bit`
**Gone upstream** — the Hub answers `404` for this repository, so every pin on
it is unverified: a delete, a rename and a privatisation look exactly like this.
rc=1
and for a read that failed for another reason (429/timeout, fixture in
--self-test): "0 of 2 checked, 2 not checked", no all-clear, rc=2.
A healthy day is byte-identical: --check over all 147 pins still writes
"# Model pins: 1 of 147 moved" with the same body (diff empty), rc 1 — notes is
empty on a normal run, so the strict rule costs nothing.
--self-test 20 -> 29 assertions (the 404 state, the count line, the no-all-clear
pair, --bump's refusal, --pr-body's text, and the rc=2 run), network-free.
…s JSON file
The pin is only as good as the number it records. Four tracked files are Q4NX containers, and for them
the tool had two defects that cancel each other out:
- `remote_template()` fell through to "hash the text file verbatim" for anything that is not a `.gguf`,
so for a `tokenizer_config.json` it would have hashed the whole JSON file (51015f78470ac7f7… for
Qwen3-0.6B-NPU2) while the engine's serve line names the `chat_template` **string**
(a136047ba140347b…). Two numbers for one template — the disagreement #1131 was about.
- `--bump` only re-read a template that was not already `null`, so the three FastFlowLM entries could
never gain one: pinned at `null`, they claimed a container carries no template when it carries
Qwen3's.
Now a `.json` path is parsed and its `chat_template` string hashed (a named-template list is refused
rather than guessed), `chat_template.jinja` stays verbatim, and `--bump`/`--pin` read the template of
every tracked file, so a `null` is a measured "no template" rather than an old one.
Measured, and cross-checked against the engine's own line:
FastFlowLM/*-NPU2 tokenizer_config.json null -> a136047ba140347b… (= the serve-log digest recorded
in #1095 for a real container)
1bit-MONSTER/Qwen3.6-35B-A3B-NPU chat_template.jinja was not tracked at all; its
tokenizer_config.json carries no template, so the template
this container runs was unpinned. Now tracked:
e84f32a23fdda276… 7764 B (== sha256sum of the raw file)
`--check` over all 147 pins is unchanged (1 moved, same body, rc 1), `--self-test` 29 -> 35.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
Conflict note, verdict unchanged (READY): #1148 is CONFLICTING/DIRTY as of 17:5x AST, in .github/workflows/ci.yml:
$ git merge-tree --write-tree --name-only origin/main 5c2a3c3c
Auto-merging .github/workflows/ci.yml
CONFLICT (content): Merge conflict in .github/workflows/ci.yml
ci.yml has moved under it several times tonight (the census/release lanes each add a job), so this is a rebase-and-re-resolve in one file; the PR's own content - models/pins.json, tools/pin_watch.py, docs/model-pins.md - is untouched by the conflict.
Two things worth carrying into the rebase rather than losing: the watcher's exit-code contract (rc 2 when nothing could be checked, and 404 as its own "gone upstream" state) and the container-template rule (a .json path hashes its chat_template string, not the file). Both are covered by --self-test's 35 ok lines, so a bad merge will fail there.
5c2a3c3 to
20122d7
Compare
|
Rebased onto main — the branch was python3 tests/hrx_long_prompt_test.py
python3 tools/template_bom.py --self-test # the chat-template BOM component
- name: Model pins
# fixtures only: models/pins.json itself is compared with the Hub by pin-watch.yml
run: python3 tools/pin_watch.py --self-test
Verified after the rebase, not before (all five commits rewritten, head
The earlier verdict was on |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY
Re-verdicted at 20122d78, and the conflict I flagged is gone: MERGEABLE/BEHIND now, after the rebase onto current main.
The rebase preserved the reviewed content exactly - git diff 5c2a3c3c 20122d78 is empty for tools/pin_watch.py, models/pins.json and docs/model-pins.md, so every check from my 5c2a3c3c review still applies to these bytes:
rc 2when nothing could be checked (pin_watch: no pin could be checked (N unreadable) — this is not a clean run), and 404 as its own**Gone upstream**state instead of a clean-day headline;- a container's template is its
chat_templatestring, not the file:FastFlowLM/Qwen3-0.6B-NPU2file51015f78…vs stringa136047b…, and thechat_template.jinjarowe84f32a2…at 7764 B; --self-test35oklines in 0.05 s, no network;- full
--checkover every pin:1 of 147 moved, rc 1, the sameunsloth/Qwen3.8-Flash-Next-GGUFrows.
Merge note unchanged: #1157 cites models/pins.json and names it as this PR's, so this one goes first (or #1157 is re-read after it lands).
|
CI green at |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
|
Post-merge smoke of the merged code (not my branch), run at
One defect found while checking that the workflow can do this unattended: the step that opens the bump PR had never been executed (it runs only when |
…one that does not (#1157) * docs/laya-first: say which template check runs today, and that the per-row digest is step 3 The section ended with a present-tense claim the tree does not meet: that `~/.local/share/1bit/laya-first.json` "ships with the digests measured on 2026-11-05" and that "the table's validator (§8 step 3) fails" a row that skips one. Measured on main: `config/laya-first.json` — the file compiled in as `laya_first_default.h` — has no `template_sha256` in any of its four rows, nothing in `app/`, `src/`, `tests/`, `include/` or `os/` reads one, and no installed `<laya home>/laya-first.json` exists on the c25/c26 media, the staging trees or the box (§8 step 3 is where that field and the pull-time check are scheduled). A design note that asserts a check which does not run is the exact failure this row is supposed to prevent, so the paragraph now states what is true: - the one template check on a routed pull today is the load-time one, against the engine's `config/template-pins.json` — 7 names, three of them these rows; - that check warns unless the row ships `--template-pin-strict` (§9), and it catches one thing: the same name carrying a different template; - the field, the per-row requirement and the pull-time refusal are §8 step 3, and the digests they will need are already measured — `models/pins.json` records 57f1fd00f0… (short), b55ecfbbbc… (prose), 87710339d2… (code), 12827f24b7… (long_doc). Doc only; no code, no table and no operand changed. * docs/laya-first: cite the digests that are in this tree, not one an open PR would add The reviewer's blocker, and it is the same class as the text being fixed: the paragraph said the digests for the four rows "are already measured — models/pins.json records the template of every file we serve and publish", and `models/pins.json` does not exist in this tree (`ls` fails, `git cat-file -e origin/main:models/pins.json` fails). It is engine #1148, open, so a reader on `main` was sent to a path that is not there, and the "already measured" claim rested on an unmerged working tree — including the one digest that exists nowhere else yet (`short`'s 57f1fd00f0…). Now the paragraph says what a reader can check today: - three of the four rows are in `config/template-pins.json` **now** — zaya1-8b-q4_k_m.gguf b55ecfbbbc…, qwen3-coder-30b-a3b-instruct-q4_k_m.gguf 87710339d2…, qwen3.8-27b-ud-q4_k_xl.gguf 12827f24b7… — because they are the same files the load-time check already pins (verified against the tree at this head); - the fourth, `short`'s Qwen3-0.6B-Q8_0.gguf 57f1fd00f0…, is **not in any table in this tree yet**; it is measured, it is added by #1158, and it is recorded with every other served/published file in #1148's models/pins.json — said as such rather than implied to be here. The Q4NX sentence now links docs/npu-chat-template.md, which is in this tree (#1115). Nothing else in the paragraph changed: what runs today, what it catches, and what step 3 adds all verified in the review. --------- Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Why
The owner asked on 2026-11-05 whether we have a daily pin for unsloth's models. We did not:
tools/census.pymeasures which architectures the engine can run across HF, and nothing in the repository recorded which revision of an upstream model we serve, stage on the stick, benchmark, or convert from. So unsloth's 2026-11-04 re-quantization ofQwen3.8-Flash-Next-GGUF— "Better UD 3.1 quants", "mix n match engram", UD-IQ4_XS from 3 shards to 4 — went unnoticed, and the files we re-host are the 2026-11-02 ones under unchanged names.What this adds
models/pins.jsonlaya-firstserve targets, the stick catalogue's rows, our published re-hosts, and the upstream repos behind themtools/pin_watch.py--check(compare + report + template re-read),--pr-body,--pin,--bump,--self-test; exit 0 clean, 1 moved-or-gone, 2 nothing could be read.github/workflows/pin-watch.ymldocs/model-pins.mddocs/registry.mdA pin is per repo: the
commitwe pinned, and per file we take from it the content identity Hugging Face reports (lfs.oid, the sha256 of the bytes; the git blob id for a small text file), the size, and the sha256 of the chat template that file carries.used_forsays which serve class, catalogue row or re-host the repo is for.filesrecords what we hold, not only what upstream said, and the check reconciles both — a pin taken after a vendor re-push would otherwise store our older copy under the newer commit and look clean:commitvs the repo's currentmain→ "upstream moved", plus its commit titles and any new.gguf/.q4nx;For a mismatched file it also re-reads the template at the new revision — a GGUF's
tokenizer.chat_templatefrom the metadata prefix over an HTTP range request (no tensor is ever fetched) — and reportsunchanged/TEMPLATE CHANGED <old> -> <new> — re-audit before adopting/ added / removed. That is the gate a re-quantization has to pass before anything may adopt it: weights moving under a stable name is ordinary, the instructions the model obeys on every request moving under it is not (docs/template-publishing.md).The first run, against the committed file — the case that prompted it
Note the MTP row: same size, different bytes. A size-only check would have called that clean. The template column is the reassuring half — this re-quantization did not touch the template, so adopting it is a weights decision, not a template one.
prism-ml/Ternary-Bonsai-2-27B-ggufis pinned at its head with no held files: its rename on 2026-11-04 left no path in common with our copy, so the entry watches the revision and nothing else (recorded in the entry'sused_for).What is deliberately not pinned
base_modelon a card is provenance, not a dependency — what we took is the file we re-hosted (upstream_gguf), and that file is pinned.third_party/*git pins keep their own mechanisms (tools/check_pins.py, the weeklytools/weekly_changes.py).Verification
tools/pin_watch.py --self-test→ 35/35 on fixtures, no network (0.06 s): a GGUF metadata read with and without a template, a moved pin with a changed template, a stale copy at the same commit (the case above), a transient read failure (a note, not a change), a 404 (a finding:gone upstream), the report'sN of M checked, K not checkedfirst line with and without a note, exit 2 when nothing could be read, and--bumprefusing both a pin whose tracked file vanished upstream and a repo that is gone. Wired intoci.yml.--checkover all 147 pins (1 moved, 146 clean);--bumpon a copy ofmodels/pins.jsonfor the moved repo, then--checkagain → 0 moved, exit 0;--pr-bodyon the same repo produces the body the workflow posts. The reviewer's repro (a pins file whose only repo does not exist) now reports**Gone upstream**and exits 1, and a run whose reads all fail exits 2.tools/copyright.py --check→ rc 0.Two notes for whoever reviews:
HF_TOKENsecret for the range reads; without it the Hub is read anonymously and the tool's existing backoff covers the rate limit. OnlyHRX_BUMP_TOKENis required, as incensus.yml.workflowscope, so it cannot create.github/workflows/pin-watch.ymlor editci.yml. Both files are in this PR for review.Merging this adds the record and the daily check — it adopts nothing. The one pin that is stale (
unsloth/Qwen3.8-Flash-Next-GGUF) stays as it is, listing the bytes we actually hold, until someone decides to re-take the new quants; the report is what makes that decision visible instead of invisible.