Skip to content

docs/laya-first: name the template check that runs today, instead of one that does not - #1157

Merged
bong-water-water-bong merged 3 commits into
mainfrom
i-tmplsec-laya-gate
Nov 5, 2026
Merged

bong-water-water-bong merged 3 commits into
mainfrom
i-tmplsec-laya-gate

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Root cause. docs/laya-first.md §"A row is not served with an unexpected template" ended with a
present-tense guarantee for something the tree does not have: that ~/.local/share/1bit/laya-first.json
"shipped with the digests measured on 2026-11-05", that "adding a row means adding its digest", and that
"the table's validator (§8 step 3) fails one that does". The field, the per-row requirement and the
pull-time refusal are §8 step 3 — scheduled work, not shipped work. §8 step 3 is where template_sha256
becomes required per row and where the pull-time check that reads it lands.

Measured on main a56f0e98 (read-only).

claim in the doc what the tree has
the shipped table carries the digests config/laya-first.json — compiled in as laya_first_default.h — has no template_sha256 in any of its 4 rows
a validator fails a row without one grep -rn template_sha256 app/ src/ tests/ include/ os/ returns nothing
the installed copy ships with them no <laya home>/laya-first.json exists on the c25 or c26 /data, in ~/stage-c25, ~/stage-c26, or on this box
3 of the rows are the files the engine already pins true — config/template-pins.json pins zaya1-8b-Q4_K_M.gguf, Qwen3-Coder-30B-A3B-Instruct-Q4_K_M.gguf, Qwen3.8-27B-UD-Q4_K_XL.gguf; the short row's Qwen3-0.6B-Q8_0.gguf is not in that 7-row table

The fix. The paragraph now states what is true and keeps the requirement where it belongs: today a routed
pull is checked at exactly one place — load time, against the engine's config/template-pins.json, which
warns unless the row ships --template-pin-strict (§9, the owner decision) and catches only "the same name,
a different template". The field, its per-row requirement and the pull-time refusal are labelled §8 step 3,
and the digests they will need are named as already measured in models/pins.json (57f1fd00f0… short,
b55ecfbbbc… prose, 87710339d2… code, 12827f24b7… long_doc). The field's row in the schema table now
says it is required when step 3 lands rather than implying it is required now.

This is the lane's own defect: a design note asserting a check that does not run is the failure mode the
template-audit row exists to catch.

Doc only — no code, no table, no operand changed. docs/laya-first.md +19/−7.

No issue closes: this corrects #1089's text rather than fixing a reported bug.

…r-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.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: CHANGES

One item, and it is the same kind of thing the PR is fixing: a sentence that a reader on main can check in one command and find false.

Everything else in it verified at this head (6174960e, base main):

  • config/laya-first.json exists and contains 0 occurrences of template_sha256; the compiled-in default is generated from it (CMakeLists.txt:93 writes generated/laya_first_default.h, included at app/laya_first.cpp:29), so "carries no template_sha256" is right.
  • Nothing under app/, src/, tests/ or os/ reads template_sha256: grep -rn template_sha256 app/ src/ tests/ os/ -> no matches.
  • config/template-pins.json holds 7 pins and includes the three rows named, with the digests as quoted: 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…; Qwen3-0.6B-Q8_0.gguf is not in it ('Qwen3-0.6B-Q8_0.gguf' in json.dumps(...) -> False).
  • --template-pin-strict is real and defaults to warn: app/serve.cpp:188 (template_pin_strict = false), :773 (if (!o.template_pin_strict) -> the warning path), :777 (the refusal), env ONEBIT_TEMPLATE_PIN_STRICT at :2815.

The blocker: models/pins.json is not in the tree this merges into. The new paragraph reads

When step 3 lands, the digests are already measured — models/pins.json records the template of every file we serve and publish, including all four rows here (57f1fd00f0… short, b55ecfbbbc… prose, 87710339d2… code, 12827f24b7… long_doc …)

and on this head ls models/pins.json -> absent, git cat-file -e origin/main:models/pins.json -> fails. That file is engine #1148, which is open and currently CHANGES, so as written a reader on main is sent to a path that does not exist, and the "already measured" claim rests on an unmerged working tree (including the 57f1fd00f0… short-row digest, which is only in that file - the other three are in config/template-pins.json and I did check them).

Two ways to close it, either is fine: cite the three digests that are in config/template-pins.json today and say the fourth is recorded with #1148, or say "recorded in #1148's models/pins.json, not in this tree yet" and land this after that PR. The rest of the paragraph — what runs today (load-time against config/template-pins.json, warn unless --template-pin-strict), and what it can and cannot catch — is accurate and worth having.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

#1157 ready for review at 6174960.

…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.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

CHANGES addressed at e7eedb0c — the blocker was exactly right, and it is the same class as the text being fixed: I sent a reader on main to models/pins.json, which only exists in #1148 (open). Fixed by citing what is checkable in this tree today:

Nothing else in the paragraph moved: what runs today (load time, against config/template-pins.json, warn unless --template-pin-strict), what it can and cannot catch, and that the field + pull-time check are §8 step 3 — all as you verified.

Self-check on my own diff, since that is how the first version slipped: every repo path my added lines cite now resolves on this head (config/template-pins.json ✓, docs/npu-chat-template.md ✓, models/pins.json explicitly labelled as #1148). The only other path-like strings in it are model file names, i.e. keys of the pin table, which is what they are meant to be.

Doc-only, +9/−6. build was green at the previous head; this changes two sentences of prose.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

#1157 ready for review at e7eedb0 (2 commits).

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY

Re-verdicted at e7eedb0c. The blocker is gone and the replacement is better than what I asked for: it separates what is in the tree from what an open PR would add, instead of dropping the fourth digest.

At this head:

  • the three digests it now attributes to config/template-pins.json are there, at the digits quoted: 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… (7 rows in that table);
  • short's 57f1fd00f0… is described as "measured, and it is added by #1158 (and recorded with every other file we serve and publish in models/pins.json, #1148)" - both of which are open PRs and named as such, so nothing points at a file that is not in the tree. For the record the digest is right: 1bit-MONSTER/Qwen3-0.6B-GGUF:Qwen3-0.6B-Q8_0.gguf hashes to 57f1fd00f0013a2b… (and Qwen's own repo gives the same; unsloth's copy of that name gives a different one, 5da44855…, which is why the source repo matters);
  • the new [npu-chat-template.md] link resolves - docs/npu-chat-template.md exists at this head.

Still accurate: config/laya-first.json carries no template_sha256 (0 occurrences), nothing under app/, src/, tests/ or os/ reads one, --template-pin-strict exists and defaults to warn (serve.cpp:188, :773, :777), and Qwen3-0.6B-Q8_0.gguf is not in template-pins.json today - which is exactly what the new paragraph says.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

CI green at e7eedb0c: build 6m40s, CodeQL pass, hw_gate skipped, pr_agent red only because coder-llm is off. Re-verdict requested.

@bong-water-water-bong
bong-water-water-bong merged commit 19d21ac into main Nov 5, 2026
9 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the i-tmplsec-laya-gate branch November 5, 2026 22:34
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

1089 - Partially compliant

Compliant requirements:

  • The GGUF's tokenizer.chat_template is now named and checked at load time
  • The line 1bit serve: template sha256=<sha256> is printed at every load
  • A pin table config/template-pins.json is compiled in
  • The pin is matched against --alias or the file's name
  • Mismatches print a warning and still serve, unless --template-pin-strict is used
  • The --template-pin-strict flag refuses the load and exits with code 1
  • The --template-pin option replaces the default pin table
  • The --no-template-pin option logs the sha and compares nothing
  • Models with no template and no pin print nothing
  • The docs/serve.md has been updated
  • The docs/template-publishing.md has been added
  • The docs/laya-first.md has been updated to reflect the current state vs future state
  • Tests have been added/updated for the new functionality

Non-compliant requirements:

  • The PR description mentions that the pull-time check for Q4NX models is not yet implemented, but this is noted as "Not in this PR"

Requires further human verification:

  • Verification that the config/template-pins.json entries are correct and match the actual templates in the GGUF files
  • Verification that the --template-pin-strict flag works as expected in all cases
  • Verification that the --no-template-pin flag works as expected in all cases
  • Verification that the --template-pin option correctly replaces the default pin table
  • Verification that the docs/laya-first.md correctly reflects the current state vs future state
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Inaccurate Documentation

The documentation states that the config/laya-first.json (compiled in as laya_first_default.h) carries no template_sha256 in any of its 4 rows, but the PR description also mentions that the table's validator (§8 step 3) fails one that does. This discrepancy needs clarification to ensure the documentation accurately reflects the current implementation.

**What runs today, and what does not.** The field and the pull-time check are §8 step 3, and neither is
in the tree yet: `config/laya-first.json` (compiled in as `laya_first_default.h`) carries no
`template_sha256`, nothing in `app/`, `src/`, `tests/` or `os/` reads one, and no installed
`<laya home>/laya-first.json` ships with digests. So a routed pull is checked at exactly **one** place
today — **load time**, against the engine's own `config/template-pins.json`, which pins 7 model names,
three of them the rows above (`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…`; the `short` row's `Qwen3-0.6B-Q8_0.gguf` is not in that table). That check warns unless
the row ships `--template-pin-strict` (§9), and it catches one thing only — the same name carrying a
different template. A file under a new name, or a row whose digest nobody recorded, is visible only as
the load line; that is the gap step 3 closes.

When step 3 lands it will need one digest per row, and three of the four are in the tree **today** in
`config/template-pins.json` — `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 those rows are the same files the load-time check already pins. The fourth, `short`'s
`Qwen3-0.6B-Q8_0.gguf` `57f1fd00f0…`, is not in any table in this tree yet: it is measured, and it is added
by #1158 (and recorded with every other file we serve and publish in `models/pins.json`, #1148). A Q4NX row
is read from its `tokenizer_config.json` because it has no GGUF ([npu-chat-template.md](npu-chat-template.md)).
Adding a row then means adding its digest: the table is not allowed to grow a row that skips that step, and
the validator that fails one is part of step 3, not something to assume today.

bong-water-water-bong pushed a commit that referenced this pull request Nov 6, 2026
…ut-unpinned repo is not "did not move"

`--json` is what the workflow's PR step reads, and its docstring calls it "the sweep". #1181 added it when
a sweep was only pins; #1193 added `--orgs`, which finds repos by a different route (we publish them and
nobody pinned them). Applied together, `--check --orgs --json` wrote `date, pins, changes, notes`: the
census rows survived only in the Markdown report, and the artifact the PR step consumes described half of
what the run looked at — the F1/#1157 failure mode, an artifact whose prose is wider than its contents.

Measured before, on the union tree: a sweep with `1 of 148 moved, 1 published but not pinned` wrote a JSON
with `changes: [unsloth/Qwen3.8-Flash-Next-GGUF]` and no census key at all, and
`--pr-body our-org/unpinned --from sweep.json` answered "did not move" — the one word that would send a
reader looking at the wrong thing, and (with `|| true` removed by #1181) the shape of failure that opens an
empty-bodied PR.

Now: the JSON carries `published_not_pinned` and both `--pr-body` paths (`--from`, and the in-memory one
after a `--check --orgs`) refuse a census row in one wording — "published but not pinned … needs `--pin`,
not a bump PR". A sweep written before this commit has no such key and can still only say "did not move";
that is why the writer is fixed in the same change.

`--self-test` 51 -> 56: the sweep runs with both flags and exits 1; the JSON holds both halves; a census row
is refused in those words; and a pin that *did* move still prints its body from the same file (so the fix
cannot have been "refuse everything"). `tests/workflow_shell_test.py` still passes.

This commit belongs to whichever of #1181/#1193 lands *second*: it is the only change that is about the two
of them meeting. Rebased onto current main either way the union tree is identical.
bong-water-water-bong added a commit that referenced this pull request Nov 6, 2026
… until someone pins it (#1193)

* pin-watch: census our own orgs, so a repo we publish is not invisible until someone pins it

--check iterates over pins, so a repo that was never pinned is watched by nobody: the template audit of
2026-11-05 had to be done by hand for exactly that reason. --orgs A,B adds one listing per org (full=true
carries the siblings) and reports every repo that ships a model artifact or a chat template and is not in
the table, under 'Published, not pinned', in the report's first line and as a run annotation.

Measured on the committed pins (19:3x): of 167 repos in the two orgs, 92 are not in models/pins.json —
89 of them hold only .1bp packs (the 1BP lane's format, not this table's business) and 2 are private (this
file is public, docs/model-pins.md), so exactly one is reported: bong-water-water-bong/ZR1-1.5B-Strix.

--pin now selects the files this table exists for (.gguf, .q4nx, chat_template.jinja,
tokenizer_config.json) rather than .gguf alone: a container or LoRA repo has no .gguf, so it could not be
pinned at all before this.

--self-test 45/45, including: a published repo that ships a template is reported and an .1bp-only repo is
not, a private repo stays out, an already-pinned repo is not reported twice, an org that cannot be listed
is a note rather than a clean day, and --pin on a container repo pins the model plus both template files.

* models/pins.json: pin bong-water-water-bong/ZR1-1.5B-Strix (the one published repo the census found)

6 files at 64f092fe: three chat_template.jinja (root and both checkpoints, all
b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1 — the same digest the audit measured and
the ZR1 GGUF arms carry) and three tokenizer_config.json that carry no chat_template key (459 B each, so
null is the honest value, not a missing read). Nothing else in the file moved.

* pin-watch: the sweep's JSON carries the census half, so a published-but-unpinned repo is not "did not move"

`--json` is what the workflow's PR step reads, and its docstring calls it "the sweep". #1181 added it when
a sweep was only pins; #1193 added `--orgs`, which finds repos by a different route (we publish them and
nobody pinned them). Applied together, `--check --orgs --json` wrote `date, pins, changes, notes`: the
census rows survived only in the Markdown report, and the artifact the PR step consumes described half of
what the run looked at — the F1/#1157 failure mode, an artifact whose prose is wider than its contents.

Measured before, on the union tree: a sweep with `1 of 148 moved, 1 published but not pinned` wrote a JSON
with `changes: [unsloth/Qwen3.8-Flash-Next-GGUF]` and no census key at all, and
`--pr-body our-org/unpinned --from sweep.json` answered "did not move" — the one word that would send a
reader looking at the wrong thing, and (with `|| true` removed by #1181) the shape of failure that opens an
empty-bodied PR.

Now: the JSON carries `published_not_pinned` and both `--pr-body` paths (`--from`, and the in-memory one
after a `--check --orgs`) refuse a census row in one wording — "published but not pinned … needs `--pin`,
not a bump PR". A sweep written before this commit has no such key and can still only say "did not move";
that is why the writer is fixed in the same change.

`--self-test` 51 -> 56: the sweep runs with both flags and exits 1; the JSON holds both halves; a census row
is refused in those words; and a pin that *did* move still prints its body from the same file (so the fix
cannot have been "refuse everything"). `tests/workflow_shell_test.py` still passes.

This commit belongs to whichever of #1181/#1193 lands *second*: it is the only change that is about the two
of them meeting. Rebased onto current main either way the union tree is identical.

---------

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant