blog: 179 features that change no bits - #877
Open
gHashTag wants to merge 1 commit into
Open
Conversation
A parity report between the himbaechel xilinx port and nextpnr-xilinx 0.9.3 ranked its own work items backwards: the 179 missing BRAM features are zero-codepoints, and the blocker that kept a bitstream off a board produced no feature-count delta at all. Adds the post with receipts for #165, its two corrections, openXC7/nextpnr#1, prjxray-db#7, and the two open BUFIO items. Every measurement is attributed upstream; none was reproduced here, and that is stated in openQuestions. Co-Authored-By: Claude <noreply@anthropic.com>
Owner
Author
|
Status check, 2026-09-05. This PR has been open since 2026-08-31 with no activity — flagging it rather than letting it go quiet indefinitely. Both CI failures look like repo-wide gates, not content issues:
The PR body already defers the merge decision to the operator ("Not merging — publication is the operator's decision"). Restating that here so it's visible without needing to open the diff: this is ready for a merge/hold/close call whenever convenient, not something blocking on more work. |
gHashTag
added a commit
to gHashTag/trinity-fpga
that referenced
this pull request
Sep 5, 2026
…n a real disk crisis
Posted the PR #877 status-check comment per explicit operator go-ahead
("post it as-is") after drafting it on request:
gHashTag/trinity#877 (comment).
Investigating before drafting corrected a standing mischaracterization from
OD3/OD9: #877 is the operator's own PR (authored via an earlier Claude Code
session, in their own repo), not a third-party-adjacent one needing extra
publish caution. Also found why it looked stuck: "Brain Health Check" is a
repo-wide CI gate that ran full brain-region tests against a blog-post-only
diff touching no brain code, and its own failure-comment bot hit an
unrelated 403 permissions bug. The PR body had already deferred the merge
call to the operator from the start -- not neglect, a parked proposal.
Separately: this cycle opened with a real disk crisis (0.18 GiB, then 127
MiB free) -- the third this session. It resolved on its own (disk recovered
to ~20 GiB, /tmp state including the compiled binary was gone, consistent
with a reboot or cache-clear outside this loop's control) before any
action was needed. This gave B21's hysteresis its first real-world test:
the first post-crisis reading correctly held the verdict at HALT for one
more confirmation despite the raw reading already showing recovery, then
cleared on the second confirming reading -- exactly the designed behavior,
now proven against production data instead of only a scratch copy.
gHashTag
added a commit
to gHashTag/trinity-fpga
that referenced
this pull request
Sep 5, 2026
…sis (#746) * feat(loop): add STATE.json/JOURNAL.md/dashboard.html for the t27 correctness loop Structured backlog/done/anomalies tracking (STATE.json), narrative session log (JOURNAL.md), and a static status page (dashboard.html) for the ongoing autonomous work on the t27 Rust-to-Zig transpiler's corpus-correctness rate and the openXC7/nextpnr-xilinx BUFR clock-enable collaboration. Today's session: t27 corpus 74.2% -> 76.7% clean (497 specs), a silent infinite-loop bug found and fixed in the compiler's copy-propagation pass, all six optimizer passes swept to confirm no sibling instance survives. * feat(loop): give tri_loopstate a runnable CLI, fix dashboard drift, commit skill - src/tri/tri_loopstate_main.zig: standalone entry point for tri_loopstate.zig (zig build-exe, no root build.zig needed), exposing `status` and `check` subcommands. Proves a working Zig-0.16 Io-threaded pattern for file I/O and argv (pub fn main(init: std.process.Init) !u8) -- the first confirmed instance in this repo, verified by compiling and running against real files. - src/tri/tri_loopstate.zig: adds liveCounts/extractReadoutNumber/checkDrift plus 6 new tests (13/13 pass), so a dashboard's numeric readout can be verified against STATE.json mechanically instead of by hand. - src/tri/tri_loopstate.zig itself was still untracked despite being recorded as a "done" deliverable -- committing it now closes that gap. - .trinity/loop/{STATE.json,JOURNAL.md,dashboard.html}: recorded this iteration's work, added loop.continuity_protocol so the next cron firing reads state instead of re-deriving it, added awaiting_operator_decision tracking, and fixed the dashboard's stale/self-contradictory readout and "why the loop stopped" section (mislabeled as current three days after it resumed). - .claude/skills/fpga-bufr/SKILL.md: was untracked since 2026-09-01; commits the one skill artifact this loop has produced so far. * fix(loop): correct a stale cron job ID the loop believed was live (A21) STATE.json's loop.cron_job named 947e19e4, but CronList shows only one live job this session -- eaa1cb07, created by this iteration's own /loop invocation. 947e19e4 belonged to an earlier, now-terminated session: session-scoped cron jobs die when the session exits, so it could not have survived the restart loop.status itself already records. Corrected the field and dashboard masthead; JOURNAL.md keeps the original (now-stale) mentions in place per its append-only convention, with a correction note appended rather than an edit in place. * feat(loop): implement the three tripwires for "autonomous with tripwires" The operator picked cooperation option 2 (autonomous with tripwires) for the overnight loop. A 4-agent design workflow (3 independent proposals + synthesis) turned that into exact, implementable semantics; this commit builds them. New in src/tri/tri_loopstate.zig: - freeGiB(): a direct libc statvfs binding (no shell df), struct layout verified against the macOS SDK header and cross-checked live against df. - decisionGateStatus() (clear/some_gated/all_gated) plus a real behavior change to nextItem(): a needs_operator_decision row is now skipped silently, every time, so it never blocks unrelated actionable work. Only total gridlock (all_gated) is a real tripwire. - autoHealDrift()/rewriteReadoutNumber(): a plain numeric dashboard mismatch self-heals the same run; a MISSING label is left alone rather than guessed at. - renderHaltBanner()/injectHaltBanner(): marker-based, idempotent -- a clear verdict actively erases a stale banner. - evalDiskTier/readDiskThresholds/evaluateTripwires tie the three readings together. New `tripwire` subcommand in tri_loopstate_main.zig ties it all together and updates dashboard.html's halt banner. 30/30 tests pass. Verified end-to-end against the real files, not just synthetic fixtures -- including a deliberately forced halt (temporarily raised the disk threshold past current free space) to confirm the banner appears and clears for real. That real-file run caught an actual bug unit tests missed: the halt banner's own diagnostic text sits above the readout block and can echo a label's exact words, which extractReadoutNumber's whole-document search matched instead of the real cell, producing a false MISSING report that would have wedged the loop permanently halted the first time it ever legitimately halted for drift. Fixed by scoping the search to the readout block; added a regression test reproducing the exact shape. Deliberately not built, recorded rather than silently skipped: hysteresis/ flap-detection (backlog B21) and a GitHub escalation channel, which needs an operator-designated tracking issue that doesn't exist yet (awaiting_ operator_decision OD6). Today the dashboard banner and one JOURNAL.md line are the only two escalation channels. * chore(loop): iteration 77, halted (disk 1.47 GiB free < 2.00 threshold) The disk tripwire fired for real on its first live cron re-firing -- free space dropped from ~9.8 GiB to 1.47 GiB across the elapsed cycles, corroborated independently with df -h. Per design: no backlog work this iteration, no autonomous disk remediation attempted (operator-only call per B16). Halt recorded in loop.halt/loop.status; dashboard banner written automatically by `tripwire`. continuity_protocol's "don't re-run the research workflow on a repeated /loop prompt" rule held on its first real test. * chore(loop): iteration 78, still halted (disk 1.45 GiB free) Repeat check, same reason as iteration 77 -- no backlog work, no remediation attempted. Freshly re-verified, not carried over from the previous write. * chore(loop): iteration 79, still halted (disk 1.29 GiB, declining) Free space dropped further (1.47 -> 1.45 -> 1.29 GiB across three checks). Not re-sending the already-sent push notification (no-spam intent) but recorded an explicit notify_threshold_gib=1.0 so the next drop that actually matters re-notifies mechanically rather than needing a fresh judgment call each cycle. * chore(loop): record joint disk investigation (D60/A23), still halted Operator ran lsof/tmutil/du live during the halt -- ruled out deleted-open files and local snapshots, found this session's own scratchpad had grown to 3.1GB of stale FPGA probe artifacts (now cleared), and confirmed CoreSimulator's own directories are unchanged from the session's first reading (not an active real-time leak). Raw rm -rf on Caches/* mostly failed on permission-protected dyld files; handed off the correct tool (xcrun simctl runtime delete) instead. * chore(loop): iteration 82, still halted (disk 1.27 GiB), sent 1h reminder * chore(loop): iteration 83, RESUMED -- disk halt resolved (13.49 GiB free) Operator explicitly named the runtime to remove (iOS 26.5); xcrun simctl runtime delete <UUID> (note: needs the UUID from `runtime list`, not the identifier `list runtimes` reports) freed it asynchronously, 1.2 -> 13.49 GiB, confirmed with a fresh tripwire run. Total halt: iterations 77-83, ~2 hours, zero backlog work and zero autonomous remediation attempted during it, matching the design exactly. * feat(loop): B14/B16 delivered -- BUFRCLK0-3 round-trip confirmed on fbg484 Resumed from the disk halt into real backlog work. GitHub sweep found cavearr's BUFRCLK0/1/3 fuzzing campaign results on #149. The locally patched db already carried the exact same 4 minted rows (an independent cross-validation of an earlier pattern-based prediction). Built a synthetic FASM and ran fasm2frames against xc7a200tfbg484-2 (AX7203's actual part): exit 0, and verified the real bit pattern (frame 0x00421400/0x00421401, word 50, bits 23+31 both halves) rather than trusting the return code alone. Posted the confirmation to #149 with a recommendation against forcing the still-unverified #172 PERFCLK pip. B14 (round-trip deliverable) and B16 (disk decision) both closed. * docs(loop): add dashboard narrative for the disk-halt-to-B14-delivery arc * feat(loop): B17 delivered -- measure.py regression suite + git-history fix Added gen/zig/test_measure.py in t27 (11 stdlib-only tests, verified with a real fault injection). Writing the fixtures surfaced a real, previously undocumented scope boundary in silent()'s detector regex. Also found and fixed measure.py itself having zero git history -- a blanket gen/ ignore caught this hand-written "only sanctioned instrument" tool, same failure shape as this loop's own STATE.json being untracked for 76 iterations. * chore(loop): B20 delivered, B15 stale blocker fixed Extended the fpga-bufr skill's already-documented "watch PR state, not just issue comments" lesson (A16) to also poll reviewDecision and statusCheckRollup -- closes B20. Fixed a stale "disk" blocker on B15 (resolved hours ago); hansfbaier genuinely hasn't commented on #120, so it stays correctly blocked for the real reason. * chore(loop): iteration check -- no new GitHub activity, B18 count refreshed (53->23) * feat(loop): save the periodic self-audit as a named, reusable workflow The original /loop mandate asked for a "dynamic workflow per Claude Code best practices" -- delivered so far only as two ad hoc Workflow-tool invocations (research/decompose, tripwire design), never saved as a standing asset. Closes that gap: .claude/workflows/loop-periodic-self-audit.js is a real, invokable-by-name workflow (3 parallel finders -- infra health, code/data freshness, external-collaboration drift -- synthesized into concrete backlog items), scoped to find what's changed SINCE the last audit rather than repeating already-done work. First validation run launched to confirm it actually executes correctly, not just parses. * fix(loop): self-audit's first run found 4 real bugs, all fixed and tested .claude/workflows/loop-periodic-self-audit.js's first real invocation (about 20 minutes after being created) found: 1. loop.iteration frozen at 76 for 9+ real iterations -- a direct recurrence of anomaly A7 at ~10x the original drift. Corrected to 88 (highest number any commit actually names); continuity_protocol now mandates an explicit before-commit check. Filed OD7 for the real structural fix. 2. nextItem()/decisionGateStatus() had no check for the literal backlog status "blocked" (only blocked_by/needs_operator_decision) -- B8 carries status:"blocked" with an empty blocked_by, and the tool was live-recommending it as the next actionable item. Fixed both, 2 new regression tests. 3. liveCounts() treated any non-empty anomaly status as closed, silently undercounting anomalies_open (5 reported, 13 real) because A13/A15's status TEXT plainly says they're still unresolved. Replaced with anomalyIsOpen(): still-open phrases checked first (so A13's own coincidental "corrected" substring, describing a diagnosis correction not a resolution, doesn't get misread), then a resolved-verb whitelist, defaulting unmatched text to open. 5 new tests. 4. The audit workflow itself hardcoded this ephemeral worktree's path, contradicting its own "run from the repo root" instruction. Fixed to discover the live working tree at invocation time. 36/36 tests pass (was 30). Also fixed B6's stale blocked_by (named the now-completed B7 instead of the real blocker) and cross-posted the PERFCLK recommendation to #172 for hansfbaier specifically. Filed OD8 (not fixed blind): t27's branch has diverged from origin/master with unmerged edits to the same compiler files this loop patches -- a rebase decision, not something to attempt unsupervised. * fix(tri): migrate simple_synth_report.zig to Zig 0.16's Io-threaded main() First real production file migrated using the pattern proven in tri_loopstate_main.zig (B10's own survey groundwork): pub fn main(init: std.process.Init), argv via init.minimal.args.toSlice(), file I/O via std.Io.Dir.cwd()+init.io. Also fixed two pre-existing bugs the migration surfaced: bare `return;` statements in a `!u8`-returning function (masked until the argsAlloc error was fixed first), and a genuine memory leak (parsed.deinit() was never called -- silent under the old page_allocator, surfaced immediately once init.gpa's debug-tracked allocator was used). Verified by running, not just compiling: --help, a missing-file error path, and a real JSON parse all produce correct output with zero leaks. * chore(loop): iteration 89, B10 sized precisely and one file migrated 137 files use the old Zig 0.16 APIs; only 12 have their own main() and are independently migratable without touching main.zig's own unmigrated Io-threading (the real structural blocker, not the file count). Migrated and verified simple_synth_report.zig as proof (see ff60eb1); 9 more of the 12 remain for a future iteration. * chore(loop): iteration 90 -- cavearr confirms round-trip, prjxray-db PR being prepared * fix(tri): migrate wave9_generator.zig to Zig 0.16's Io-threaded main() Second file in B10's "quick wins" batch. Beyond the already-known args/fs API changes, this one surfaced two more from the same family, not previously catalogued: - std.ArrayListUnmanaged(T){} no longer default-initializes (needs `.empty` explicitly) -- same trap already fixed once in tri_loopstate.zig this session, now confirmed to recur across the codebase. - ArrayList(u8).writer(allocator) no longer exists; use .print(allocator, fmt, args) directly on the list. - std.heap.GeneralPurposeAllocator -> std.heap.DebugAllocator (matches A1's own documented rename, confirmed here in test blocks that `zig build-exe` silently never compiles -- only `zig test` catches it). Verified by running, not just compiling: --help, a real 3-worker docker-compose generation into a fresh nested directory, a second run into the same directory (exercises the PathAlreadyExists branch), and the too-many-workers error path. Both pre-existing unit tests (generateWorker, generateCompose) still pass unmodified. * chore(loop): iteration 91, B10 second quick win + subprocess-migration sizing * fix(tri): migrate sacred_synth_report.zig to Zig 0.16, fix a use-after-free Third file in B10's quick-wins batch. Migration itself is the now-familiar pattern: pub fn main(init: std.process.Init), argsAlloc -> args.toSlice, std.fs.cwd()+stat+readAll -> Dir.readFileAlloc, io threaded through runSacredSynthReportCommand into parseYosysJson. Running the success path (never previously exercised -- the repo has no fpga/openxc7-synth/sacred_alu.json, so this code path may never have run against real data before) surfaced a genuine, pre-existing use-after-free: countCellTypes() stored entry.key_ptr.* (a slice into whatever allocator parsed the JSON) directly into the returned stats.module_name, while parseYosysJson's arena backing that JSON gets destroyed the instant it returns. Printing the module name after that is a dangling-pointer read -- segfaulted immediately on real data. Notably countCellTypes already took an `allocator` parameter that was unused (`_ = allocator;`), suggesting the fix (dupe the string into an allocator that outlives the arena) was the original intent, just never implemented. Fixed by having countCellTypes dupe the name into its allocator parameter, and having parseYosysJson pass gpa (not the doomed arena allocator) at that one call site. Updated the existing test to free the now-owned string and added an assertion on module_name itself, which the test previously never checked -- exactly the gap that let this ship unnoticed. Verified by running all three output formats (human/csv/json) against a real parsed JSON, plus --help and the missing-file error path. * chore(loop): iteration 92, B10 third quick win found and fixed a real segfault * chore(loop): iteration 93, corrected B10 sizing -- zero of 7 remaining files are quick wins * fix(tri): migrate testnet_rewards.zig to Zig 0.16, first Io.Clock file Fourth file in B10's batch, and the first to need the newly-investigated Io.Clock/Timestamp system (std.time.timestamp() is gone entirely, not renamed). Verified the replacement empirically before using it: a throwaway program calling std.Io.Clock.real.now(io).toSeconds() matched `date +%s` exactly. Threaded `io: std.Io` through every function/method that needed a timestamp -- RewardEntry.isVested/canClaim/timeUntilVesting, NodeRewardEntry.init/updateUptime/addJob/isHealthy, RewardsManager.init/registerNode/recordNodeActivity/addBugBounty/ getStats/exportToJson -- and through all 15 affected unit tests, using std.testing.io (the stdlib's own ready-made Io for test contexts) rather than hand-rolling one. Centralized the repeated `@intCast(...toSeconds())` pattern into one nowSeconds(io) helper. Also fixed two already-known traps encountered along the way: ArrayListUnmanaged(T) inferred-`.{}` field values need `.empty` (same issue as the explicit `T{}` form, already fixed once in tri_loopstate.zig and wave9_generator.zig this session) and ArrayList.writer(allocator) -> .print(allocator, fmt, args). Verified by running: zig test (28/28, all 15 io-threaded tests among them), then the actual binary against all four subcommands (leaderboard, register, stats, claim) plus the no-args usage/error path. * chore(loop): iteration 94, B10 fourth file + Clock/Timestamp pattern proven * chore(loop): iteration 95, halted (disk 0.23 GiB free, critical) Disk crashed much harder than the first halt episode -- 0.23 GiB at detection (fluctuating 0.2-0.8 GiB since), well below the prior crisis's 1.2-1.5 GiB low point and close to the zero-crash zone that previously took down the harness entirely. No backlog work this iteration, no autonomous remediation attempted, matching the design exactly. * chore(loop): iteration 96, RESUMED -- disk halt resolved without sacrificing a runtime Second disk crisis (0.23 GiB, worse than the first episode's 1.2 GiB low point). Found and deleted an already-unusable duplicate iOS 26.5 disk image (xcrun simctl runtime list flagged it explicitly) -- zero functional loss, freed 0.2 -> 6.0 GiB on its own, no real runtime sacrificed this time. * fix(tri): migrate testnet_faucet.zig to Zig 0.16, first real socket file Fifth file in B10's batch, and the first to need real networking -- the last of the three unexplored API surfaces this migration turned up (subprocess spawn, Io.Clock, and now raw sockets). Researched std.Io.net before touching the file: std.posix.socket/bind/ listen are all gone, replaced by a genuinely higher-level API -- IpAddress.listen(&addr, io, .{.reuse_address=true}) does what used to take socket()+setsockopt()+bind()+listen() in one call, returning a Server. Verified the whole client-server round trip empirically in a throwaway program (spawn a server thread, connect a real client, confirm the byte stream) before touching real code -- same discipline as every other API claim this session. The migration itself turned out smaller than feared: runFaucetServer() never actually called accept() (a stub that binds+listens then sleeps forever), so there was no read/write loop to port, just the listen call itself. Also fixed std.Thread.sleep (gone, replaced by std.Io.sleep(io, duration, clock)) and threaded the already-proven Clock/Timestamp pattern through 6 more functions and all 15 tests. Verified by running: zig test (28/28), the CLI drip/status commands, and the actual server command -- confirmed genuinely listening via `lsof -iTCP:PORT -sTCP:LISTEN`, not just "the process didn't crash". Found and fixed a third instance of the same pre-existing-leak-masked-by-page_ allocator pattern from earlier files this session: runFaucetCli() never freed response.tx_hash on the success path. * chore(loop): iteration 98, B10 5th file (sockets proven) + B18 reviewed not guessed + B19 filed OD9 testnet_faucet.zig migrated to Zig 0.16 Io-threading (first socket-using file, std.Io.net.IpAddress.listen/connect verified via lsof against a real bound port; also found std.Thread.sleep is gone, replaced with std.Io.sleep). Third leak found+fixed in this migration series (tx_hash never freed on success path). B18: read the actual 17-file diff instead of re-counting -- it's one coherent in-progress body of work matching origin/master's already-merged PRs #3017-3027, not scattered litter. Untracked 6 pure-noise Coq build byproducts (t27 #3188, 8d980d937); left the rest for the operator (updated blocked_by with the real reconciliation question) instead of guessing whether to commit or discard. B19: PR #877 escalation not acted on -- filed as OD9, needs an explicit go-ahead distinct from the openXC7 issue-comment channel's standing authorization. * fix(tri): migrate testnet_explorer.zig to Zig 0.16, B10 sixth file, clean pass Sockets: same std.Io.net.Server pattern proven in testnet_faucet.zig (D72), applied cleanly -- deinit(io)/start(io), std.Thread.sleep -> std.Io.sleep. Also fixed three ArrayList(u8).writer(allocator).print(...) call sites in handleGetNodes() that zig test could not see (main() is dead code under test) but zig build-exe caught immediately. Verified: zig test 24/24, zig build-exe clean, ran every subcommand against the real binary including confirming the server subcommand is genuinely listening via lsof. No leaks found this time. Also records this cycle's openXC7 collaboration check: prjxray-db#13 and prjxray#14 are up, cross-referenced, addressed to hansfbaier for review -- read in full, nothing to correct, no action needed on this end. * fix(tri): migrate cyrillic_guard.zig to Zig 0.16, B10 seventh file + two real bugs Subprocess: std.process.run(gpa,io,options) -> {term,stdout,stderr} replaces Child.run() directly, verified with a throwaway program before touching real code. This closes out the last of four Zig 0.16 API families this migration needed (file I/O, Clock/Timestamp, sockets, subprocess). Also migrated std.fs.cwd() usages (Dir.cwd().readFileAlloc/openDir/statFile/walk, all now take an io param) and main() to the Init-based signature. Running the built binary against real directory arguments (main() is dead code under zig test, so only build-exe + actually running it catches this) found two genuine pre-existing bugs: - checkPath/walkDirectory each returned a same-shaped but distinct anonymous struct, so the directory-redirect branch never type-checked under build-exe. Fixed with a shared CheckPathResult type. - statFile does not throw error.IsDir for a directory in this stdlib, so pointing the tool at a directory silently fell through to the single-file path and reported a false "0 files checked, no Cyrillic found". Fixed by checking stat.kind == .directory explicitly. Fixing the second exposed a third: walkDirectory read entry.path (relative to the walked directory) through checkFile's cwd-relative open, correct only when scanning "." -- fixed by joining path + entry.path before reading. Verified: zig test 3/3, zig build-exe clean, ran the real binary against all three code paths (no staged files, a file with real Cyrillic literals, a directory with one clean and one Cyrillic file). * fix(tri): migrate sacred_bench.zig to Zig 0.16, B10 complete, five real bugs found Subprocess: std.process.run(gpa,io,options) replaces three Child.exec() call sites, using the same pattern proven in cyrillic_guard.zig. This closes B10 -- all four Zig 0.16 API families it needed (file I/O, Clock/Timestamp, sockets, subprocess) now have proven patterns across 8 independently-verified files. Running the actual binary end-to-end (main() and both print functions had zero prior test coverage) found five pre-existing defects, none of them migration mechanics: - Two print_row closures used C printf-style specifiers (%-11s, %8.2f) with mismatched arg counts against Zig's {s}/{d} placeholders -- a guaranteed compile failure in any Zig version. Rewrote as valid Zig format strings. - Both closures accessed fields on a still-optional BenchmarkResult after an `if (result != null)` check, which doesn't narrow the type. Fixed with `if (result) |r|`. - Two call sites declared `const results` then called the mutating `.deinit()` -- fixed to `var`. - The test's CSV fixture had an uncommented header row that the `#`-only comment-skip logic tried to parse as data, crashing parseFloat at runtime. - Two real CLI-invocation bugs: the installed iverilog 13.0 doesn't recognize --version (needs -V) or +define+NAME=VAL (needs -D) -- verified against the real installed binary. Added a print-functions test (previously zero coverage). Verified end-to-end with real iverilog/vvp: the fixed binary reaches iverilog, which now runs and fails on a genuine RTL-completeness gap (three Verilog modules referenced but not compiled) -- captured and reported correctly, proving the subprocess path works. That RTL gap is a separate FPGA-domain issue, out of scope here. Also fixed sacred_commands.zig, an orphaned two-line wrapper (nothing calls it, no build.zig resolves its @import targets) whose calls were stale against both wrapped functions' current signatures and missing `try` on an !void return -- itself a pre-existing compile error, fixed for future consistency. B10 backlog item marked completed; main.zig's own migration is split out as a new tracked item (B22) since it's a different scale/risk profile than any file done under this survey. * chore(loop): iteration 102, B22 sized -- one real blocker found, filed as OD10 Surveyed main.zig's local-import graph (BFS, not repeated zig build-exe) to size the migration properly instead of diving in blind. 249 of 250 reachable files resolve cleanly; one broken import (tri_farm.zig -> deleted local_farm.zig, 5 dangling call sites) blocks the whole graph from compiling. Traced to 36f3863 (HSLM-training extraction), which deleted the file but never updated its caller. The deleted code (a Docker-based Wave-9 worker-farm manager) is fully recoverable from git history, but whether to restore it or remove the dead call sites is a product decision tied to that refactor's intent, not something the code itself answers. Filed as OD10 rather than guessing; B22 marked needs_operator_decision with blocked_by naming the reason. * feat(tri): B21 -- disk-halt hysteresis and flap detection in tri_loopstate tripwire was a stateless single-reading snapshot: a free-space bounce right at a threshold flapped the verdict every cycle even when disk pressure hadn't really changed. Two new pure state machines fix that: - applyDiskHysteresis: a raw halt reading always wins immediately, but recovering out of halt needs N consecutive non-halt readings (default 2, tunable via loop.tripwires.disk_recovery_confirmations_needed). A dip back to halt before confirmation resets the streak. - detectFlap/updateHaltEpisodes: tracks halt-episode start iterations in a rolling window (iteration count, not wall-clock time -- keeps this dependency-free); 3+ episodes in the window (tunable) surfaces a flap warning even while the current reading is clear. Old episodes are pruned once they age out of the window. New writeTripwireHysteresis persists both to loop.tripwires on every tripwire run -- this is the first time the tool writes STATE.json back instead of only reading it. 9 new tests (49 total). Found and fixed a real leak while writing the round-trip test: mutating the parsed JSON tree with the caller's allocator instead of the document's own arena leaked silently; fixed by using st.doc.arena.allocator() for tree mutations. Verified against real STATE.json in a scratch copy before touching the live file: forced halt -> held at halt through 1/2 confirmations -> cleared at 2/2; seeded a 3-episode flap and confirmed the warning fires while verdict stays RUNNING (flap is informational, not a hard tripwire, per spec). Confirmed episode pruning works. Deliberately deferred: auto-appending a formal anomalies[] row on flap detection (ID numbering, dedup, and narrative authoring are a distinct, larger task) -- flap surfaces as a printed warning + persisted history for now, documented as a bounded follow-up rather than left ambiguous. Also fixed a stale loop.tripwires._note that claimed hysteresis was unimplemented, and discovered (by running the rebuilt binary against production data for the first time) that the backlog is now fully decision-gated -- B18/B19/B22 all need an explicit operator call, B6 is externally blocked. Recorded plainly rather than manufactured busywork. * chore(loop): iteration 104, B19/OD9 resolved, B21 hysteresis proven on a real disk crisis Posted the PR #877 status-check comment per explicit operator go-ahead ("post it as-is") after drafting it on request: gHashTag/trinity#877 (comment). Investigating before drafting corrected a standing mischaracterization from OD3/OD9: #877 is the operator's own PR (authored via an earlier Claude Code session, in their own repo), not a third-party-adjacent one needing extra publish caution. Also found why it looked stuck: "Brain Health Check" is a repo-wide CI gate that ran full brain-region tests against a blog-post-only diff touching no brain code, and its own failure-comment bot hit an unrelated 403 permissions bug. The PR body had already deferred the merge call to the operator from the start -- not neglect, a parked proposal. Separately: this cycle opened with a real disk crisis (0.18 GiB, then 127 MiB free) -- the third this session. It resolved on its own (disk recovered to ~20 GiB, /tmp state including the compiled binary was gone, consistent with a reboot or cache-clear outside this loop's control) before any action was needed. This gave B21's hysteresis its first real-world test: the first post-crisis reading correctly held the verdict at HALT for one more confirmation despite the raw reading already showing recovery, then cleared on the second confirming reading -- exactly the designed behavior, now proven against production data instead of only a scratch copy. * build: declare tri-loopstate, skipped under -Dci The reachability ratchet reported 396 against a baseline of 395. The orphan was src/tri/tri_loopstate_main.zig -- an entry point that imports tri_loopstate.zig but which nothing imports in turn, so it is invisible to both the @import walk and the b.path() seed list. Its own header says "there is no build.zig anywhere in this repo that targets it". That was true when it was written and is not any more, and the same header asks for exactly this: "wire this in". Guarded behind !ci_mode, following the pattern already used for the GUI and raylib targets. The reason differs -- those need system libraries, this needs a newer compiler: the file uses std.process.Init, which is Zig 0.16, and CI still runs 0.15.2. Declaring it unconditionally fails the whole build there. The b.path() stays literal inside the guard, so the ratchet still sees the entry point: it reads build.zig as text rather than running it. Verified: `zig build loopstate` succeeds on 0.16, and `-Dci=true` lists no loopstate step. * ci: guard the last 0.16-only target, and give the migrated files a real gate "Validate VIBEE Codegen" was still red on this branch after the previous fix, on a different file: src/tri/sacred_synth_report.zig:278:30: error: root source file struct 'process' has no member named 'Init' The earlier commit guarded tri-loopstate because the compiler named it. The compiler names the first failure, not the class. Enumerating properly: ten files on this branch now carry `main(init: std.process.Init)` -- the whole B10 migration campaign -- and exactly one of them, tri-sacred-synth-report, is an installed executable that CI builds. The other nine are modules or unreferenced entry points, which Zig never compiles. The underlying fact is worth stating plainly: NEITHER toolchain builds this tree. 0.15.2 fails on the migrated files; 0.16.0 fails on ~20 targets still written against 0.15 APIs (measured, not assumed -- `zig build -Dci=true` on 0.16 locally). So bumping CI is not the fix, and won't be until the migration finishes. That makes the guard necessary, and on its own it would be a quiet loss: both guarded executables would have no automated check at all, verified once by hand by whoever migrated them and never again. A migration campaign that removes its own files from CI as it proceeds is the shape of problem that only surfaces much later. So the guard ships with its replacement. .github/workflows/zig-0-16-migrated.yml fetches a real 0.16 toolchain and compiles every file carrying that signature. The list is derived by grep, never hand-maintained: the next file someone migrates is covered the moment it lands. A derived list can silently match nothing and pass green, so an empty result is a hard failure there, not a pass. Verified before pushing, both directions: - all 10 migrated files compile under 0.16 (10 ok / 0 fail) - the step body runs verbatim under local bash (exit 0) - the same body in a tree with no matches (exit 1, guard fires) - `zig build -Dci=true --list-steps` no longer lists sacred-synth-report - `zig build --list-steps` still does - the b.path() literal stays inside the guard, so the reachability ratchet, which reads build.zig as text, still sees the entry point while-read rather than mapfile in the workflow: the only bash on this machine is 3.2, which has no mapfile, and a construct that cannot be run before pushing is how CI-only breakage happens. * ci: pin the 0.16 job's target, and handle the one file that needs libc The job I added one commit ago failed on its first run, on src/trinet/main.zig: std/c.zig:10647:12: error: dependency on libc must be explicitly specified pub extern "c" fn open(path: [*:0]const u8, oflag: O, ...) c_int; referenced by: open: src/trinet/serial.zig:33:21 I had verified all ten files locally and they passed. The verification was real and still missed this: macOS links libc implicitly, Linux requires -lc, and I checked on a macOS laptop against a Linux runner. "Verified locally" was true and not worth much, because the thing that differed was the platform. Two changes, both aimed at that gap rather than at the symptom: -target is now pinned to x86_64-linux and named in an env var next to the command that reproduces it. On the runner this is a no-op; off the runner it is the entire point -- `zig build-exe <file> -target x86_64-linux` now fails on a laptop exactly the way CI does. Confirmed by reproducing the original error locally that way before writing the fix. Files needing libc are retried with -lc and reported as "ok (needs -lc)" rather than -lc being passed to everything. Which files pull in libc is a fact about those files; linking it into the nine that don't would quietly weaken what this job proves in order to make one line shorter. Re-verified by extracting the step body from the YAML and running it verbatim under the pinned target: 10 ok / 0 fail, with src/trinet/main.zig correctly labelled as the one needing -lc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds one blog post:
features-that-change-no-bits, dated 2026-08-31.What it reports
Material from the last 24 hours in
openXC7/nextpnr-xilinx#165, plus the mergedchanges it depends on:
as "the most concrete bitstream-parity item and the one I would fix first".
proven three independent ways (segbits, a
bit2fasmround trip givingbyte-identical 5 576 340 B frames in both arms, and a Vivado ML 2026.1 golden).
produced no feature-count delta and failed in two different-looking ways
depending on the prjxray-db revision.
being the one carrying
openXC7/nextpnr#1.Honesty notes
cavearr's in fix(state): add missing child.deinit() in tri_state.zig runProcessInherit #165; the board run is jrrk2's on his own Sonata. This is stated
in the body and again as the first
openQuestionsentry.fix(state): add missing child.deinit() in tri_state.zig runProcessInherit #165 OPEN, refactor(faculty): extract hardcoded paths to constants in faculty_board.zig #168 OPEN, feat(cloud): add API key validation before spawning agent container #149 OPEN,
openXC7/nextpnr#1MERGED 2026-08-29,openXC7/prjxray-db#7MERGED 2026-08-26.quoteblocks rather thanparaphrased into something stronger — including "to our knowledge" on the
first-board-verified claim.
comparable between trees, the BRAM address cascade unverified on silicon, and
the regional clock path routed around rather than fixed.
ruis complete: title, summary, openQuestions and body.Build
npm run build:ciexits 0; the slug appears indist/assets/Blog-D9NOt413.jsand gets its own chunk
features-that-change-no-bits-DCKQ1Tl1.js.Not merging — publication is the operator's decision.
🤖 Generated with Claude Code