Repository navigation
ci-pipeline.yml Nickel job globs *.k9.ncl and generated-input .ncl: 20 of 29 template files can never pass #982
Description
Summary
ci-pipeline.yml's Nickel job selects files with
mapfile -d '' FILES < <(git ls-files -z -- '*.ncl')
... nickel format --check "$f" ...
... nickel typecheck "$f" ...That glob is wrong in two independent ways. Measured against rsr-template-repo
@ HEAD, 20 of its 29 tracked *.ncl files cannot pass this gate no matter
what the repository does.
| bucket | count | can pass? |
|---|---|---|
*.k9.ncl — k9 contract files |
18 | never — not Nickel source |
*.ncl importing generated JSON |
2 | never standalone |
genuine self-contained *.ncl |
9 | yes |
Defect A — *.ncl matches *.k9.ncl, which is not Nickel
k9 contract files share the .ncl suffix but are a different format. They
open with a K9! sentinel on line 1:
$ head -1 .machine_readable/arrival-pack/claude-md.k9.ncl
K9!
$ nickel typecheck claude-md.k9.ncl
error: unexpected token
┌─ claude-md.k9.ncl:1:3
│
1 │ K9!
│ ^
Sampled 12 of the 18 *.k9.ncl in the template: 12/12 begin with K9!.
Both nickel typecheck and nickel format --check fail on every one, forever.
The estate already validates these separately — cicd-squabbler runs a
Validate K9 contracts check, which passed on the same commit where this gate
failed.
Defect B — typechecking projections over generated inputs
arrival-pack.ncl and coaptation/coapt.ncl import JSON that is a build
artifact, correctly uncommitted. The template ships the generator, and
generate.sh lines 16–17 make the order explicit:
bash "$AP/extract.sh" .../descriptiles > "$AP/claude-md-data.json"
nickel export --format raw "$AP/arrival-pack.ncl" > "$AP/.region.tmp"
Typechecking them standalone yields
import of claude-md-data.json failed: could not find import. The files are
correct by design; the gate's assumption that every .ncl is self-contained is
what is wrong.
Evidence this is not hypothetical
The 5-repo dry-run wave (#976) produced exactly one subject whose pipeline
actually ran: cicd-squabbler (run 35726516904, job pipeline / Nickel,
runner 1002435839). It went red — and every failing file was one of these
two buckets, none of its own source:
arrival-pack/arrival-pack.ncl Bad format + typecheck (generated import)
arrival-pack/claude-md.k9.ncl Bad format + typecheck (K9! sentinel)
coaptation/coapt.ncl Bad format + typecheck (generated import)
coaptation/coapt.k9.ncl Bad format + typecheck (K9! sentinel)
So the one repo that reached the gate was failed entirely by the gate's own
blind spot. Per this repo's own doctrine a gate that fails for a reason the
repository cannot fix misleads exactly as much as one that passes vacuously.
⚠ This is why .machine_readable/pipeline-allow.txt has not been seeded from
that wave: the only earned red is not the repository's debt.
Separately: a real template bug, not this gate's fault
nickel format --check fails on the template's own current revision of
arrival-pack.ncl (reproduced locally, nickel 1.17.0, rc=1). The 9 genuine
.ncl files should be formatted and kept that way. That belongs to
rsr-template-repo; it is not a reason to keep the glob wrong.
Acceptance criteria
- The Nickel job excludes
*.k9.nclfrom bothformat --checkand
typecheck(they have their own validator). - Generator-backed
.nclare either generated first or excluded from
typecheckby a declared, in-repo mechanism — never silently skipped. -
format --checkstill runs over every genuine.ncl; the fix narrows the
file set, it does not weaken the check. - The job prints its denominator (
files checked: N) and refuses when
the surviving set is empty, so narrowing cannot create a vacuous pass. - Re-run against
cicd-squabbler: any remaining red is genuinely its own.
Activity
hyperpolymath commentedon Sep 22, 2026
Fixed in #985 (open, not merged).
Against the acceptance criteria
*.k9.nclexcluded fromformat --checkandtypecheck'*.ncl' ':!*.k9.ncl'.nclhandled honestlyimporttarget is untracked in git; each skip named with its reason via::noticeand in the step summaryformat --checkstill covers every genuine.nclAlso cures the
detect:132half:N_NICKELcounted the same inflated glob, sohas_nickelwent true on repos holding no Nickel at all.detectand the job nowshare one pathspec — a guard that counts a different set than its consumer
checks is exactly how this defect arose, so the cure is that they cannot diverge.
Verified by killing mutants, not by observing greens
Run with the workflow's own pinned nickel 1.18.0, with the step body extracted
from the YAML and diffed byte-identical to the script under test:
has_nickel=falseOne defect found by that suite and fixed before pushing: the skip predicate
matched an
importinside a#comment (_base.ncldocuments its ownusage), which would have silently skipped a file that typechecks cleanly.
Comments are now stripped before scanning. Caught only because the expected skip
count was 2 and the run printed 3.
Corrected measurement
All 11 genuine
.nclincicd-squabblerfailformat --check, not the 4 theCI log happened to name. So #985 alone does not turn that repo green — the
formatting is a separate, real defect, fixed in hyperpolymath/cicd-squabbler#106,
with the same fix at its source in hyperpolymath/rsr-template-repo#195.
🤖 Generated with Claude Code