From 918a4657637bc7e6a6e934270710e2d22c67e290 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 11:40:48 +0700 Subject: [PATCH 1/8] t27b: a bare sqrt(x) with no fn sqrt declared lowers as @sqrt, as t27c's Zig backend prints it (Refs #10837) Co-Authored-By: Claude Sonnet 5.5 --- cli/t27b/src/lower_float.rs | 17 ++++++++--------- cli/t27b/tests/source.rs | 8 ++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cli/t27b/src/lower_float.rs b/cli/t27b/src/lower_float.rs index a9b0a46fc1..2fc52f2c4b 100644 --- a/cli/t27b/src/lower_float.rs +++ b/cli/t27b/src/lower_float.rs @@ -72,21 +72,20 @@ impl<'a> Lower<'a> { } } - /// `abs(x)` with no `fn abs` declared: t27c's Zig backend prints the - /// builtin `@abs(x)` (its `declared_fns` guard). A typed f64 / f32 loses - /// its sign bit, so `-0.0` gives `+0.0`; written `x <= 0 ? 0 - x : x`, - /// with `x` evaluated once (`NaN <= 0` is false: a NaN comes back as is). - /// A comptime operand folds. A typed integer is refused: Zig's `@abs` - /// of an `iN` is a `uN`. `None`: not this call. + /// `abs(x)` / `sqrt(x)` with no such fn declared: t27c's Zig backend prints the builtin `@abs(x)` / `@sqrt(x)` + /// (its `declared_fns` guard). A typed f64 / f32 `abs` loses its sign bit, so `-0.0` gives `+0.0`; written + /// `x <= 0 ? 0 - x : x`, with `x` evaluated once (`NaN <= 0` is false: a NaN comes back as is). A comptime + /// operand folds. A typed integer `abs` is refused: Zig's `@abs` of an `iN` is a `uN`. `None`: not this call. pub(super) fn bare_abs(&mut self, n: &Node) -> R> { - if n.name != "abs" || self.sigs.contains_key("abs") || self.poison_names.contains("abs") { + let g = n.name.as_str(); + if !matches!(g, "abs" | "sqrt") || self.sigs.contains_key(g) || self.poison_names.contains(g) { return Ok(None); } + if g == "sqrt" { return self.expr(&Node { name: "@sqrt".into(), ..n.clone() }).map(Some); } self.builtin_plan(n).map(Some) } - /// `@abs(x)`, `@max(a, b)`, `@min(a, b)`, `@divTrunc(a, b)` and a bare `abs(x)`: what they take, the - /// result type and the select chain or operator each lowers to are specs/tri/t27b/builtin_plan.t27. + /// `@abs`, `@max`, `@min`, `@divTrunc` and a bare `abs`: operands, result type and select chain/operator are specs/tri/t27b/builtin_plan.t27. pub(super) fn builtin_plan(&mut self, n: &Node) -> R { let b = match n.name.as_str() { "@abs" => bp::ABS, "@max" => bp::MAX, "@min" => bp::MIN, "@divTrunc" => bp::DIV_TRUNC, _ => bp::BARE_ABS }; self.see(n); diff --git a/cli/t27b/tests/source.rs b/cli/t27b/tests/source.rs index d29b43e3a3..c491d07acc 100644 --- a/cli/t27b/tests/source.rs +++ b/cli/t27b/tests/source.rs @@ -3024,9 +3024,8 @@ fn type_alias_rejections() { // ------------------------------------------------------------ bare abs -/// `abs(x)` with no `fn abs` declared is Zig's `@abs` (#7227): t27c prints -/// the builtin. A float loses its sign bit (`-0.0` gives `+0.0`), the -/// operand runs once, and a comptime operand folds. +/// `abs(x)` with no `fn abs` declared is Zig's `@abs` (#7227): t27c prints the builtin. A float loses its +/// sign bit (`-0.0` gives `+0.0`), the operand runs once, a comptime operand folds; bare `sqrt` is `@sqrt`. #[test] fn bare_abs_is_the_zig_builtin() { let src = "module a; @@ -3059,6 +3058,7 @@ test ok { assert(CALLS == 1); assert(abs(-2.5) == 2.5); assert(abs(-3) == 3); + assert(sqrt(mag(-16.0)) == 4.0); } test fails { @@ -3079,7 +3079,7 @@ fn bare_abs_rejections() { let cases: &[(&str, &str)] = &[ ("fn f(x: i32) -> u32 { return abs(x); }", "ExprCall(abs of an integer)"), ("fn f(x: f64, y: f64) -> f64 { return abs(x, y); }", "ExprCall(undeclared fn)"), - ("fn f(x: f64) -> f64 { return sqrt(x); }", "ExprCall(undeclared fn)"), + ("fn f(x: f64) -> f64 { return tanh(x); }", "ExprCall(undeclared fn)"), ]; for (body, construct) in cases { let m = rejected(&format!("module a;\n\n{}\n", body)); From 3adb935488124ad2d0d19c7224377b4490eb65d6 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 11:48:29 +0700 Subject: [PATCH 2/8] ledger(t27b): rows for hslm_tokenizer (pass) and hslm_corpus (blocked on host I/O), 11 sqrt specs flip to pass, cap 139 -> 129 (Closes #10837) Co-Authored-By: Claude Sonnet 5.5 --- docs/reports/t27b_expectations.json | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index c65d3ed1de..7ce6b4c1e9 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -12,7 +12,7 @@ "reference-bug": "the reference passes it for the wrong reason; t27b is right not to", "n/a": "outside what t27b is for (say why in the PR that sets it)" }, - "max_not_pass": 139, + "max_not_pass": 129, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -455,7 +455,7 @@ {"path": "specs/hosting/sybil.t27", "t27b": "pass"}, {"path": "specs/hosting/toolchain.t27", "t27b": "pass"}, {"path": "specs/hosting/topology.t27", "t27b": "pass"}, - {"path": "specs/hslm/forward_pass.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/hslm/forward_pass.t27", "t27b": "pass"}, {"path": "specs/i18n/agents-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/blog-ru.t27", "t27b": "pass"}, {"path": "specs/i18n/docs-ru.t27", "t27b": "pass"}, @@ -464,7 +464,7 @@ {"path": "specs/igla/coder/benchmark.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/dataset.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/igla/coder/eval.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, - {"path": "specs/igla/coder/kill_rule.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/igla/coder/kill_rule.t27", "t27b": "pass"}, {"path": "specs/igla/coder/pipeline.t27", "t27b": "blocked", "blocker": "ConstDecl(slice)", "reason": "unimplemented"}, {"path": "specs/igla/coder/preflight.t27", "t27b": "pass"}, {"path": "specs/igla/coder/prm.t27", "t27b": "pass"}, @@ -543,27 +543,28 @@ {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/hslm_corpus.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/igla_champion_capsule.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/layers/avgpool2d_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/conv2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dense_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/dropout_layer.t27", "t27b": "pass"}, - {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/layers/embedding_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/flatten_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/layernorm_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/maxpool2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/residual_connection.t27", "t27b": "pass"}, {"path": "specs/ml/loss/binary_crossentropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/loss/contrastive_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/cross_entropy_loss.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/loss/huber_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/kl_divergence.t27", "t27b": "pass"}, {"path": "specs/ml/loss/mse_loss.t27", "t27b": "pass"}, - {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adam.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/adamw.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/optimizer/lamb.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adam.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/adamw.t27", "t27b": "pass"}, + {"path": "specs/ml/optimizer/lamb.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/race_config.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/optimizer/rmsprop.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/sgd.t27", "t27b": "pass"}, @@ -577,7 +578,7 @@ {"path": "specs/ml/recurrent/rnn_cell.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/recurrent/self_attention.t27", "t27b": "pass"}, {"path": "specs/ml/recurrent/seq2seq.t27", "t27b": "pass"}, - {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/rl/advantage_estimator.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn.t27", "t27b": "pass"}, {"path": "specs/ml/rl/dqn_target_network.t27", "t27b": "pass"}, {"path": "specs/ml/rl/ppo_actor.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -609,7 +610,8 @@ {"path": "specs/nn/hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_backward.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_data.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/nn/ternary_ste.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/hslm_tokenizer.t27", "t27b": "pass"}, + {"path": "specs/nn/ternary_ste.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf1024.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf128.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf16.t27", "t27b": "pass"}, From b1abfb2230ad8005e9b98a1bf31667ef6b340ee7 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 11:56:11 +0700 Subject: [PATCH 3/8] t27b: @truncate by truncate_plan, which frees a ledger slot for #10380's two unlisted hslm specs (Closes #10837) #10380 merged specs/nn/hslm_tokenizer.t27 and specs/ml/hslm_corpus.t27 with no ledger rows, so every PR built on master got UNLISTED 2 from t27b-native-ratchet. hslm_corpus is blocked (cross-module use-resolution), and the ledger already sat at max_not_pass 139: its row would have been the 140th not-pass row, which reads as OVER CAP. The cap stays where it is. This change frees a slot instead. specs/tri/utils/utf8.t27 was blocked only by ExprCall(@truncate). @truncate now lowers through int_cast_plan.t27: - truncate_plan is plan in wrap mode, so it keeps the low bits and never traps; - a comptime operand folds with wrap; - a result type wider than the operand is refused (REFUSE_WIDER), as Zig refuses it; - from_destination now names @truncate, so `x = @truncate(e)` takes x's type. lower.rs is glue only, net 0 lines: int_cast takes a trunc flag that picks ic::truncate_plan and the @truncate site texts. gen/rust/tri/t27b/int_cast_plan.rs is regenerated by t27c gen-rust, and the seal is re-saved. Ledger: utf8 -> pass, hslm_tokenizer pass, hslm_corpus blocked. That leaves 138 blocked + 1 codegen = 139, at the cap, which is unchanged. The t27b-loop skill records four traps: the cap trap, the x86 lab without a qemu runner, the full /data volume, and old negative tests that flip. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27b-loop/SKILL.md | 7 ++++ .trinity/seals/t27b_T27bIntCastPlan.json | 22 +++++----- cli/t27b/src/lower.rs | 18 ++++---- docs/reports/t27b_expectations.json | 4 +- gen/rust/tri/t27b/int_cast_plan.rs | 30 +++++++++++++- specs/tri/t27b/int_cast_plan.t27 | 52 ++++++++++++++++++++++-- 6 files changed, 108 insertions(+), 25 deletions(-) diff --git a/.claude/skills/t27b-loop/SKILL.md b/.claude/skills/t27b-loop/SKILL.md index efc6e6e548..52827cb4e8 100644 --- a/.claude/skills/t27b-loop/SKILL.md +++ b/.claude/skills/t27b-loop/SKILL.md @@ -87,3 +87,10 @@ Append one line to the loop ledger memory (`t27b-loop-ledger.md`): the time, wha - **The lab drops a request whose head moved.** It also dropped requests it had not fetched yet; since #8320 those wait 30 min. - **An unrelated session can change shared `.git/config`.** Verify the setting; do not trust that it was restored. Pin per worktree instead of fighting over the shared value. - **Other sessions' spec rewrites can turn master's native ratchet red with UNEXPECTED FAILURE**, even when t27b did not regress (c7570433e and c9d36f314 on 2026-10-10). Before suspecting t27b, check with `git log` whether the spec changed. + +## Traps seen (2026-10-19, #10837) + +- **A blocked row cannot be added while the ledger sits at `max_not_pass`.** #10380 merged two specs with no rows. One of them is blocked, so its row would be the 140th not-pass row, and the ratchet then reports OVER CAP. Raising the cap is not allowed. Free a slot first: make t27b pass a blocked spec whose blocker is small. In #10837 that was `@truncate` in `specs/tri/utils/utf8.t27`, written as `truncate_plan` in `int_cast_plan.t27`. Count the slots before you file the issue: `grep -c '"t27b": "blocked"'` plus `codegen`, compared with the cap. +- **The fs-lab is x86_64, but the t27b JIT runs only on arm64.** Without `--runner "qemu-aarch64 -L /usr/aarch64-linux-gnu"`, `t27b corpus` reports every spec as `crash: exit 74`, and the ratchet then shows hundreds of UNEXPECTED FAILURE lines that t27b did not cause. Keep `--jobs 16`: at 32 or 40 the reference returns `spawn: Resource temporarily unavailable`, and those blocked rows poison the reference cache. Before reusing a cache, keep only its `pass` and `fail` rows. +- **The lab's `/data` volume can fill to 100%.** Cargo then fails with `failed to select a version ... locked to` rather than a disk error, because the index update in `CARGO_HOME=/data/cargo` cannot write. Set `CARGO_HOME` to a directory on `/root` (the overlay has free space) rather than deleting other lanes' trees. +- **Changing a plan module's behaviour also flips that module's old negative tests.** `assert !from_destination("@truncate")` failed once `@truncate` became a destination cast. Read the module's tests for the construct before you run them. diff --git a/.trinity/seals/t27b_T27bIntCastPlan.json b/.trinity/seals/t27b_T27bIntCastPlan.json index d5d811b5f8..2ce2dc0406 100644 --- a/.trinity/seals/t27b_T27bIntCastPlan.json +++ b/.trinity/seals/t27b_T27bIntCastPlan.json @@ -1,33 +1,33 @@ { - "built_by": "t27c-bootstrap@0.5.2+12c6d9140", + "built_by": "t27c-bootstrap@0.5.2+355d44a39", "closure": [ { "spec": "specs/tri/t27b/type_text.t27", - "spec_hash": "sha256:f474fe76cd84a302b46401c9a1b7db0b6a7282c0f0c8a462e4a9493415d0d915" + "spec_hash": "sha256:0c2ea71bc30d0865c2fb2206de7a0499e836bba7da6d1340bb1bef116823f473" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:dd275abc7d4d4b4b1c66d4ba4cb4f68dc77df14aab682b99befb25b5cffcf1c4", - "gen_hash_rust": "sha256:3b9360c9528f90657a1e23bdaeb0e456b07af742a9b7af6d52972b0c4771fbfb", - "gen_hash_verilog": "sha256:7300347f1653e23aad8c9407fdfe45e71114e20a692c8604f2c9a7299a37e8d1", - "gen_hash_zig": "sha256:800d462d250ddc2be3bcc67882b83421d7b897c4f6d2c58a4a0d59b371e6b34f", + "gen_hash_c": "sha256:edc22f81f999522d6399175b14c25fc32903dc8d78159fcf0b665579c8e5743a", + "gen_hash_rust": "sha256:a21600fc43a214f2d011c4330dda78f0b3ad2ac7201a42280ec5e6ccef99c848", + "gen_hash_verilog": "sha256:ae41776f84b010c488e1fa4b736674e0963f874ad9619971f661382ca517e598", + "gen_hash_zig": "sha256:39ba7667eca666138a3ec170d6d17460e840fcfb53a941ab9dba5e27ba88cb44", "module": "T27bIntCastPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-10T19:08:25Z", + "sealed_at": "2026-10-19T04:30:30Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:bd544b0bb2aaf79e600e974b91eb5c25daeecbb09ee1e4f1d3fd0b3cd3d91946", + "spec_hash": "sha256:9ec7b05df748df422d374d5adb6b23f5f333a84ec30abc2545b2d73b7a2dbf56", "spec_path": "specs/tri/t27b/int_cast_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 13, - "total": 13, + "passed": 14, + "total": 14, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/cli/t27b/src/lower.rs b/cli/t27b/src/lower.rs index 2571cd240b..ab8e3fb96d 100644 --- a/cli/t27b/src/lower.rs +++ b/cli/t27b/src/lower.rs @@ -3712,8 +3712,8 @@ impl<'a> Lower<'a> { } /// `@intCast(x)` with integer result type `ty` from the context (`@as`, a typed binding, a parameter, a - /// return). What is refused and what the rest lowers to: specs/tri/t27b/int_cast_plan.t27. - fn int_cast(&mut self, n: &Node, ty: Ty) -> R { + /// return), or with `trunc` `@truncate` (#10837). What is refused and what the rest lowers to: int_cast_plan.t27. + fn int_cast(&mut self, n: &Node, ty: Ty, trunc: bool) -> R { self.see(n); let shape = ic::shape(n.children.len(), ty.is_int()); let v = if shape == ic::GO { self.expr(&n.children[0])? } else { Val::Poison }; @@ -3723,20 +3723,20 @@ impl<'a> Lower<'a> { Val::Ct(c) => (ic::K_CT, ty, Some(*c)), _ => (ic::K_OTHER, ty, None), }; - let (fits, wrap) = (c.is_some_and(|c| ty.fits(c)), self.mode == OverflowMode::Wrap); - let plan = |k| ic::plan(k, from.bits(), from.signed(), ty.bits(), ty.signed(), c.is_some(), fits, wrap); + let (fits, wrap, site) = (c.is_some_and(|c| ty.fits(c)), self.mode == OverflowMode::Wrap, if trunc { ic::TRUNC_SITE } else { ic::SITE }); + let plan = |k| if trunc { ic::truncate_plan(k, from.bits(), from.signed(), ty.bits(), ty.signed(), c.is_some()) } else { ic::plan(k, from.bits(), from.signed(), ty.bits(), ty.signed(), c.is_some(), fits, wrap) }; let kind = match (if shape == ic::GO { plan(k) } else { shape }, v) { (ic::KEEP, v) => return Ok(v), (ic::FOLD | ic::FOLD_WRAP, _) => ExprKind::Const(ty.wrap(c.unwrap())), // a FOLD fits `ty`: wrap keeps it (ic::WIDEN, Val::E(e)) => ExprKind::Widen(Box::new(e)), (a, Val::E(e)) if ic::is_cast(a) => { - let what = format!("{} {} to {}", ic::SITE, from.name(), ty.name()); + let what = format!("{} {} to {}", site, from.name(), ty.name()); ExprKind::Cast { arg: Box::new(e), site: if ic::traps(a) { self.site(TrapKind::Cast, what, ty) } else { 0 } } } (a, v) => { let d = match &v { Val::E(e) => e.ty.name().into(), Val::Ct(c) => c.to_string(), _ => self.val_desc(&v) }; let d = if v.is_poison() { format!("{} operands", n.children.len()) } else { d }; - return self.reject(ic::WHAT, format!("`@intCast` of {} to {}: {}", d, ty.name(), ic::why(a))); + return self.reject(if trunc { ic::TRUNC_WHAT } else { ic::WHAT }, format!("`{}` of {} to {}: {}", site, d, ty.name(), ic::why(a))); } }; Ok(Val::E(Expr { ty, kind })) @@ -3744,7 +3744,7 @@ impl<'a> Lower<'a> { /// An index or a slice bound, whose result type in Zig is `ic::INDEX_TYPE`: an `@intCast` there converts to it. fn usize_operand(&mut self, n: &Node) -> R { - if n.kind == NodeKind::ExprCall && n.name == ic::SITE { self.int_cast(n, Ty::from_name(ic::INDEX_TYPE).unwrap_or(Ty::U64)) } else { self.expr(n) } + if n.kind == NodeKind::ExprCall && n.name == ic::SITE { self.int_cast(n, Ty::from_name(ic::INDEX_TYPE).unwrap_or(Ty::U64), false) } else { self.expr(n) } } /// `n` folded as Zig folds an expression that names a module constant of an integer type wider than 64 bits: @@ -5223,8 +5223,8 @@ impl<'a> Lower<'a> { if let (NodeKind::ExprCall, "@floatCast", LTy::S(ty)) = (&n.kind, n.name.as_str(), want) { return self.float_cast_call(n, *ty); } - if let (NodeKind::ExprCall, "@intCast", LTy::S(ty)) = (&n.kind, n.name.as_str(), want) { - return self.int_cast(n, *ty); + if let (NodeKind::ExprCall, "@intCast" | "@truncate", LTy::S(ty)) = (&n.kind, n.name.as_str(), want) { + return self.int_cast(n, *ty, n.name == ic::TRUNC_SITE); } if let (NodeKind::ExprCall, "@bitCast", LTy::S(ty)) = (&n.kind, n.name.as_str(), want) { return self.bit_cast(n, *ty); } if let (NodeKind::ExprCall, "@intFromEnum", LTy::S(ty)) = (&n.kind, n.name.as_str(), want) { diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index c65d3ed1de..d4f4374d20 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -543,6 +543,7 @@ {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/ml/hslm_corpus.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/igla_champion_capsule.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/layers/avgpool2d_layer.t27", "t27b": "pass"}, {"path": "specs/ml/layers/batchnorm_layer.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -609,6 +610,7 @@ {"path": "specs/nn/hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_backward.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/nn/hslm_data.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, + {"path": "specs/nn/hslm_tokenizer.t27", "t27b": "pass"}, {"path": "specs/nn/ternary_ste.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/numeric/bnf1024.t27", "t27b": "pass"}, {"path": "specs/numeric/bnf128.t27", "t27b": "pass"}, @@ -1977,7 +1979,7 @@ {"path": "specs/tri/utils/terminal.t27", "t27b": "blocked", "blocker": "ExprCall(@import)", "reason": "unimplemented"}, {"path": "specs/tri/utils/text.t27", "t27b": "pass"}, {"path": "specs/tri/utils/time.t27", "t27b": "blocked", "blocker": "ExprCall(@divFloor)", "reason": "unimplemented"}, - {"path": "specs/tri/utils/utf8.t27", "t27b": "blocked", "blocker": "ExprCall(@truncate)", "reason": "unimplemented"}, + {"path": "specs/tri/utils/utf8.t27", "t27b": "pass"}, {"path": "specs/tri/utils/version.t27", "t27b": "pass"}, {"path": "specs/trinet/eth_top_ax7203.t27", "t27b": "pass"}, {"path": "specs/trinet/etx.t27", "t27b": "pass"}, diff --git a/gen/rust/tri/t27b/int_cast_plan.rs b/gen/rust/tri/t27b/int_cast_plan.rs index cf98dc83c5..249df38d54 100644 --- a/gen/rust/tri/t27b/int_cast_plan.rs +++ b/gen/rust/tri/t27b/int_cast_plan.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 13 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 14 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -32,10 +32,16 @@ pub const CHECK: u8 = 9; pub const TRUNCATE: u8 = 10; +pub const REFUSE_WIDER: u8 = 11; + pub const WHAT: &'static str = "ExprCall(@intCast)"; pub const SITE: &'static str = "@intCast"; +pub const TRUNC_WHAT: &'static str = "ExprCall(@truncate)"; + +pub const TRUNC_SITE: &'static str = "@truncate"; + pub const INDEX_TYPE: &'static str = "usize"; pub const INDEX_BITS: u32 = 64; @@ -46,6 +52,9 @@ pub fn from_destination(name: &[u8]) -> bool { if (same(name, SITE) || same(name, "@bitCast")) { return true; } + if same(name, TRUNC_SITE) { + return true; + } return ((same(name, "@floatFromInt") || same(name, "@intFromFloat")) || same(name, "@floatCast")); } @@ -97,6 +106,19 @@ pub fn plan(k: u8, from_bits: u32, from_signed: bool, to_bits: u32, to_signed: b return CHECK; } +pub fn truncate_plan(k: u8, from_bits: u32, from_signed: bool, to_bits: u32, to_signed: bool, is_const: bool) -> u8 { + if (k == K_OTHER) { + return REFUSE_OPERAND; + } + if (k == K_CT) { + return FOLD_WRAP; + } + if (to_bits > from_bits) { + return REFUSE_WIDER; + } + return plan(k, from_bits, from_signed, to_bits, to_signed, is_const, false, true); +} + pub fn is_cast(act: u8) -> bool { return ((act == CHECK) || (act == TRUNCATE)); } @@ -106,6 +128,9 @@ pub fn traps(act: u8) -> bool { } pub fn refuses(act: u8) -> bool { + if (act == REFUSE_WIDER) { + return true; + } return ((act >= REFUSE_ARITY) && (act <= REFUSE_LITERAL)); } @@ -122,6 +147,9 @@ pub fn why(act: u8) -> &'static str { if (act == REFUSE_LITERAL) { return "the literal does not fit the result type"; } + if (act == REFUSE_WIDER) { + return "the result type is wider than the operand"; + } return ""; } diff --git a/specs/tri/t27b/int_cast_plan.t27 b/specs/tri/t27b/int_cast_plan.t27 index 32911f8003..f0c1645df2 100644 --- a/specs/tri/t27b/int_cast_plan.t27 +++ b/specs/tri/t27b/int_cast_plan.t27 @@ -62,11 +62,19 @@ pub const KEEP : u8 = 7; pub const WIDEN : u8 = 8; pub const CHECK : u8 = 9; pub const TRUNCATE : u8 = 10; +pub const REFUSE_WIDER : u8 = 11; /// The construct every refusal names, and the word a checked Cast's trap site starts with. pub const WHAT : str = "ExprCall(@intCast)"; pub const SITE : str = "@intCast"; +/// `@truncate(x)` (#10837, specs/tri/utils/utf8.t27's `@as(u8, @truncate(cp))`): Zig 0.16 keeps the low bits +/// of x in the result type, in either overflow mode, and never traps. So it is `plan` in wrap mode: a +/// conversion that does not widen truncates, a constant folds to its truncated value, and a comptime_int +/// literal folds truncated too. A result type wider than the operand is a Zig compile error (REFUSE_WIDER). +pub const TRUNC_WHAT : str = "ExprCall(@truncate)"; +pub const TRUNC_SITE : str = "@truncate"; + /// The result type an index or a slice bound gives `@intCast` (`POW2[@intCast(e - 1)]`): Zig's `usize`, which /// is 64 bits and unsigned on every target t27b runs on. pub const INDEX_TYPE : str = "usize"; @@ -76,10 +84,12 @@ pub const INDEX_SIGNED : bool = false; // Reuse type_text::same rather than another byte-comparison body (#8756). /// Does a plain assignment `x = f(..)` to a scalar var give the builtin `f` x's type as its result type? Each -/// builtin here takes its type from the result location: `@intCast` (by `plan`), `@floatFromInt`, -/// `@intFromFloat`, `@floatCast` and `@bitCast`. Any other right-hand side is evaluated on its own. +/// builtin here takes its type from the result location: `@intCast` (by `plan`), `@truncate` (by +/// `truncate_plan`), `@floatFromInt`, `@intFromFloat`, `@floatCast` and `@bitCast`. Any other right-hand side +/// is evaluated on its own. pub fn from_destination(name: []const u8) -> bool { if (type_text::same(name, SITE) or type_text::same(name, "@bitCast")) { return true; } + if (type_text::same(name, TRUNC_SITE)) { return true; } return type_text::same(name, "@floatFromInt") or type_text::same(name, "@intFromFloat") or type_text::same(name, "@floatCast"); } @@ -118,6 +128,15 @@ pub fn plan(k: u8, from_bits: u32, from_signed: bool, to_bits: u32, to_signed: b return CHECK; } +/// The decision for `@truncate`, once `shape` said GO: `plan` in wrap mode, whatever `--overflow` says, after +/// two refusals of its own. A literal never needs to fit: it folds to its low bits. +pub fn truncate_plan(k: u8, from_bits: u32, from_signed: bool, to_bits: u32, to_signed: bool, is_const: bool) -> u8 { + if (k == K_OTHER) { return REFUSE_OPERAND; } + if (k == K_CT) { return FOLD_WRAP; } + if (to_bits > from_bits) { return REFUSE_WIDER; } + return plan(k, from_bits, from_signed, to_bits, to_signed, is_const, false, true); +} + /// Does the answer build a Cast node, and does that node trap? pub fn is_cast(act: u8) -> bool { return act == CHECK or act == TRUNCATE; @@ -129,6 +148,7 @@ pub fn traps(act: u8) -> bool { /// Is the answer a refusal? Its words, after the operand and the result type, are `why`. pub fn refuses(act: u8) -> bool { + if (act == REFUSE_WIDER) { return true; } return act >= REFUSE_ARITY and act <= REFUSE_LITERAL; } @@ -137,6 +157,7 @@ pub fn why(act: u8) -> str { if (act == REFUSE_RESULT) { return "the result type is not an integer"; } if (act == REFUSE_OPERAND) { return "the operand is not an integer"; } if (act == REFUSE_LITERAL) { return "the literal does not fit the result type"; } + if (act == REFUSE_WIDER) { return "the result type is wider than the operand"; } return ""; } @@ -277,6 +298,31 @@ test only_check_and_truncate_build_a_cast_and_only_check_traps { assert !refuses(TRUNCATE); } +// utf8.t27's `@as(u8, @truncate((cp >> 6) | 0xC0))`: a u21 or a u32 to u8 truncates with no trap site, in +// default mode as well as wrap mode, where `@intCast` of the same operand is checked. +test truncate_keeps_the_low_bits_in_either_mode_and_never_traps { + assert truncate_plan(K_INT, 32, false, 8, false, false) == TRUNCATE; + assert truncate_plan(K_INT, 21, false, 8, false, false) == TRUNCATE; + assert truncate_plan(K_INT, 64, true, 8, false, false) == TRUNCATE; + assert truncate_plan(K_INT, 32, false, 32, true, false) == TRUNCATE; + assert plan(K_INT, 32, false, 8, false, false, false, false) == CHECK; + assert traps(truncate_plan(K_INT, 32, false, 8, false, false)) == false; + assert is_cast(truncate_plan(K_INT, 64, true, 16, true, false)); + assert truncate_plan(K_INT, 32, false, 8, false, true) == FOLD_WRAP; + assert truncate_plan(K_CT, 8, false, 8, false, true) == FOLD_WRAP; + assert truncate_plan(K_INT, 8, false, 8, false, false) == KEEP; + assert truncate_plan(K_INT, 8, true, 8, true, true) == FOLD; + assert truncate_plan(K_INT, 8, false, 16, false, false) == REFUSE_WIDER; + assert truncate_plan(K_INT, 8, false, 16, true, false) == REFUSE_WIDER; + assert truncate_plan(K_INT, 8, false, 9, false, false) == REFUSE_WIDER; + assert truncate_plan(K_INT, 9, false, 8, false, false) == TRUNCATE; + assert truncate_plan(K_OTHER, 8, false, 8, false, false) == REFUSE_OPERAND; + assert refuses(REFUSE_WIDER); + assert std.mem.eql(u8, why(REFUSE_WIDER), "the result type is wider than the operand"); + assert from_destination(TRUNC_SITE); + assert std.mem.eql(u8, TRUNC_WHAT, "ExprCall(@truncate)"); +} + // ocp_mx.t27's indexes: an i64 or an i32 is checked and a negative one traps; a u8 or a u32 widens; a usize // is kept; a constant folds when it fits; a literal that does not fit (a negative one) is refused. test an_index_converts_its_cast_to_a_usize { @@ -314,7 +360,7 @@ test an_assignment_gives_its_destination_type { assert from_destination("@intFromFloat"); assert from_destination("@floatCast"); assert from_destination("@bitCast"); - assert !from_destination("@truncate"); + assert from_destination("@truncate"); assert !from_destination("@as"); assert !from_destination("intCast"); assert !from_destination("@intCastx"); From 2fcc7ca1e1f4e43994a9cb507da0abe8670b6bf9 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 11:59:55 +0700 Subject: [PATCH 4/8] ledger: rows for six specs master merged unlisted, all pass under t27b (Refs #10837) mos_psp103, mos_psp103_data, sar_switch (specs/analog), idea_factory, particle_ds, petal_menu (specs/t27f) landed on master with no rows in docs/reports/t27b_expectations.json. t27b passes all six on the lab (qemu-aarch64 corpus on 535334d2a), so they cost no not-pass slot: the ledger stays at 139 not-pass rows, the cap. Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 117de4dada..484b4e395c 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -46,9 +46,12 @@ {"path": "specs/analog/bench.t27", "t27b": "pass"}, {"path": "specs/analog/linear.t27", "t27b": "pass"}, {"path": "specs/analog/linear_zoh.t27", "t27b": "blocked", "blocker": "ExprCall(@sin)", "reason": "unimplemented"}, + {"path": "specs/analog/mos_psp103.t27", "t27b": "pass"}, + {"path": "specs/analog/mos_psp103_data.t27", "t27b": "pass"}, {"path": "specs/analog/mos_table.t27", "t27b": "pass"}, {"path": "specs/analog/sar_cdac.t27", "t27b": "pass"}, {"path": "specs/analog/sar_comparator.t27", "t27b": "pass"}, + {"path": "specs/analog/sar_switch.t27", "t27b": "pass"}, {"path": "specs/api/c_abi.t27", "t27b": "pass"}, {"path": "specs/api/tri_api_context.t27", "t27b": "pass"}, {"path": "specs/api/tri_api_loop.t27", "t27b": "pass"}, @@ -1509,11 +1512,14 @@ {"path": "specs/t27f/frame_shard.t27", "t27b": "blocked", "blocker": "type [N]T", "reason": "unimplemented"}, {"path": "specs/t27f/frontend.t27", "t27b": "pass"}, {"path": "specs/t27f/hot.t27", "t27b": "pass"}, + {"path": "specs/t27f/idea_factory.t27", "t27b": "pass"}, {"path": "specs/t27f/lesson_reel.t27", "t27b": "blocked", "blocker": "type [N]T", "reason": "unimplemented"}, {"path": "specs/t27f/outfit_atlas.t27", "t27b": "pass"}, + {"path": "specs/t27f/particle_ds.t27", "t27b": "pass"}, {"path": "specs/t27f/particle_engine.t27", "t27b": "blocked", "blocker": "ExprCall(@sin)", "reason": "unimplemented"}, {"path": "specs/t27f/particle_field.t27", "t27b": "pass"}, {"path": "specs/t27f/pbr_sphere.t27", "t27b": "pass"}, + {"path": "specs/t27f/petal_menu.t27", "t27b": "pass"}, {"path": "specs/t27f/quest.t27", "t27b": "pass"}, {"path": "specs/t27f/reel.t27", "t27b": "pass"}, {"path": "specs/t27f/reel_beats.t27", "t27b": "pass"}, From 7ab095a45d49438ab4319bd33a2c9c67d9d99f39 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:06:56 +0700 Subject: [PATCH 5/8] t27b-loop skill: three traps from consolidating #10853 into #10862 (Refs #10837) Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27b-loop/SKILL.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.claude/skills/t27b-loop/SKILL.md b/.claude/skills/t27b-loop/SKILL.md index 52827cb4e8..7d52859fb1 100644 --- a/.claude/skills/t27b-loop/SKILL.md +++ b/.claude/skills/t27b-loop/SKILL.md @@ -94,3 +94,6 @@ Append one line to the loop ledger memory (`t27b-loop-ledger.md`): the time, wha - **The fs-lab is x86_64, but the t27b JIT runs only on arm64.** Without `--runner "qemu-aarch64 -L /usr/aarch64-linux-gnu"`, `t27b corpus` reports every spec as `crash: exit 74`, and the ratchet then shows hundreds of UNEXPECTED FAILURE lines that t27b did not cause. Keep `--jobs 16`: at 32 or 40 the reference returns `spawn: Resource temporarily unavailable`, and those blocked rows poison the reference cache. Before reusing a cache, keep only its `pass` and `fail` rows. - **The lab's `/data` volume can fill to 100%.** Cargo then fails with `failed to select a version ... locked to` rather than a disk error, because the index update in `CARGO_HOME=/data/cargo` cannot write. Set `CARGO_HOME` to a directory on `/root` (the overlay has free space) rather than deleting other lanes' trees. - **Changing a plan module's behaviour also flips that module's old negative tests.** `assert !from_destination("@truncate")` failed once `@truncate` became a destination cast. Read the module's tests for the construct before you run them. +- **Two sessions can take the same ledger issue.** #10853 (bare `sqrt`) and #10862 (`@truncate`) both closed #10837, and each stayed red alone: one lacked the rows the other had, and the other lacked the slots. Before you open a PR for an issue, run `gh pr list --search ""`. If a sibling PR exists, merge its branch into yours (fetch it as `refs/heads/`), and say "Carries #N" in the body. +- **A cap lowered to the exact count leaves no room for rows other PRs still need.** #10853 set `max_not_pass` to 129. eval_hslm and ngram_hslm still needed two blocked rows, so the combined PR set 130. That is still below master's 139, so it does not raise the cap. Recount after every merge: `blocked` + `codegen` = cap. +- **The Mac hook refuses any Bash text that contains the words of a test command**, even inside a heredoc for a PR body. Write PR bodies with the Write tool. From 8f043ad0b0f625eab99d29822b500658bbe61b8f Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:18:23 +0700 Subject: [PATCH 6/8] t27b ledger: pass rows for analog/sar_logic and policy/secret_gate (Refs #10837) Master added both specs after this branch's last ledger update; the native ratchet on 7ab095a45 reported them UNLISTED, with every other count at 0. Both pass under t27b, so the not-pass count stays 130. Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index a26b758d6a..157d642f9f 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -51,6 +51,7 @@ {"path": "specs/analog/mos_table.t27", "t27b": "pass"}, {"path": "specs/analog/sar_cdac.t27", "t27b": "pass"}, {"path": "specs/analog/sar_comparator.t27", "t27b": "pass"}, + {"path": "specs/analog/sar_logic.t27", "t27b": "pass"}, {"path": "specs/analog/sar_switch.t27", "t27b": "pass"}, {"path": "specs/api/c_abi.t27", "t27b": "pass"}, {"path": "specs/api/tri_api_context.t27", "t27b": "pass"}, @@ -692,6 +693,7 @@ {"path": "specs/policy/own_language.t27", "t27b": "pass"}, {"path": "specs/policy/pr_budget.t27", "t27b": "pass"}, {"path": "specs/policy/required_contexts.t27", "t27b": "pass"}, + {"path": "specs/policy/secret_gate.t27", "t27b": "pass"}, {"path": "specs/policy/text_lines.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bitnet_irq.t27", "t27b": "pass"}, {"path": "specs/port/bootstrap/src/bridge.t27", "t27b": "pass"}, From 885584c0f5e00457da03be897a47f99030fc8846 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:22:01 +0700 Subject: [PATCH 7/8] t27b ledger: cap 129 as master has it; eval_hslm and ngram_hslm go back to #10858 (Refs #10837) #10853 merged on its own with max_not_pass 129, the exact count, so master has no room for eval_hslm and ngram_hslm. With @truncate freeing utf8, keeping both rows would need 130 and raise master's cap. This branch drops them and takes master's 129: 128 not-pass rows, one slot free for #10858. The cap line now matches master's, so the branch merges without conflict. Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 157d642f9f..48026f85f1 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -12,7 +12,7 @@ "reference-bug": "the reference passes it for the wrong reason; t27b is right not to", "n/a": "outside what t27b is for (say why in the PR that sets it)" }, - "max_not_pass": 130, + "max_not_pass": 129, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -548,7 +548,6 @@ {"path": "specs/ml/activation/softmax.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/activation/tanh_activation.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, - {"path": "specs/ml/eval_hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/hslm_corpus.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, @@ -569,7 +568,6 @@ {"path": "specs/ml/loss/huber_loss.t27", "t27b": "pass"}, {"path": "specs/ml/loss/kl_divergence.t27", "t27b": "pass"}, {"path": "specs/ml/loss/mse_loss.t27", "t27b": "pass"}, - {"path": "specs/ml/ngram_hslm.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/optimizer/adagrad.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/adam.t27", "t27b": "pass"}, {"path": "specs/ml/optimizer/adamw.t27", "t27b": "pass"}, From 330dd0aef6ef9fe476aa1f3f3b665e1745d6e43b Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:24:03 +0700 Subject: [PATCH 8/8] t27b-loop skill: a master merge touching lefthook.yml is refused; match master and check merge-tree (Refs #10837) Co-Authored-By: Claude Opus 5.5 --- .claude/skills/t27b-loop/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/skills/t27b-loop/SKILL.md b/.claude/skills/t27b-loop/SKILL.md index 7d52859fb1..ef9506f488 100644 --- a/.claude/skills/t27b-loop/SKILL.md +++ b/.claude/skills/t27b-loop/SKILL.md @@ -97,3 +97,4 @@ Append one line to the loop ledger memory (`t27b-loop-ledger.md`): the time, wha - **Two sessions can take the same ledger issue.** #10853 (bare `sqrt`) and #10862 (`@truncate`) both closed #10837, and each stayed red alone: one lacked the rows the other had, and the other lacked the slots. Before you open a PR for an issue, run `gh pr list --search ""`. If a sibling PR exists, merge its branch into yours (fetch it as `refs/heads/`), and say "Carries #N" in the body. - **A cap lowered to the exact count leaves no room for rows other PRs still need.** #10853 set `max_not_pass` to 129. eval_hslm and ngram_hslm still needed two blocked rows, so the combined PR set 130. That is still below master's 139, so it does not raise the cap. Recount after every merge: `blocked` + `codegen` = cap. - **The Mac hook refuses any Bash text that contains the words of a test command**, even inside a heredoc for a PR body. Write PR bodies with the Write tool. +- **A merge of master that changes `lefthook.yml` is refused by the own-language pre-commit hook**, because the gate file is owner-only and the merge commit carries master's edit to it. Never `--no-verify`. Instead, `git merge --abort`, make the branch's side of each conflicting file equal master's (for the ledger: master's rows plus only the lane's rows) in a normal commit, then check `git merge-tree --write-tree HEAD FETCH_HEAD`. rc 0 means GitHub can squash it with no conflict (#10862).