Repository navigation
tri: stranded-work finder and cron tick resume card - #5787
Conversation
tri stranded lists remote branches with commits off the default branch, tips older than --hours, and no open same-repo PR. It skips dependabot/*, patch-equivalent branches (git cherry) and squash-merged ones (merge-tree result equals the default's tree), reports merge clean/conflict and whether the branch ever had a PR, and runs merge-tree against a throwaway object directory so the clone is never written. With no PR state nothing is called stranded: the rows are "undecided" and the exit is 2. tri tick is the resume card for a cron loop: claim and its age, the last ledger section, each worktree's branch, dirty count, ahead/behind vs its base and last commit age, and anomalies with a one-line fix each. git runs with GIT_OPTIONAL_LOCKS=0; nothing is written. Both self-tests run in loop-tools-gate, each with a negative control, and each was seen to fail on a mutated copy of its tool. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Catch-up for the squash merge of #5787 (carries #5824, #5864, #5868, #5896). Conflicts: .github/workflows/loop-tools-gate.yml path filters -- both sides added test files, kept all four; tools/census/shell.txt -- master's side, then `tri census pin --bless`. Census vs master after the bless: fetches.txt bounded reads in scripts/tri_loop/*.py 4 -> 5 (pr_state.py reads the GitHub API, bounded); shell.txt run: steps 285 -> 288 and runner-without-container 264 -> 267 (the stranded, tick and pr-state steps in loop-tools-gate.yml). Gate: PASS. Local on the merge: loop-tools-tracked.sh, test_stranded_work_is_found_ without_writing, test_a_tick_resumes_from_what_it_reads, test_an_open_pr_is_ not_a_green_one, test_damage_repair_snapshot_required, test_tri_says_it_ could_not_run all pass; every scripts/tri_loop/*.py compiles. test_a_page_is_measured_at_both_widths hit a 600 s local cap; harness.py and that test are byte-identical to master, where #5828's CI ran them. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR DashboardGenerated at: 2026-10-04 09:29:56 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
tools/census/shell.txt conflicted only in its counts; settled by taking master's file and re-running `tri census pin --bless`. Against master the pins move by this branch's own additions, as in 9b5f1e7: - fetches.txt: bounded reads 4 -> 5 (scripts/tri_loop/pr_state.py asks the PR API once per PR, bounded); - shell.txt: run steps 278 -> 281 and runner steps 257 -> 260 (the three test steps loop-tools-gate.yml adds). Master's base is 7 steps lower than at 9b5f1e7 because #5951 deleted the NOW gate's steps. `tri census pin --gate`: PASS. The four docs/now entries stay: #5951 stopped requiring entries, it did not remove the 1291 already there. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR DashboardGenerated at: 2026-10-04 09:57:20 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
tools/census/shell.txt conflicted only in its counts again; settled by taking master's file and re-running `tri census pin --bless`. Against master the pins move by this branch's own additions, as in 859d4f4: - fetches.txt: bounded reads 4 -> 5 (scripts/tri_loop/pr_state.py asks the PR API once per PR, bounded); - shell.txt: run steps 264 -> 267 and runner steps 243 -> 246 (the three test steps loop-tools-gate.yml adds). Master's base is lower than at 859d4f4 because master removed CI steps since then. `tri census pin --gate`: PASS. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR DashboardGenerated at: 2026-10-04 11:20:27 UTC
Summary
Seal Status
|
… nine as switch (#5964) * fix(t27c): typecheck refuses a Rust match block the parser kept as text Since W914 the parser captures a Rust `match` block verbatim into a StmtExpr so parsing does not fail, and no backend reads that node: a match used as a fn's value generated an empty body in every backend with every gate green. check_captured_match now refuses each captured block in typecheck, naming its line and its fn or test, and points at t27's own `switch`. Parse still accepts it, so generation is unchanged. `match` as an identifier, field, member or call is not refused. Unit tests: tests_5949_captured_match (refusal names lines 4 and 10; negative control: switch, var match, match = false, Hit{match:1}, g(h.match), match(n)). Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): rewrite nine match blocks as t27 switch; ledger git/diff 31 captured Rust match blocks in 7 specs. 9 are rewritten as switch (or if) in auth/config, ar/restraint, ar/coa_planning, ar/composition, igla/training/roadmap and igla/evaluation/multi_lang_harness, and their seals re-saved. ar/composition execute_ar_component keeps its match: its arms assign a tuple, which t27 has no statement for; that spec is already red upstream. git/diff matches Ok/Err on Command::new("git") in 21 blocks and is ledgered at typecheck (issue 5949, expires 2026-11-30). max_entries 112 -> 113 is a hand edit, recorded in _why_entries_rose: no entry could be retired without inventing a value. Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a Rust match block the parser kept as text (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen): signed and float % lowers to @Rem (Closes #5973) Zig refuses infix `%` unless both operands are unsigned ("signed integers and floats must use @Rem or @mod"), so `return a % b;` with `a: i32` made the generated file fail to compile. The W593 `@divTrunc` arm for `/` already had the right test; `%` now uses it and emits `@rem(a, b)`, the truncated remainder (rem(-7, 2) == -1, as in C, Rust and t27b). Unsigned `%` stays infix. `/` needed no change. 8 of 1184 specs change gen output and 0 change gen-c; none has a new zig error, and compiler/optimizer.t27 now passes 14/14. 12 seals resealed. Found by the t27b differential test. Part of #5905. Closes #5973 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): type integral Rust repeat counts (Fixes #5986) * feat(t27b): run invariant blocks like tests, report them apart An `invariant` block lowers exactly like a `test` (a parameterless body, the test binding rule) and runs through the same trap machinery. t27c's Zig backend emits it as `comptime { ... }`, so a broken invariant is a compile error there; t27b runs it at test time and prints INVARIANT PASS / INVARIANT FAIL, plus an `invariants N held, M broken, K not checked` line. An invariant whose body the front-end discarded (a `forall`, an unparseable clause) is NOT CHECKED, never "held"; a partially parsed one is rejected, as a partial test already was. Clause-form blocks carry no line on any node, so their header line is looked up in the source text. Corpus: 48 supported (47 pass, 1 spec failure), was 37; 0 mismatches. Differential: 0 mismatches, 2500 programs per mode. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(t27b): memory primitives -- frame slots, loads/stores, rodata, bounds The IR primitives that structs, arrays, slices and strings lower onto, end to end through the interpreter, the JIT and the Mach-O object: - Ty::Ptr; ExprKind::Slot / Data / Load{addr,off} / Offset{base,idx, scale} / Bounds{idx,len,site}; Stmt::Store / Copy; Func::slots; Program::data; TrapKind::Bounds = 16 (numbered high, so the scalar lane can add kinds below it). - eval (the oracle): a byte-addressed model with per-call slots, a gap between objects and per-byte written flags. It faults on a read of unwritten bytes, an access outside a live slot or blob, a store into data, and a bool load of anything but 0/1. A fault is a lowering defect and never agrees with the JIT. - codegen: - an aggregate area below x29, up to 16 KiB; - x29-relative ldur/stur fast paths; - uimm, unscaled and register load/store forms, with signed narrow loads; - ADRP + ADD for data; - shifted add or madd for Offset; - cmp + b.hs to a Bounds trap stub; - Copy unrolled 8/4/2/1, or as a post-indexed loop past 64 bytes. - JIT: data blobs appended to the image and patched in place. - Mach-O: a __const section with PAGE21/PAGEOFF12 relocations against local l_t27b_data.N symbols. - a64: sized load/store, adr/adrp encoders and disassembly, with 32 new clang-verified encodings and an otool comparison. Tests: - The random differential generator covers every primitive: slots up to 9000 bytes, data blobs, loads and stores of every scalar type at constant, masked and bounds-checked indices, short and looped copies, and pointer parameters into the caller's frame (the hidden-pointer ABI). 0 mismatches over 2500 programs per overflow mode. - tests/macho.rs links an object with clang and runs it against the interpreter. - tests/memory.rs pins the interpreter's faults. Corpus unchanged (47 pass): nothing lowers to these yet. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): preserve immutable constant names in Rust (Fixes #5994) * gate(coq): re-bless the three trios-coq files #4754 built, and run on trios-coq/** (#6006) Coq kernel has been red on master on every run since 2026-09-24 (six, 28fd522 through 558dab4), at "No new Coq file falls outside every build": NullorReversible.v, SparsityMask.v and SpeculativeExit.v joined trios-coq/_CoqProject in #4754 without a re-bless. - tools/coq_outside_build_baseline.txt: `check_coq_in_build.py --bless`, 18 -> 15. The three compile locally (Rocq 9.1.1, `coqc -Q . TriosCoq`, exit 0 each, 11 Qed each, `Admitted` only inside comments). - coq-kernel.yml: `trios-coq/**` in both path lists. The checker reads every _CoqProject, but the workflow ran only for coq/ and proofs/, so #4754 started no run and #4755 inherited the red. Negative control: putting SparsityMask.v back in the baseline exits 1 with "1 file(s) joined a build". `--self-check` 6/6, `tri census pin --gate` PASS, check_pr_branch_filters.py exit 0. Closes #6005 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(c): initialize array-repeat wrappers by value (Fixes #6013) * tri: stranded-work finder and cron tick resume card (#5787) * tri: stranded-work finder and cron tick resume card tri stranded lists remote branches with commits off the default branch, tips older than --hours, and no open same-repo PR. It skips dependabot/*, patch-equivalent branches (git cherry) and squash-merged ones (merge-tree result equals the default's tree), reports merge clean/conflict and whether the branch ever had a PR, and runs merge-tree against a throwaway object directory so the clone is never written. With no PR state nothing is called stranded: the rows are "undecided" and the exit is 2. tri tick is the resume card for a cron loop: claim and its age, the last ledger section, each worktree's branch, dirty count, ahead/behind vs its base and last commit age, and anomalies with a one-line fix each. git runs with GIT_OPTIONAL_LOCKS=0; nothing is written. Both self-tests run in loop-tools-gate, each with a negative control, and each was seen to fail on a mutated copy of its tool. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri census: re-bless fetches 4->5 and shell 283->285 for tri stranded/tick Two pinned numbers moved, both because of this PR: - fetches: SURFACE bounded reads in scripts/tri_loop/*.py 4 -> 5. The new read is `gh pr list --limit PR_LIMIT` in scripts/tri_loop/stranded.py. It already refused to answer on a full page, but said so as "a lower bound" in lowercase, which the census (says_lower_bound, the Rust side's wording) does not read. The message now says LOWER BOUND, so the split is 5 guarded and 0 that do not, rather than 4 and 1. - shell: run steps 283 -> 285 (runner bash 262 -> 264). The rise comes from loop-tools-gate.yml, which gains the two tri_loop tests (test_stranded_work_is_found_without_writing.py and test_a_tick_resumes_from_what_it_reads.py). quiet was re-recorded byte-identical. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" (#5824) * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" A cron loop reported t27#5787 as "open" for six ticks while cli-tri was red. tri pr-state reads every PR a tick-state.json names: gh pr view, then tri pr ready while it is open, quoting the verdict and its exit code. SETTLED only when every verdict is "safe to merge"; an open PR with a running check exits 1. Anomalies: head-moved, not-open, unresolved (an alias is never guessed), unreadable, no-verdict. Read-only; --answers replaces gh and tri for the offline test. tri tick's pick_id/usage take the command name so tri pr-state errors under its own; its card points at tri pr-state for "still open?". First real run, cron 8782e5f8, 9 PRs: all safe, exit 0, and one head-moved on 999#3554 -- the loop's own tick-8 push, never recorded. Census: shell run: steps 285 -> 286 and runner bash 264 -> 265, the one new loop-tools-gate step for scripts/ci/test_an_open_pr_is_not_a_green_one.py. Fetches unchanged: pr_state.py has no --limit read. Closes #5823 Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: the printed card says safe is not proof any check ran The billing-blocked caveat lived only in the docstring, while #5824's body said the card's NOT ESTABLISHED block carried it. Now the printed footer says it, and the test asserts it on the settled (healthy) card; seen red with the word removed. No census moved. Closes #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): a released claim is not held, and the dead-tick fix names its files (#5864) * fix(tri tick): a released claim is not held, and the dead-tick fix names its files tri tick never read claim.released. A tick that had finished read as dead 45 minutes later. On tick 18 of cron 8782e5f8, its fix line was `git add -A && git commit` on PR #5839's worktree, whose only dirty files were cron_tracking/ and .claude/launch.json, both untracked on purpose. - A release that parses and is not older than `since` ends the hold. A leftover or unparseable release does not; the unparseable one is reported as claim-unparseable on claim.released. - Dirty excludes the state directory and a worktree's keep_untracked paths, counted per file (--untracked-files=all). Every other untracked file still counts. - The fix is `git add -- <each file, quoted>`, never `add -A`. - Test 20 -> 36 checks; 12 mutations, each red. Closes #5863 Refs #5786 #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): dirty work nobody holds is an anomaly, released claim or not (#5896) A released claim made every dirty worktree silent. Tick 21 of cron 8782e5f8 delegated two tasks to background agents and released; the session ended, both agents died uncommitted, and tick 22's card said ANOMALIES: 0. New anomaly orphaned-dirty: no claim held, a worktree dirty, its newest dirty file older than --stale-minutes, and no process with its cwd inside it (lsof -d cwd; "NOT RUN" without lsof). Fresh writes or a live process print a DELEGATE line instead. The detail quotes held_by; the fix names the files, never add -A, and clears held_by. Test: orphaned, no claim, and two negative controls (fresh write, a live sleep standing in the tree). 7 mutations, all red. Closes #5895 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(tri pr-state): --why, and each name under the heading tri printed it under (#5868) tri pr-state could not pass --why to tri pr ready, so a failure red elsewhere under the same check name read as pre-existing in the loop's report. Its parser also put every " - name" after the VERDICT into one only_here list: CANNOT TELL's no-baseline names and --why's new-reason names read as "only here", and the NOT compared list above the VERDICT was never read. - --why is passed through; a tri without the flag (clap usage error, exit 2 = WAIT's code) is a no-verdict that says so, never WAIT - only_here / no_baseline / new_reason / not_compared are kept apart; an unknown heading, an unknown verdict, or a name under no heading is kept as `other` with its heading and printed - with --why a REASONS line counts over the rows that got a verdict and names the rest as not compared (a live run with an old tri read "REASONS: 0" over 18 no-verdicts before this) - test 27 -> 51 checks, #5452's real answer as a fixture, a fake gh and tri on PATH proving the flag reaches argv; 19 mutations, each red Closes #5867 Refs #5823 #5852 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): quote declared primitive value bindings (Fixes #6040) * feat(t27b): structs, field access, pointers and struct results Lower struct declarations, struct literals, field reads and writes, `&x` / `p.*` / `*T`, struct parameters and struct results to the memory primitives of the previous change. Structs always live in memory: - A struct local or temporary lives in a frame slot. Its literal is built in place when the destination is fresh and its address is pure; otherwise it is built in a temporary and copied. - A struct parameter is the caller's memory, passed by address and never written. - A struct result is built through a hidden last pointer parameter. Such functions are listed in `Program.internal_abi`, and `t27b build` does not export them (it prints a note). - A module struct constant goes to read-only data. A scalar field of it folds to a constant (`const OX: i32 = ORIGIN.x`). - Layout follows the C rules and is computed on first use. A pointee is only named, so `next: *const Node` works. A struct that contains itself by value is rejected, and so is a generic struct. - An address-taken scalar local or parameter lives in a slot. - Compound assignment through an impure address evaluates the address once. Every rejection is precise: a missing, unknown or duplicate field, a positional initializer, an anonymous `.{}` with no result type, a write through a constant, a struct or pointer operand, a non-value `X.y`, and a struct that contains itself. IR: new `ExprKind::Seq { stmts, value }` (stores and copies, then a value), so a struct temporary is built exactly where its argument is evaluated. The interpreter and the code generator both implement it, and the random differential generator now emits it. Corpus (specs/, --jobs 6): 47 -> 70 files supported with every test passing, 0 JIT/interpreter mismatches. - One newly reached file fails one test (fpga/verification/build_verify, module count 33 vs 31). t27c gen + zig test fails the same assertion, so the reference fails here too. - Two files time out in the t27c parse phase alone at a load average near 780. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): install Zig for native controls; shell run steps 264 to 265 (Refs #6040) * fix(ci): install corpus Icarus prerequisite; shell 268 to 269 (Refs #6040) * fix(verilog): preserve conditional return and assertion context; Fixes #6043 * fix(rust): preserve function and conditional tail values; Fixes #6062 * feat(core): t27core -- self-hosted compiler core MVP, fixpoint with gen-c (#6041) A t27 compiler written in t27 (specs/compiler/core/t27core.t27): lexer, parser, checks and C emitter for the subset `t27c gen-c` lowers without loss. Built by gen-c and run on its own source, it writes gen-c's output byte for byte. bootstrap/tests/core_selfhost.rs gates the fixpoint, the core's own test blocks, 6 fixtures, 16 refusals with their codes, and a differential over specs/: 58 specs accepted, all byte-identical to gen-c (floor 50). Shapes gen-c lowers with loss are refused, not copied: `- -x` as `--x`, declarations after `endmodule`, top-level consts truncated after their first token, and the earlier ones recorded on the epic. Closes #5981 Refs #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(agents): Gamma holds a /self-host skill for epic #5980 (#6054) The rules of the t27-in-t27 compiler had no home a bee loads: byte identity with gen-c, refusing the shapes gen-c lowers with loss, probing gen-c first, and the ;-alone-line trap. They now live in .claude/skills/self-host/SKILL.md. Its card is specs/skills/t27-self-host.t27, and agent C holds it through its prompt's Skills section and the alphabet's domain-lead table. Closes #6053 Part of #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse a colon inside a field type and @as to a slice type; fix the six silent specs (#5971) * fix(t27c): typecheck refuses a colon inside a field type and @as to a slice type Six specs parsed, typechecked and generated output their backend cannot compile (`tri misread` silent pairs). Two defects: - A struct field whose type, with `::` paths removed, still holds a `:`. Either the field has no `,` after it (missing, or inside a trailing `#` comment, which runs to the end of the line) and swallowed the fields after it, or it is a map type `[K: V]`, which t27 does not have. - `@as(T, x)` with a slice or array type: in argument position the parser reads `[]u8` as an array literal, and every backend loses the type. Both are refused in typecheck, naming the line. The parser now records a struct field's line; no backend reads it, so generated output does not move (seal currency: 0 stale generated-code hashes). Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): put the comma before the comment, drop the map type and slice casts - ar/coa_planning: the missing `,` after `verification_status`. - tri/agent/governance_agent, tri/utils/help: `field : T # note,` is now `field : T, // note`; the `#` comment had taken the `,` with it. - git/schema: `env: [str: str]` is now `env: []EnvVar`; the one constructor, git/operations' options_construction test, builds EnvVar. - port/tools/wp18: `_ssot` writes one `// CATALOG: id=<id>` line per id, as the Python original does, instead of an `@as([]u8, ..)` cast. - tri/pipeline/builder: `@as([]T, &.{})` in a test is now a length check. Re-sealed with `t27c seal --save`, including git/status and git/diff, whose generated code inherits Options. Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a colon inside a field type and a slice cast (Closes #5968) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): var tuple destructure silences never-mutated Zig locals (Closes #5886) (#5902) `var (s, d) = f();` lowered to `var s, var d = f();` with no silencer, so Zig rejected every element the function never reassigns ("local variable is never mutated") and `t27c test-report` printed BLOCKED for d_slow_blink.t27 (Refs #5682). The StmtLocal tuple-destructure branch now emits, for a `var` destructure, the same `_ = &name;` per named element that the single-name `var` path already emits, and records the name in `discarded_by_ref` so W730 drops a later `_ = name;` (a pointless discard in Zig). A bare `_` element gets no silencer; `let (s, d)` is unchanged. Test: test_var_destructure_never_reassigned_zig. Without the fix it fails by assertion ("`_ = &s;` silencer missing"); with it, it passes, and the whole t27c bin unit suite passes (1759, 0 failed, 2 ignored). FROZEN_HASH resealed to the new compiler.rs digest. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(seals): re-seal wp18 with this branch's compiler after the master merge The master merge (2ec2d00) took master's wp18 seal, but this branch keeps the #5971 spec (spec_hash c2c89bb3..., the CATALOG port of `_ssot`). Lab run of 2ec2d00: seal-currency and seal-coverage red on this one file only. Re-sealed on the Railway t27c lab: t27c built from 2ec2d00, zig 0.16.0 on PATH, `t27c seal --save` + `tri seals sync-twins` (517 twins consistent, 0 written). gen_hash_zig is 7fd0ed8c, the same output #5971 sealed; the zig test result is unchanged (expected `;` at the `sha256::Hasher` line, 86:24 in the emitted source). Refs #6092 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) (#6003) * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) Dead-store elimination read `_` as a variable nobody reads and deleted every top-level `_ = call(args);` in a fn body, call and all. The unused-parameter pass then added `_ = p;` for a parameter that only the deleted call used, which Zig rejects as a pointless discard once the call is back. OptConfig gains keep_call_discards. Compiler::compile (gen-zig) sets it, so a discard whose right-hand side holds a call survives. It defaults to false, so the Verilog path is byte-identical (0 of 37 specs with a discarded call differ). Measured over 1297 tracked .t27 files: gen-zig output changes for 5 specs, 9 calls come back, 2 spurious `_ = param;` lines and 1 `_ = json_str;` line go. Their 9 seals are re-sealed. FROZEN_HASH re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest after the gen-zig discard-call fix Spec unchanged; only gen_hash_zig moves (the emitter fix in this PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest with the merged compiler (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dmitrii Fedorov <dmitrii.f@t27.ai>
…down specs (#5923) (#5947) * fix(specs): rewrite eight Markdown documents named .t27 as t27 modules Eight files under specs/ were Markdown with a .t27 name. The parser refused each at its first line, so they generated nothing and their "tests" had comment-only bodies. Each is now a module whose prose is `;` comments and whose arithmetic is checked: 54 test blocks, all passing under gen-zig. physics/hslm_benchmark (7) physics/e8_lqg_bridge (8) physics/lqg_cs_bridge (5) physics/gamma-conflict (10, module gamma_conflict) benchmarks/bench_nn (6) benchmarks/gf16_bfloat16_nmse (7) api/tri_net_api (5) conformance/e2e_scenarios (6) Where the document contradicted itself or the repository, the module records the defect rather than the claim: gamma-conflict carries six CORRECTIONS, tri_net_api states the FROZEN_HASH line format build.rs actually reads, and e2e_scenarios pins a pack scenario that overwrites its input before reading it and a total verdict its own rows do not imply. Seals re-taken with `t27c seal <spec> --save`. Refs #5923 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse Rust macro calls in parse and typeless fields in typecheck Two ways the compiler read a spec wrong while every gate stayed green. parse: `format!("{}", a)` was read as `format` followed by the statement `!("{}", a)`, which gen-rust lowered to `(("{}", a) == 0);`. `assert!(c)` in a test became an assert of a negated tuple. t27 has no macros; the parser now refuses an identifier glued to `!` and followed by `(`, `[` or `{` on the same line, naming the macro. `a != b`, a prefix `!x`, strings and comments are untouched (unit tests cover both sides). typecheck: a struct field whose type is empty or an integer literal is an error (#3225). The parser recovers list-valued declarations it does not implement as fields, and gen-rust wrote them as `pub f: ,` / `pub f: 0,`. Generation does not run typecheck, so those specs still generate exactly as before; what changes is that a gate now goes red on them. Specs that used macros are fixed in their source: auth/config `panic!` -> `unreachable`, seven port specs `assert!(` -> `assert(`. The generate baseline drops the eight rewritten Markdown specs and records the two whose remaining failure is now the named `format!` refusal. FROZEN_HASH updated for compiler.rs. Refs #5923, #3225 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(tri): misread splits each pair into refused and silent; ledger the new rule `tri misread` now typechecks every spec that carries a shape and reports each spec-shape pair as REFUSED (typecheck goes red) or SILENT (every gate green). A refusing typecheck is not trusted until a clean spec has been seen to typecheck and show no shape -- a broken binary would otherwise turn the whole table "refused" and read as progress. Measured on the corpus: 41 pairs, 35 refused, 6 silent (one Vec<> type, five colon-in-type specs). Suite ledger: +25 typecheck entries for the specs the new rule refuses (issue 3225, each naming its struct and fields), -8 parse entries for the specs rewritten as modules; cap 126 -> 112. The tool card's ABOUT follows the new clap doc. Closes #5923 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ledger): swap two Lean completeness entries the Markdown rewrite moved; add the NOW entry corpus_classifier_matches_lean_completeness failed on CI. Two of the eight Markdown files this PR rewrote as t27 modules changed their Rust verdict: - physics_lqg_cs_bridge now agrees with its Lean theorem; retired. - benchmarks_gf16_bfloat16_nmse became classifiable and disagrees. The spec computes in f64, which Icarus does not lower (with the f64 lines removed the classifier answers true); the theorem was written over the Markdown document. Entered with its reason, as #2884 did. max_entries stays at 77 and max_vacuous at 44. Closes #5923 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse a Rust match block the parser kept as text; rewrite nine as switch (#5964) * fix(t27c): typecheck refuses a Rust match block the parser kept as text Since W914 the parser captures a Rust `match` block verbatim into a StmtExpr so parsing does not fail, and no backend reads that node: a match used as a fn's value generated an empty body in every backend with every gate green. check_captured_match now refuses each captured block in typecheck, naming its line and its fn or test, and points at t27's own `switch`. Parse still accepts it, so generation is unchanged. `match` as an identifier, field, member or call is not refused. Unit tests: tests_5949_captured_match (refusal names lines 4 and 10; negative control: switch, var match, match = false, Hit{match:1}, g(h.match), match(n)). Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): rewrite nine match blocks as t27 switch; ledger git/diff 31 captured Rust match blocks in 7 specs. 9 are rewritten as switch (or if) in auth/config, ar/restraint, ar/coa_planning, ar/composition, igla/training/roadmap and igla/evaluation/multi_lang_harness, and their seals re-saved. ar/composition execute_ar_component keeps its match: its arms assign a tuple, which t27 has no statement for; that spec is already red upstream. git/diff matches Ok/Err on Command::new("git") in 21 blocks and is ledgered at typecheck (issue 5949, expires 2026-11-30). max_entries 112 -> 113 is a hand edit, recorded in _why_entries_rose: no entry could be retired without inventing a value. Closes #5949 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a Rust match block the parser kept as text (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen): signed and float % lowers to @Rem (Closes #5973) Zig refuses infix `%` unless both operands are unsigned ("signed integers and floats must use @Rem or @mod"), so `return a % b;` with `a: i32` made the generated file fail to compile. The W593 `@divTrunc` arm for `/` already had the right test; `%` now uses it and emits `@rem(a, b)`, the truncated remainder (rem(-7, 2) == -1, as in C, Rust and t27b). Unsigned `%` stays infix. `/` needed no change. 8 of 1184 specs change gen output and 0 change gen-c; none has a new zig error, and compiler/optimizer.t27 now passes 14/14. 12 seals resealed. Found by the t27b differential test. Part of #5905. Closes #5973 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): type integral Rust repeat counts (Fixes #5986) * feat(t27b): run invariant blocks like tests, report them apart An `invariant` block lowers exactly like a `test` (a parameterless body, the test binding rule) and runs through the same trap machinery. t27c's Zig backend emits it as `comptime { ... }`, so a broken invariant is a compile error there; t27b runs it at test time and prints INVARIANT PASS / INVARIANT FAIL, plus an `invariants N held, M broken, K not checked` line. An invariant whose body the front-end discarded (a `forall`, an unparseable clause) is NOT CHECKED, never "held"; a partially parsed one is rejected, as a partial test already was. Clause-form blocks carry no line on any node, so their header line is looked up in the source text. Corpus: 48 supported (47 pass, 1 spec failure), was 37; 0 mismatches. Differential: 0 mismatches, 2500 programs per mode. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(t27b): memory primitives -- frame slots, loads/stores, rodata, bounds The IR primitives that structs, arrays, slices and strings lower onto, end to end through the interpreter, the JIT and the Mach-O object: - Ty::Ptr; ExprKind::Slot / Data / Load{addr,off} / Offset{base,idx, scale} / Bounds{idx,len,site}; Stmt::Store / Copy; Func::slots; Program::data; TrapKind::Bounds = 16 (numbered high, so the scalar lane can add kinds below it). - eval (the oracle): a byte-addressed model with per-call slots, a gap between objects and per-byte written flags. It faults on a read of unwritten bytes, an access outside a live slot or blob, a store into data, and a bool load of anything but 0/1. A fault is a lowering defect and never agrees with the JIT. - codegen: - an aggregate area below x29, up to 16 KiB; - x29-relative ldur/stur fast paths; - uimm, unscaled and register load/store forms, with signed narrow loads; - ADRP + ADD for data; - shifted add or madd for Offset; - cmp + b.hs to a Bounds trap stub; - Copy unrolled 8/4/2/1, or as a post-indexed loop past 64 bytes. - JIT: data blobs appended to the image and patched in place. - Mach-O: a __const section with PAGE21/PAGEOFF12 relocations against local l_t27b_data.N symbols. - a64: sized load/store, adr/adrp encoders and disassembly, with 32 new clang-verified encodings and an otool comparison. Tests: - The random differential generator covers every primitive: slots up to 9000 bytes, data blobs, loads and stores of every scalar type at constant, masked and bounds-checked indices, short and looped copies, and pointer parameters into the caller's frame (the hidden-pointer ABI). 0 mismatches over 2500 programs per overflow mode. - tests/macho.rs links an object with clang and runs it against the interpreter. - tests/memory.rs pins the interpreter's faults. Corpus unchanged (47 pass): nothing lowers to these yet. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(compiler): preserve immutable constant names in Rust (Fixes #5994) * gate(coq): re-bless the three trios-coq files #4754 built, and run on trios-coq/** (#6006) Coq kernel has been red on master on every run since 2026-09-24 (six, 28fd522 through 558dab4), at "No new Coq file falls outside every build": NullorReversible.v, SparsityMask.v and SpeculativeExit.v joined trios-coq/_CoqProject in #4754 without a re-bless. - tools/coq_outside_build_baseline.txt: `check_coq_in_build.py --bless`, 18 -> 15. The three compile locally (Rocq 9.1.1, `coqc -Q . TriosCoq`, exit 0 each, 11 Qed each, `Admitted` only inside comments). - coq-kernel.yml: `trios-coq/**` in both path lists. The checker reads every _CoqProject, but the workflow ran only for coq/ and proofs/, so #4754 started no run and #4755 inherited the red. Negative control: putting SparsityMask.v back in the baseline exits 1 with "1 file(s) joined a build". `--self-check` 6/6, `tri census pin --gate` PASS, check_pr_branch_filters.py exit 0. Closes #6005 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(c): initialize array-repeat wrappers by value (Fixes #6013) * tri: stranded-work finder and cron tick resume card (#5787) * tri: stranded-work finder and cron tick resume card tri stranded lists remote branches with commits off the default branch, tips older than --hours, and no open same-repo PR. It skips dependabot/*, patch-equivalent branches (git cherry) and squash-merged ones (merge-tree result equals the default's tree), reports merge clean/conflict and whether the branch ever had a PR, and runs merge-tree against a throwaway object directory so the clone is never written. With no PR state nothing is called stranded: the rows are "undecided" and the exit is 2. tri tick is the resume card for a cron loop: claim and its age, the last ledger section, each worktree's branch, dirty count, ahead/behind vs its base and last commit age, and anomalies with a one-line fix each. git runs with GIT_OPTIONAL_LOCKS=0; nothing is written. Both self-tests run in loop-tools-gate, each with a negative control, and each was seen to fail on a mutated copy of its tool. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri census: re-bless fetches 4->5 and shell 283->285 for tri stranded/tick Two pinned numbers moved, both because of this PR: - fetches: SURFACE bounded reads in scripts/tri_loop/*.py 4 -> 5. The new read is `gh pr list --limit PR_LIMIT` in scripts/tri_loop/stranded.py. It already refused to answer on a full page, but said so as "a lower bound" in lowercase, which the census (says_lower_bound, the Rust side's wording) does not read. The message now says LOWER BOUND, so the split is 5 guarded and 0 that do not, rather than 4 and 1. - shell: run steps 283 -> 285 (runner bash 262 -> 264). The rise comes from loop-tools-gate.yml, which gains the two tri_loop tests (test_stranded_work_is_found_without_writing.py and test_a_tick_resumes_from_what_it_reads.py). quiet was re-recorded byte-identical. Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" (#5824) * tri pr-state: a loop's pull requests with tri pr ready's verdict, not "open" A cron loop reported t27#5787 as "open" for six ticks while cli-tri was red. tri pr-state reads every PR a tick-state.json names: gh pr view, then tri pr ready while it is open, quoting the verdict and its exit code. SETTLED only when every verdict is "safe to merge"; an open PR with a running check exits 1. Anomalies: head-moved, not-open, unresolved (an alias is never guessed), unreadable, no-verdict. Read-only; --answers replaces gh and tri for the offline test. tri tick's pick_id/usage take the command name so tri pr-state errors under its own; its card points at tri pr-state for "still open?". First real run, cron 8782e5f8, 9 PRs: all safe, exit 0, and one head-moved on 999#3554 -- the loop's own tick-8 push, never recorded. Census: shell run: steps 285 -> 286 and runner bash 264 -> 265, the one new loop-tools-gate step for scripts/ci/test_an_open_pr_is_not_a_green_one.py. Fetches unchanged: pr_state.py has no --limit read. Closes #5823 Refs #5786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tri pr-state: the printed card says safe is not proof any check ran The billing-blocked caveat lived only in the docstring, while #5824's body said the card's NOT ESTABLISHED block carried it. Now the printed footer says it, and the test asserts it on the settled (healthy) card; seen red with the word removed. No census moved. Closes #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): a released claim is not held, and the dead-tick fix names its files (#5864) * fix(tri tick): a released claim is not held, and the dead-tick fix names its files tri tick never read claim.released. A tick that had finished read as dead 45 minutes later. On tick 18 of cron 8782e5f8, its fix line was `git add -A && git commit` on PR #5839's worktree, whose only dirty files were cron_tracking/ and .claude/launch.json, both untracked on purpose. - A release that parses and is not older than `since` ends the hold. A leftover or unparseable release does not; the unparseable one is reported as claim-unparseable on claim.released. - Dirty excludes the state directory and a worktree's keep_untracked paths, counted per file (--untracked-files=all). Every other untracked file still counts. - The fix is `git add -- <each file, quoted>`, never `add -A`. - Test 20 -> 36 checks; 12 mutations, each red. Closes #5863 Refs #5786 #5823 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tri tick): dirty work nobody holds is an anomaly, released claim or not (#5896) A released claim made every dirty worktree silent. Tick 21 of cron 8782e5f8 delegated two tasks to background agents and released; the session ended, both agents died uncommitted, and tick 22's card said ANOMALIES: 0. New anomaly orphaned-dirty: no claim held, a worktree dirty, its newest dirty file older than --stale-minutes, and no process with its cwd inside it (lsof -d cwd; "NOT RUN" without lsof). Fresh writes or a live process print a DELEGATE line instead. The detail quotes held_by; the fix names the files, never add -A, and clears held_by. Test: orphaned, no claim, and two negative controls (fresh write, a live sleep standing in the tree). 7 mutations, all red. Closes #5895 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(tri pr-state): --why, and each name under the heading tri printed it under (#5868) tri pr-state could not pass --why to tri pr ready, so a failure red elsewhere under the same check name read as pre-existing in the loop's report. Its parser also put every " - name" after the VERDICT into one only_here list: CANNOT TELL's no-baseline names and --why's new-reason names read as "only here", and the NOT compared list above the VERDICT was never read. - --why is passed through; a tri without the flag (clap usage error, exit 2 = WAIT's code) is a no-verdict that says so, never WAIT - only_here / no_baseline / new_reason / not_compared are kept apart; an unknown heading, an unknown verdict, or a name under no heading is kept as `other` with its heading and printed - with --why a REASONS line counts over the rows that got a verdict and names the rest as not compared (a live run with an old tri read "REASONS: 0" over 18 no-verdicts before this) - test 27 -> 51 checks, #5452's real answer as a fixture, a fake gh and tri on PATH proving the flag reaches argv; 19 mutations, each red Closes #5867 Refs #5823 #5852 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(zig): quote declared primitive value bindings (Fixes #6040) * feat(t27b): structs, field access, pointers and struct results Lower struct declarations, struct literals, field reads and writes, `&x` / `p.*` / `*T`, struct parameters and struct results to the memory primitives of the previous change. Structs always live in memory: - A struct local or temporary lives in a frame slot. Its literal is built in place when the destination is fresh and its address is pure; otherwise it is built in a temporary and copied. - A struct parameter is the caller's memory, passed by address and never written. - A struct result is built through a hidden last pointer parameter. Such functions are listed in `Program.internal_abi`, and `t27b build` does not export them (it prints a note). - A module struct constant goes to read-only data. A scalar field of it folds to a constant (`const OX: i32 = ORIGIN.x`). - Layout follows the C rules and is computed on first use. A pointee is only named, so `next: *const Node` works. A struct that contains itself by value is rejected, and so is a generic struct. - An address-taken scalar local or parameter lives in a slot. - Compound assignment through an impure address evaluates the address once. Every rejection is precise: a missing, unknown or duplicate field, a positional initializer, an anonymous `.{}` with no result type, a write through a constant, a struct or pointer operand, a non-value `X.y`, and a struct that contains itself. IR: new `ExprKind::Seq { stmts, value }` (stores and copies, then a value), so a struct temporary is built exactly where its argument is evaluated. The interpreter and the code generator both implement it, and the random differential generator now emits it. Corpus (specs/, --jobs 6): 47 -> 70 files supported with every test passing, 0 JIT/interpreter mismatches. - One newly reached file fails one test (fpga/verification/build_verify, module count 33 vs 31). t27c gen + zig test fails the same assertion, so the reference fails here too. - Two files time out in the t27c parse phase alone at a load average near 780. Refs #5977 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ci): install Zig for native controls; shell run steps 264 to 265 (Refs #6040) * fix(ci): install corpus Icarus prerequisite; shell 268 to 269 (Refs #6040) * fix(verilog): preserve conditional return and assertion context; Fixes #6043 * fix(rust): preserve function and conditional tail values; Fixes #6062 * feat(core): t27core -- self-hosted compiler core MVP, fixpoint with gen-c (#6041) A t27 compiler written in t27 (specs/compiler/core/t27core.t27): lexer, parser, checks and C emitter for the subset `t27c gen-c` lowers without loss. Built by gen-c and run on its own source, it writes gen-c's output byte for byte. bootstrap/tests/core_selfhost.rs gates the fixpoint, the core's own test blocks, 6 fixtures, 16 refusals with their codes, and a differential over specs/: 58 specs accepted, all byte-identical to gen-c (floor 50). Shapes gen-c lowers with loss are refused, not copied: `- -x` as `--x`, declarations after `endmodule`, top-level consts truncated after their first token, and the earlier ones recorded on the epic. Closes #5981 Refs #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * feat(agents): Gamma holds a /self-host skill for epic #5980 (#6054) The rules of the t27-in-t27 compiler had no home a bee loads: byte identity with gen-c, refusing the shapes gen-c lowers with loss, probing gen-c first, and the ;-alone-line trap. They now live in .claude/skills/self-host/SKILL.md. Its card is specs/skills/t27-self-host.t27, and agent C holds it through its prompt's Skills section and the alphabet's domain-lead table. Closes #6053 Part of #5980 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): refuse a colon inside a field type and @as to a slice type; fix the six silent specs (#5971) * fix(t27c): typecheck refuses a colon inside a field type and @as to a slice type Six specs parsed, typechecked and generated output their backend cannot compile (`tri misread` silent pairs). Two defects: - A struct field whose type, with `::` paths removed, still holds a `:`. Either the field has no `,` after it (missing, or inside a trailing `#` comment, which runs to the end of the line) and swallowed the fields after it, or it is a map type `[K: V]`, which t27 does not have. - `@as(T, x)` with a slice or array type: in argument position the parser reads `[]u8` as an array literal, and every backend loses the type. Both are refused in typecheck, naming the line. The parser now records a struct field's line; no backend reads it, so generated output does not move (seal currency: 0 stale generated-code hashes). Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(specs): put the comma before the comment, drop the map type and slice casts - ar/coa_planning: the missing `,` after `verification_status`. - tri/agent/governance_agent, tri/utils/help: `field : T # note,` is now `field : T, // note`; the `#` comment had taken the `,` with it. - git/schema: `env: [str: str]` is now `env: []EnvVar`; the one constructor, git/operations' options_construction test, builds EnvVar. - port/tools/wp18: `_ssot` writes one `// CATALOG: id=<id>` line per id, as the Python original does, instead of an `@as([]u8, ..)` cast. - tri/pipeline/builder: `@as([]T, &.{})` in a test is now a length check. Re-sealed with `t27c seal --save`, including git/status and git/diff, whose generated code inherits Options. Closes #5968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(now): t27c refuses a colon inside a field type and a slice cast (Closes #5968) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(t27c): var tuple destructure silences never-mutated Zig locals (Closes #5886) (#5902) `var (s, d) = f();` lowered to `var s, var d = f();` with no silencer, so Zig rejected every element the function never reassigns ("local variable is never mutated") and `t27c test-report` printed BLOCKED for d_slow_blink.t27 (Refs #5682). The StmtLocal tuple-destructure branch now emits, for a `var` destructure, the same `_ = &name;` per named element that the single-name `var` path already emits, and records the name in `discarded_by_ref` so W730 drops a later `_ = name;` (a pointless discard in Zig). A bare `_` element gets no silencer; `let (s, d)` is unchanged. Test: test_var_destructure_never_reassigned_zig. Without the fix it fails by assertion ("`_ = &s;` silencer missing"); with it, it passes, and the whole t27c bin unit suite passes (1759, 0 failed, 2 ignored). FROZEN_HASH resealed to the new compiler.rs digest. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(seals): re-seal wp18 with this branch's compiler after the master merge The master merge (2ec2d00) took master's wp18 seal, but this branch keeps the #5971 spec (spec_hash c2c89bb3..., the CATALOG port of `_ssot`). Lab run of 2ec2d00: seal-currency and seal-coverage red on this one file only. Re-sealed on the Railway t27c lab: t27c built from 2ec2d00, zig 0.16.0 on PATH, `t27c seal --save` + `tri seals sync-twins` (517 twins consistent, 0 written). gen_hash_zig is 7fd0ed8c, the same output #5971 sealed; the zig test result is unchanged (expected `;` at the `sha256::Hasher` line, 86:24 in the emitted source). Refs #6092 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) (#6003) * fix(gen-zig): keep `_ = call(..);` instead of deleting it (Closes #5984) Dead-store elimination read `_` as a variable nobody reads and deleted every top-level `_ = call(args);` in a fn body, call and all. The unused-parameter pass then added `_ = p;` for a parameter that only the deleted call used, which Zig rejects as a pointless discard once the call is back. OptConfig gains keep_call_discards. Compiler::compile (gen-zig) sets it, so a discard whose right-hand side holds a call survives. It defaults to false, so the Verilog path is byte-identical (0 of 37 specs with a discarded call differ). Measured over 1297 tracked .t27 files: gen-zig output changes for 5 specs, 9 calls come back, 2 spurious `_ = param;` lines and 1 `_ = json_str;` line go. Their 9 seals are re-sealed. FROZEN_HASH re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest after the gen-zig discard-call fix Spec unchanged; only gen_hash_zig moves (the emitter fix in this PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * seal: re-seal wp18 selftest with the merged compiler (Closes #5949) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dmitrii Fedorov <dmitrii.f@t27.ai> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dmitrii Fedorov <dmitrii.f@t27.ai>
Refs #5786
Two read-only
triloop helpers, stdlib only,--json, each printing what it does NOT establish.tri stranded--scripts/tri_loop/stranded.pyRemote branches with commits off the default branch, tip older than
--hours(24), and no open same-repo PR. Motivating case: trinityfeat/queen-browser-embedded-- 2 commits, merging clean, pushed 2026-09-29, no PR, found by a cron tick by accident.refs/remotes/origin/HEAD; open heads fromgh pr list. If gh cannot answer, the rows are "undecided" and the exit is 2 -- nothing is called stranded.git cherry), and branches whose merge result equals the default's tree (squash-merged; 245 of these in t27).merge-treeclean or conflict, and PR history ("never had one" / "#N merged"). Never-had-one rows come first.merge-treewrites objects, so it runs withGIT_OBJECT_DIRECTORYset to a temp dir (the repo's objects as an alternate). The clone is not written.Real runs (exit 1 both):
tri tick--scripts/tri_loop/tick.pyThe resume card for a cron loop, read from
cron_tracking/<id>/tick-state.jsonandledger.md. It prints topic, tick, claim and its age, and the last ledger##section. For each worktree: exists, branch match, dirty count, ahead/behind vs base (local refs), and last commit age.Anomalies, each with a one-line fix:
stale-claim-dirtyworktree-missingbranch-mismatchbase-unknownstate-unparseableclaim-unparseableledger-tick-mismatchclaim-in-futureExit 0 / 1 / 2. git runs with
GIT_OPTIONAL_LOCKS=0.claim-in-futurewas added after the first real run. The live state8782e5f8hasclaim.since6 minutes ahead of the clock, and until that time passes the stale-claim check cannot fire. The real run exits 1 on exactly that anomaly.Tests (in
loop-tools-gate)scripts/ci/test_stranded_work_is_found_without_writing.py: a bare origin plus a clone, with one branch of each kind. It checks the PR-state-unknown path (exit 2) and that no loose object is written. Negative control: a plain merge-tree does write. Seen to fail when the object-directory redirect is removed.scripts/ci/test_a_tick_resumes_from_what_it_reads.py: covers healthy, broken, future, junk, no--idand bad--id, and checks that the index is not rewritten (negative control: a plaingit statusrewrites it). Seen to fail withGIT_OPTIONAL_LOCKSremoved, and with the stale check ignoring dirtiness.Not done / notes
specs/tools/tri/covers the Rust binary only, so trinity needs no re-vendor.REQUIRED_TOOLSinloop-tools-tracked.sh, which lists measurement-loop dependencies.pr_mine,swarmandwhats_openare not in it either.t27c; this worktree has no build, so the commit usedTRI_T27Cpointed at the main checkout's binary.🤖 Generated with Claude Code