Skip to content

fix(build): compile the five blocked test roots again (Refs #989) - #1210

Merged
dmitrii-f-t27 merged 3 commits into
mainfrom
spec/trinity-s12-unblock
Oct 1, 2026
Merged

dmitrii-f-t27 merged 3 commits into
mainfrom
spec/trinity-s12-unblock

Conversation

@dmitrii-f-t27

@dmitrii-f-t27 dmitrii-f-t27 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

S12 of #988, second part (Refs #989). #1207 put a judge in front of the test step and named the five test roots that did not compile as blocked. This change fixes all five and takes them off the list, so the test graph runs whole for the first time in months.

Before and after (Zig 0.15.2, Linux aarch64, empty caches)

main 3c33adb this branch
zig build test -Dci=true 124/135 steps; 5 failed; 2510/2510 tests passed 135/135 steps; 2760/2768 tests passed; 8 skipped
run steps that ran a test 61 66
zig build libvsa, zig build libqueen fail installing the C header build; both C examples compile against the header with -Wall -Werror and run

The eight skipped tests are the four tests in src/jit.zig that execute x86-64 machine code. They are compiled into both src/trinity.zig and src/e2e_test.zig, and they skip themselves on any other architecture.

The first x86_64 run

The first push failed on CI's x86_64 runners with 132/135 steps succeeded; 2 failed; 2760/2768 tests passed; 8 failed. That is the gate doing its job, and it found two things.

  • A real bug in src/jit.zig. finalize returned *const fn (*anyopaque, *anyopaque) void, which uses Zig's own calling convention, while the code it emits reads its arguments from rdi and rsi. The x86-64 backend that builds Debug does not pass them there. So "bind correctness" and "bundle correctness" read the wrong memory ("expected 1, found -1"). The dot product test, which casts to callconv(.c) itself, passed. The type is now JitBinaryFn with callconv(.c), as src/jit_arm64.zig already declares, and all six tests of the file pass when built for x86_64-linux and run on x86_64 (an amd64 container).
  • A misreport by the judge. zig prints each failed run step twice, above its error report and in the tree after the summary. The judge therefore counted 4 failed steps for 2 and missed that a step whose tests failed did run tests, which added a false PARTIAL_GRAPH. It also never printed zig's output, so the log could not say which tests failed. Now run steps are read from the tree after the summary, failed tests are named in the record and in TEST_FAILED, a failed command has its whole output printed, and each command's output is uploaded beside the record. The self-check carries verbatim zig 0.15 output for these cases; the previous parser fails it.

The second x86_64 run, with the JIT fixed, failed four tests of src/e2e_test.zig, and the judge now named them: three benchmarks that assert under 1 ms per 1024-trit operation, and the VERDICT that scores them.

per operation aarch64, Debug (LLVM) x86_64 CI, Debug (Zig's own backend)
cosine(1024) 11.6 µs 19.4 ms
permute(1024) 16.8 µs 3.1 ms
pack/unpack(1024) 10.2 µs 2.4 ms
bind(1024) 10.7 µs 47.7 µs

Zig 0.15 builds Debug for x86_64 with its own backend and for aarch64 with LLVM. The VSA kernels of zig-golden-float create and return a 71 KB HybridBigInt by value (zero(), permute()), and the self-hosted backend compiles that into code 185 to 1680 times slower, so on x86_64 the thresholds were measuring the backend. The e2e test compile now sets use_llvm. Under x86_64 emulation the root then passes 99/99 (cosine 11.4 µs, permute 12.2 µs, pack/unpack 21.8 µs, VERDICT 100/100), and aarch64 is unchanged. No threshold was raised and no test is skipped (.claude/rules/testing.md), and the other roots keep the default backend. Whether the VSA kernels should stop passing 71 KB values around is a question for zig-golden-float, not for this PR.

The five roots

root what was wrong fix
src/trinity.zig, the library's own test root the test module provides the dependency as hdc_vsa, while src/hybrid.zig and src/vsa.zig, which it re-exports, import zig-hdc-vsa; src/science.zig reads unpacked_cache as an optional both names on the module, as trinity_mod already does; the cache is indexed directly (every reader already calls ensureUnpacked() first)
src/vm.zig vm.zig:1150 reads the optional cache; vsa_jit.zig:117 passes an allocator to dotProduct ensureUnpacked(), then index. The old test silently skipped its check whenever the cache was null; now it always checks. No allocator.
src/queen_api.zig the C allocator without libc link_libc on its test module
src/c_api.zig a duplicated if (value > 0) { that kept the file from parsing. Behind it: src/vsa/gen_encoding.zig, which moved to zig-hdc; the optional cache; allocator arguments to dotProduct, bundle2 and bundle3; no libc vsa.encoding through the facade, the array API, link_libc on its test module (both of its libraries already call linkLibC())
src/e2e_test.zig imports packed_trit.zig, deleted in 42490a2 (#517); reaches src/trinity.zig without hdc_vsa or golden_float the unused import removed; both modules provided

Every API change follows HybridBigInt as the pinned zig-golden-float defines it (e7ce3288, src/ternary/hybrid.zig): unpacked_cache: [MAX_TRITS]Trit, dotProduct(a, b) i32, bundle2(a, b), bundle3(a, b, c). This is one change rather than five because the fixes share two causes, a module name and that API, and three of the roots needed both.

build.zig also pointed both C headers at libs/c/..., but they moved to src/libs/c/... on 2026-03-19 (bc75049). As a result zig build libvsa and zig build libqueen compiled their libraries and then failed. Both build now.

The gate

  • specs/reproduce/headless.t27: BLOCKED_ROOTS is empty. The floors rise to 66 run steps with tests and 2760 tests passed, and a new TESTS_SKIPPED_MAX = 8 caps skips. The header keeps the history of the five.
  • tools/reproduce.py: a new SKIPPED rule. The self-check no longer relies on the real list being non-empty. It plants a blocked root of its own, so the ledger stays tested in both directions: a named root that compiles fails the gate, and a named root that still fails is accepted. It now plants a defect for every rule of the judge (21 planted defects, up from 15) and fails if any rule was never seen to fire. As negative controls, a copy without the SKIPPED rule and a copy with a rule that has no plant were each reported BAD.

Found, not changed here

  • 23 of the 24 tests in src/jit_arm64.zig, and 4 of the 5 in src/jit_x86_64.zig, return early off their own architecture and count as passed there; error.SkipZigTest would report them as skipped. This is recorded in the spec. Converting them changes the skip count per architecture, so it is left for its own change.
  • trinity_vsa_encode_text hashes the text into one integer (HybridBigInt.fromI64, src/c_api.zig), which is not a semantic encoding: the C example's "similar" pair scores 0.05.
  • libvsa, libqueen and the C examples are not in the headless profile, so a regression there is still not gated.

Still open in #989

The pinned, byte-checked t27 lock and the capability -> spec -> artifact -> evidence index; the end-to-end task over CLI/MCP, tri-api policy, persistence and the Queen lifecycle; the catalog refresh and publication.

{
  "version": 1,
  "head_sha": "85de91bba047b8ecc58ca13bded27cd3c466e900",
  "summary": "The five test roots that did not compile on main compile and pass again, so the headless test graph runs whole (135/135 steps, 2760 tests passed, 8 skipped on aarch64) and the spec names no blocked root.",
  "changes": [
    "Fixed the five blocked roots: both dependency module names and golden_float on the test modules of src/trinity.zig and src/e2e_test.zig, libc on the queen_api and c_api test modules, the duplicated if in src/c_api.zig, and the removed packed_trit.zig import.",
    "Ported src/science.zig, src/vm.zig, src/vsa_jit.zig and src/c_api.zig to the HybridBigInt of the pinned zig-golden-float: the unpacked cache is an array, and dotProduct, bundle2 and bundle3 take no allocator; c_api reaches gen_encoding through the vsa.zig facade.",
    "Pointed build.zig at the C headers in src/libs/c, where they moved on 2026-03-19, so zig build libvsa and zig build libqueen build again.",
    "Gave the x86-64 JIT of src/jit.zig the C calling convention its generated code assumes (JitBinaryFn), which the first x86_64 run of the unblocked roots showed was missing.",
    "Built the e2e and benchmark root with LLVM (use_llvm), because Zig 0.15's own x86_64 Debug backend compiles zig-golden-float's by-value 71 KB HybridBigInt code 185 to 1680 times slower and its wall-clock thresholds measured the backend.",
    "Emptied BLOCKED_ROOTS in specs/reproduce/headless.t27, raised its floors, added TESTS_SKIPPED_MAX, and fixed tools/reproduce.py: run steps read once from the summary tree, failed tests named, a failed command's output printed and uploaded, a SKIPPED rule, and a self-check that plants a defect for every rule and parses verbatim zig output."
  ],
  "tests": [
    {
      "command": "python3 tools/reproduce.py --self-check",
      "status": "passed",
      "result": "21 planted defects are each reported under their own code, a conforming record and a blocked root that still fails stay silent, and all 20 rules of the judge were seen to fire; copies without the SKIPPED rule or with an unplanted rule fail.",
      "evidence": "tools/reproduce.py self_check, run locally on macOS Python 3 before the commit"
    },
    {
      "command": "python3 tools/reproduce.py headless --zig zig-0.15.2 in ubuntu:24.04 aarch64 with empty caches",
      "status": "passed",
      "result": "Build 94/94 steps with 46/46 installs; test 135/135 steps, 2760/2768 tests passed and 8 skipped, 66 run steps with tests and one empty, 67 compiled roots, no compile failure, both commands exit 0; no violation.",
      "evidence": "Linux aarch64 container, head 85de91bba047b8ecc58ca13bded27cd3c466e900, fresh zig caches, 3 min 9 s; the record and both command logs written by the tool"
    },
    {
      "command": "zig test src/jit.zig -target x86_64-linux-gnu, run in an amd64 container",
      "status": "passed",
      "result": "All six tests pass with callconv(.c); before the fix, bind correctness and bundle correctness failed exactly as on CI.",
      "evidence": "Run locally before the commit, Zig 0.15.2"
    },
    {
      "command": "zig build e2e -Dci=true with use_llvm, x86_64 zig 0.15.2 in an amd64 container",
      "status": "passed",
      "result": "99/99 tests including the four x86-64 JIT tests; cosine 11.4 us, permute 12.2 us, pack/unpack 21.8 us per operation; VERDICT 100/100.",
      "evidence": "Run locally under emulation before the commit; without use_llvm the same root failed the three benchmarks and the verdict, as on CI"
    },
    {
      "command": "zig build libvsa and libqueen, then zig cc -Wall -Werror on examples/basic.c and semantic_search.c against the installed header and static library",
      "status": "passed",
      "result": "Both libraries build with their headers, both examples compile without warnings and run to completion.",
      "evidence": "Linux aarch64 container with Zig 0.15.2, run locally before the commit"
    }
  ],
  "limitations": [
    "The x86_64 numbers are from emulation and from the failing CI run; this PR's own x86_64 job is the first native run with LLVM for the e2e root.",
    "Most tests in src/jit_arm64.zig and src/jit_x86_64.zig return early off their architecture and count as passed there; they are recorded, not converted to skips.",
    "The C libraries and their examples are not part of the headless profile, so a regression there is not yet gated."
  ],
  "tags": [
    "Engineering",
    "Verification",
    "CI"
  ],
  "blog": {
    "title": "Five test roots back in the graph, and what kept them out for months",
    "summary": "Once CI read the exit code of the test step, the five test roots it had hidden were fixed: two module names, libc, a duplicated if, a deleted import, and an old API.",
    "outline": [
      "What the judge named: five roots, among them the library's own test root, and the compiler's message for each.",
      "Two causes behind most of them: a dependency offered under the wrong name, and code written for an older HybridBigInt.",
      "What the whole graph shows once it runs: 2760 tests, eight JIT tests that only x86_64 runs, and passes that test nothing."
    ]
  }
}

🤖 Generated with Claude Code

S12 of #988, second part. #1207 named five test roots that did not compile and
whose tests had not run for months; this change fixes all five, so the test
graph runs whole: 135/135 steps, 2760 tests passed and 8 skipped on Linux
aarch64 (the four x86-64 JIT tests of src/jit.zig, in two roots), up from
124/135 and 2510.

- src/trinity.zig and src/e2e_test.zig: their test modules offered the
  dependency under one name while the files they reach import it under the
  other (zig-hdc-vsa / hdc_vsa), and e2e had no golden_float; e2e_test.zig
  imported packed_trit.zig, deleted in 42490a2 (#517).
- src/vm.zig, src/science.zig, src/vsa_jit.zig, src/c_api.zig: written for an
  older HybridBigInt, whose unpacked cache was an optional and whose
  dotProduct, bundle2 and bundle3 took an allocator; zig-golden-float's holds
  an array and takes none.
- src/c_api.zig: a duplicated `if` that kept the file from parsing, and an
  import of src/vsa/gen_encoding.zig, which moved to zig-hdc (now reached
  through the vsa.zig facade); the queen_api and c_api test modules link libc,
  as their libraries do.
- build.zig: the C headers moved to src/libs/ on 2026-03-19 (bc75049), so
  `zig build libvsa` and `zig build libqueen` failed installing the header;
  both build now, and the two C examples compile against the header with
  -Wall -Werror and run.

specs/reproduce/headless.t27 names no blocked root, raises the floors (66 run
steps with tests, 2760 tests passed) and caps skipped tests at 8.
tools/reproduce.py judges skipped tests, and its self-check no longer depends on
the list being non-empty: it plants a blocked root of its own, and fails if a
rule of the judge was never seen to fire.

Пять заблокированных корней тестов снова компилируются.

S12 эпика #988, вторая часть. #1207 назвал пять корней тестов, которые не
компилировались и чьи тесты месяцами не запускались; это изменение чинит все
пять, и граф тестов идёт целиком: 135/135 шагов, 2760 тестов прошли и 8
пропущены на Linux aarch64 (четыре x86-64 JIT-теста src/jit.zig в двух корнях),
было 124/135 и 2510.

- src/trinity.zig и src/e2e_test.zig: тестовые модули отдавали зависимость под
  одним именем, а файлы, до которых они доходят, импортируют её под другим
  (zig-hdc-vsa / hdc_vsa), у e2e не было golden_float; e2e_test.zig
  импортировал packed_trit.zig, удалённый в 42490a2 (#517).
- src/vm.zig, src/science.zig, src/vsa_jit.zig, src/c_api.zig: написаны под
  старый HybridBigInt, где распакованный кэш был optional, а dotProduct,
  bundle2 и bundle3 принимали аллокатор; у zig-golden-float кэш -- массив, и
  аллокатор не нужен.
- src/c_api.zig: продублированный `if`, из-за которого файл не парсился, и
  импорт src/vsa/gen_encoding.zig, переехавшего в zig-hdc (теперь через фасад
  vsa.zig); тестовые модули queen_api и c_api линкуют libc, как и их
  библиотеки.
- build.zig: C-заголовки переехали в src/libs/ 2026-03-19 (bc75049), поэтому
  `zig build libvsa` и `zig build libqueen` падали на установке заголовка;
  теперь обе собираются, а два C-примера компилируются с заголовком при
  -Wall -Werror и запускаются.

specs/reproduce/headless.t27 больше не называет заблокированных корней,
поднимает нижние границы (66 шагов с тестами, 2760 пройденных тестов) и
ограничивает пропущенные тесты восемью. tools/reproduce.py судит пропущенные
тесты, а его self-check больше не зависит от того, что список не пуст: он
подкладывает свой заблокированный корень и падает, если какое-то правило судьи
ни разу не сработало.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Oct 1, 2026
dmitrii-f-t27 and others added 2 commits October 1, 2026 12:37
…n (Refs #989)

The first x86_64 run of the roots that compile again failed eight tests, and the
judge reported them badly. Both fixed here.

src/jit.zig: `finalize` returned `*const fn (*anyopaque, *anyopaque) void`, Zig's
own calling convention, while the code it emits reads its two pointers from rdi
and rsi. Nothing promises the default convention passes them there, and the
x86-64 backend that builds Debug does not: "bind correctness" and "bundle
correctness" read the wrong memory ("expected 1, found -1"). The dot product
test, which casts to callconv(.c) itself, passed. The pointer type is now
JitBinaryFn with callconv(.c), as src/jit_arm64.zig already declares; all six
tests of the file pass when built for x86_64-linux and run on x86_64.

tools/reproduce.py: zig prints each failed run step twice, above its error
report and in the tree after the summary, so the judge counted four failed
steps for two and missed that a step whose tests failed ran tests, which added
a false PARTIAL_GRAPH. Run steps are now read from the tree after the summary
only; failed tests are named in the record and in TEST_FAILED; and a command
that fails has its whole output printed, as a wrapper that keeps a tool's
output must. The output of each command is written beside the record and
uploaded with it. The self-check carries verbatim zig 0.15 output for these
cases; the previous parser fails it.

Вызываем сгенерированный x86-64 код с соглашением вызова C.

Первый прогон на x86_64 корней, которые снова компилируются, уронил восемь
тестов, а судья плохо о них сообщил. Здесь исправлено и то, и другое.

src/jit.zig: `finalize` возвращал `*const fn (*anyopaque, *anyopaque) void`, то
есть собственное соглашение вызова Zig, а сгенерированный код читает два
указателя из rdi и rsi. Соглашение по умолчанию не обещает передавать их туда,
и x86-64 бэкенд, которым собирается Debug, не передаёт: «bind correctness» и
«bundle correctness» читали чужую память («expected 1, found -1»). Тест
скалярного произведения, который сам приводит указатель к callconv(.c),
проходил. Теперь тип указателя -- JitBinaryFn с callconv(.c), как уже объявлено
в src/jit_arm64.zig; все шесть тестов файла проходят в сборке для x86_64-linux
при запуске на x86_64.

tools/reproduce.py: zig печатает каждый упавший шаг дважды -- над отчётом об
ошибке и в дереве после сводки, поэтому судья насчитал четыре упавших шага вместо
двух и не засчитал шаг с упавшими тестами как запускавший тесты, отсюда ложный
PARTIAL_GRAPH. Теперь шаги читаются только из дерева после сводки, упавшие тесты
названы в записи и в TEST_FAILED, а у упавшей команды печатается весь вывод, как
и положено обёртке, которая его забирает. Вывод каждой команды пишется рядом с
записью и загружается вместе с ней. Self-check содержит дословный вывод zig 0.15
для этих случаев; прежний парсер его не проходит.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t (Refs #989)

With the JIT fixed, the second x86_64 run failed four tests of src/e2e_test.zig,
now named by the judge: three benchmarks that assert under 1 ms per 1024-trit
operation, and the VERDICT that scores them. On CI: cosine 19.4 ms, permute
3.1 ms, pack/unpack 2.4 ms per operation; natively on aarch64 the same Debug
code takes 11.6, 16.8 and 10.2 microseconds.

Zig 0.15 builds Debug for x86_64 with its own backend and for aarch64 with LLVM.
The VSA kernels of zig-golden-float make and return a 71 KB HybridBigInt by value
(zero(), permute()), and the self-hosted backend compiles that into code 185 to
1680 times slower, so on x86_64 the thresholds measured the backend. The e2e
test compile now sets use_llvm: under x86_64 emulation the root passes 99/99
(cosine 11.4 us, permute 12.2 us, pack/unpack 21.8 us, VERDICT 100/100), and on
aarch64 nothing changes. No threshold is raised and no test is skipped; the other
roots keep the default backend.

tools/reproduce.py: TEST_FAILED names the failed tests only. The text after
"failed:" is the first line the test wrote, which for a benchmark is its own
output (a cosine failure "said" bind(1024): 47710 ns/op), so the record keeps it
as first_line and the printed output carries the whole report.

Собираем корень e2e и бенчмарков через LLVM на любой платформе.

С исправленным JIT второй прогон на x86_64 уронил четыре теста src/e2e_test.zig,
теперь названные судьёй: три бенчмарка, требующие меньше 1 мс на операцию над
1024 тритами, и VERDICT, который их оценивает. На CI: cosine 19,4 мс, permute
3,1 мс, pack/unpack 2,4 мс на операцию; на aarch64 тот же Debug-код занимает
11,6, 16,8 и 10,2 микросекунды.

Zig 0.15 собирает Debug для x86_64 собственным бэкендом, а для aarch64 -- через
LLVM. VSA-ядра zig-golden-float создают и возвращают по значению HybridBigInt
размером 71 КБ (zero(), permute()), и собственный бэкенд компилирует это в код
в 185-1680 раз медленнее, так что на x86_64 пороги измеряли бэкенд. Теперь
компиляция e2e-тестов задаёт use_llvm: под эмуляцией x86_64 корень проходит
99/99 (cosine 11,4 мкс, permute 12,2 мкс, pack/unpack 21,8 мкс, VERDICT
100/100), на aarch64 ничего не меняется. Пороги не подняты, тесты не
пропущены; остальные корни собираются бэкендом по умолчанию.

tools/reproduce.py: TEST_FAILED называет только упавшие тесты. Текст после
"failed:" -- первая строка, которую написал тест, а у бенчмарка это его же вывод
(падение cosine «сообщало» bind(1024): 47710 ns/op), поэтому запись хранит её как
first_line, а полный отчёт есть в напечатанном выводе.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dmitrii-f-t27
dmitrii-f-t27 merged commit fd589c9 into main Oct 1, 2026
20 of 23 checks passed
@github-actions github-actions Bot added status:completed Done and removed status:in-progress 🔵 Agent working labels Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant