From 718072f9cf8e37c178d8e514d4e47380d79011e3 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 19 Oct 2026 12:13:04 +0200 Subject: [PATCH 1/5] ci(tools): section-first-fail names a failed section's first failing line (PMAT-5740, Refs #5740) Rust port of the idea in aprender-ci-tools (fat_driver.py is over the complexity hook and stays untouched). 7 tests written RED first; mutations on the ::error match and the continue_on_error skip each fail tests, restored GREEN. One ci.yml step (if: failure()) in x86-main prints it; needs non-author sign-off. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 5 + README.md | 4 +- .../ci-fat-driver-first-failing-line-v1.yaml | 71 +++++++ crates/aprender-ci-tools/src/lib.rs | 1 + crates/aprender-ci-tools/src/main.rs | 22 +- .../src/section_first_fail.rs | 195 ++++++++++++++++++ docs/roadmaps/entries/PMAT-5740.yaml | 19 ++ 7 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 contracts/ci-fat-driver-first-failing-line-v1.yaml create mode 100644 crates/aprender-ci-tools/src/section_first_fail.rs create mode 100644 docs/roadmaps/entries/PMAT-5740.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0f3850566..5cc69f715c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -264,6 +264,11 @@ jobs: # the teardown below keeps always() and still reaps the containers. if: ${{ !cancelled() }} run: python3 scripts/ci/fat_driver.py wait --results "$RUNNER_TEMP/fat/results.json" + # PMAT-5740 (#5740): a red section names its first failing line in this job log; + # the driver prints only the result, its cause sits in a ::group a cut-off log loses. + - name: First failing line of each failed section + if: ${{ failure() }} + run: cargo run -q -p aprender-ci-tools -- section-first-fail --results "$RUNNER_TEMP/fat/results.json" # #4948: a cancelled run never reaches a section's own container removal, # and its fat--
container then sits on `tail -f /dev/null` # for days. always() runs on cancel too; on the passing path the driver diff --git a/README.md b/README.md index 027a6b2a7d..191f91b659 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ publishing — all backed by YAML provable contracts that fail CI on drift. | Metric | Count | Source of truth | |-------:|------:|---| | Workspace crates | **81** workspace crates | `cargo metadata --no-deps` (NOT `ls crates/` — 4 are `exclude`d, 1 has no Cargo.toml) | -| Provable contracts | **1984** provable contracts | `contracts/census.json` `.n_files` — the set `pv lint` walks (`pv census`, ONT-001 ONT-1; regenerated by `make contracts`, written by `make readme-sync`, guarded by `scripts/check_readme_claims.sh`) | +| Provable contracts | **1985** provable contracts | `contracts/census.json` `.n_files` — the set `pv lint` walks (`pv census`, ONT-001 ONT-1; regenerated by `make contracts`, written by `make readme-sync`, guarded by `scripts/check_readme_claims.sh`) | | CLI commands | **113** CLI commands | `contracts/apr-cli-commands-v1.yaml` §`commands` (parse the list; `apr --help` also lists `help` itself, and `grep -c '^ - name:'` gives 119 — other same-indent `name:` keys exist in the file) | | Book CLI chapters | **114** chapters | `ls book/src/cli/*.md` | | Book lib chapters | **73** chapters | `ls book/src/lib/*.md` (parity with `pub mod`) | @@ -403,7 +403,7 @@ falsification_tests: prediction: apr validate bad-model.apr exits non-zero ``` -The tree carries 1984 contracts across inference, training, quantization, attention, FFN, +The tree carries 1985 contracts across inference, training, quantization, attention, FFN, tokenization, model formats, CLI safety — and this README itself. ## Migration from old crates diff --git a/contracts/ci-fat-driver-first-failing-line-v1.yaml b/contracts/ci-fat-driver-first-failing-line-v1.yaml new file mode 100644 index 0000000000..e49f2d696f --- /dev/null +++ b/contracts/ci-fat-driver-first-failing-line-v1.yaml @@ -0,0 +1,71 @@ +# ci-fat-driver-first-failing-line-v1 — PMAT-5740 (GH #5740): a failed x86-main +# section names its first failing line in the job log. +# +# Before: the driver printed `section N: failure in Ns` and `::error::section N: +# failure`; the cause sat in the section's own log, dumped only inside a ::group. +# A red whose group was cut off was not measured. +# +# Verification level: +# * L1 — `aprender-ci-tools section-first-fail` exists (crates/aprender-ci-tools/src/section_first_fail.rs). +# * L2 — falsified by 7 Rust tests (section_first_fail::tests); mutations `starts_with("::error")` +# -> `"::errorX"` (3 tests fail) and dropping `|| coe` (1 fails), each restored GREEN. +# * L3/L4 — NOT APPLICABLE: a CI log-reporting rule, not a kernel. +# * WHAT BLOCKS: unchanged. One ci.yml step (if: failure()) prints a log line; the driver, +# scripts/ci/fat_driver.py, is not edited (its complexity is already over the hook). +name: ci-fat-driver-first-failing-line +version: "1.0.0" +scope: > + What the section driver prints into the job log when a section ends in any + result other than success or skipped. Out of scope: the verdict rule and the + full per-section log dump. +status: active + +metadata: + kind: pattern + version: "1.0.0" + created: '2026-10-19' + last_modified: '2026-10-19' + author: PAIML Engineering + references: + - 'crates/aprender-ci-tools/src/section_first_fail.rs first_failing_line, report, report_results' + - '.github/workflows/ci.yml x86-main step First failing line of each failed section' + description: > + After the driver's wait step fails, a step prints one `::error::section N first failing line: …` + beside its result line: the first `::error` line of its log, else the first + panic or `error:` line. A clean or missing log prints nothing. + +equations: + failed_section_names_its_cause: + formula: | + result(n) not in {success, skipped} and log(n) has a failing line => job log has "section n first failing line: " + the line is the first ::error line, else the first panic / error: line + domain: 'one section log' + codomain: 'a single line of at most 300 chars, or none' + invariants: + - 'first_failing_line prefers the first ::error line over a later or earlier panic line' + - 'a clean or absent log yields the empty string' + preconditions: + - 'the section has ended' + postconditions: + - 'the verdict and results output are unchanged' + +proof_obligations: + - id: CFF-OB-001 + statement: "A failed section's first failing line is printed into the job log." + discharged_by: falsification_tests[0] + +falsification_tests: + - id: CFF-F-001 + rule: the first failing line of a failed section reaches the job log + prediction: >- + On a planted log, ::error:: lines yield the first one, a bare panic yields + that line, a clean log and a missing log yield none; results mode reads only + failed, blocking sections (7 tests). + test: 'cargo test -p aprender-ci-tools --lib section_first_fail' + if_fails: >- + a red whose ::group dump is cut off names no cause and is not measured + mutation: >- + starts_with("::error") -> "::errorX" fails 3 tests; dropping the continue_on_error skip fails 1; each restored GREEN (2026-10-19). + +non_goals: + - "Changing which sections block, or the per-section log dump." diff --git a/crates/aprender-ci-tools/src/lib.rs b/crates/aprender-ci-tools/src/lib.rs index 80eea0ebc0..0e18432f32 100644 --- a/crates/aprender-ci-tools/src/lib.rs +++ b/crates/aprender-ci-tools/src/lib.rs @@ -15,6 +15,7 @@ pub mod package_include_diff; pub mod publishable_crates; pub mod pyjson; pub mod pystr; +pub mod section_first_fail; pub mod tarball_build_errors; pub mod tarball_shrink_report; pub mod tarball_workspace; diff --git a/crates/aprender-ci-tools/src/main.rs b/crates/aprender-ci-tools/src/main.rs index dfd9464ad0..c2b5455974 100644 --- a/crates/aprender-ci-tools/src/main.rs +++ b/crates/aprender-ci-tools/src/main.rs @@ -2,8 +2,8 @@ use aprender_ci_tools::{ coverage_report_scope, dag_status, git_patch_id, ladder_cells_produce, llama_fit_verdict, - nextest_fail_fast, package_include_diff, publishable_crates, tarball_build_errors, - tarball_shrink_report, tarball_workspace, + nextest_fail_fast, package_include_diff, publishable_crates, section_first_fail, + tarball_build_errors, tarball_shrink_report, tarball_workspace, }; use clap::{ArgGroup, Parser, Subcommand}; use std::io::{Read, Write}; @@ -113,6 +113,17 @@ enum Cmd { /// The nextest config, e.g. .config/nextest.toml. toml: PathBuf, }, + /// `::error::section first failing line: ` for every section log that has + /// one (PMAT-5740): the first `::error` line, else the first panic or `error:` line. + /// Always exits 0; a log with no failing line prints nothing. + SectionFirstFail { + /// The driver's results.json: read only its failed, blocking sections' logs, which sit + /// beside it. Exit 1 when it cannot be read. + #[arg(long)] + results: Option, + /// Section logs, e.g. "$RUNNER_TEMP"/fat/*.log. + logs: Vec, + }, } fn cargo_metadata() -> Result { @@ -222,6 +233,13 @@ fn run(cmd: Cmd) -> Result { Cmd::LadderCellsProduce { cmd } => { ladder_cells_produce::run(&cmd).map_err(|(code, reason)| (String::new(), code, reason)) } + Cmd::SectionFirstFail { results, logs } => { + let mut out = section_first_fail::report(&logs); + if let Some(r) = results { + out.push_str(§ion_first_fail::report_results(&r).map_err(nothing_printed)?); + } + Ok(out) + } Cmd::NextestFailFast { reader, toml } => { // clap's value_parser admits only the two names parse() knows. let reader = nextest_fail_fast::Reader::parse(&reader) diff --git a/crates/aprender-ci-tools/src/section_first_fail.rs b/crates/aprender-ci-tools/src/section_first_fail.rs new file mode 100644 index 0000000000..6a0029394d --- /dev/null +++ b/crates/aprender-ci-tools/src/section_first_fail.rs @@ -0,0 +1,195 @@ +//! `section-first-fail LOG...` (PMAT-5740, #5740): name the first failing line of each +//! section log the x86-main driver wrote, as one `::error::` line per log. The driver +//! prints only `section N: failure`; the cause sits in the section's own log, dumped in a +//! `::group` that a cut-off job log loses. + +use std::path::Path; + +/// The first `::error` line of the log, else the first panic or `error:` line, else "". +/// An unreadable log has none. +pub fn first_failing_line(path: &Path) -> String { + let Ok(bytes) = std::fs::read(path) else { + return String::new(); + }; + let text = String::from_utf8_lossy(&bytes); + let lines: Vec<&str> = text.lines().collect(); + let first = lines + .iter() + .find(|l| l.starts_with("::error")) + .or_else(|| lines.iter().find(|l| is_panic_or_error(l))); + first.map_or_else(String::new, |l| (*l).to_owned()) +} + +fn is_panic_or_error(line: &str) -> bool { + let t = line.trim_start(); + line.contains("panicked at") + || t.starts_with("error:") + || t.starts_with("error[") + || t.starts_with("Error:") +} + +/// `report` over the failed, blocking sections of a driver `results.json`; each section's +/// log is `.log` beside it (the driver maps every char outside `[A-Za-z0-9_.-]` +/// to `_`). A section the driver counts as passing (success, skipped, or +/// `continue_on_error`) is not read. +pub fn report_results(results: &Path) -> Result { + let text = + std::fs::read_to_string(results).map_err(|e| format!("{}: {e}", results.display()))?; + let v: serde_json::Value = + serde_json::from_str(&text).map_err(|e| format!("{}: {e}", results.display()))?; + let obj = v + .as_object() + .ok_or_else(|| format!("{}: not an object", results.display()))?; + let dir = results.parent().unwrap_or_else(|| Path::new(".")); + let mut out = String::new(); + for (name, r) in obj { + let result = r.get("result").and_then(|x| x.as_str()).unwrap_or(""); + let coe = r + .get("continue_on_error") + .and_then(|x| x.as_bool()) + .unwrap_or(false); + if matches!(result, "success" | "skipped") || coe { + continue; + } + let safe: String = name + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || "_.-".contains(c) { + c + } else { + '_' + } + }) + .collect(); + out.push_str(&line_for( + name, + &first_failing_line(&dir.join(format!("{safe}.log"))), + )); + } + Ok(out) +} + +fn line_for(section: &str, line: &str) -> String { + if line.is_empty() { + return String::new(); + } + let cut: String = line.chars().take(300).collect(); + format!("::error::section {section} first failing line: {cut}\n") +} + +/// `::error::section first failing line: ` (line cut to 300 chars) for every +/// log that has one; logs with none print nothing. +pub fn report(logs: &[std::path::PathBuf]) -> String { + let mut out = String::new(); + for log in logs { + let line = first_failing_line(log); + if line.is_empty() { + continue; + } + let stem = log + .file_stem() + .map_or_else(String::new, |s| s.to_string_lossy().into_owned()); + let cut: String = line.chars().take(300).collect(); + out.push_str(&format!( + "::error::section {stem} first failing line: {cut}\n" + )); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn log(dir: &Path, name: &str, text: &str) -> PathBuf { + let p = dir.join(name); + std::fs::write(&p, text).expect("write log"); + p + } + + fn tmp(tag: &str) -> PathBuf { + let d = std::env::temp_dir().join(format!("sff-{tag}-{}", std::process::id())); + std::fs::create_dir_all(&d).expect("mkdir"); + d + } + + #[test] + fn first_error_line_wins() { + let d = tmp("a"); + let p = log(&d, "s.log", "ok\n::error::boom one\n::error::boom two\n"); + assert_eq!(first_failing_line(&p), "::error::boom one"); + } + + #[test] + fn panic_without_error_annotation_counts() { + let d = tmp("b"); + let p = log(&d, "s.log", "ok\nthread 'x' panicked at src/a.rs:1\n"); + assert_eq!(first_failing_line(&p), "thread 'x' panicked at src/a.rs:1"); + } + + #[test] + fn error_annotation_beats_an_earlier_panic() { + let d = tmp("c"); + let p = log( + &d, + "s.log", + "thread 'x' panicked at a.rs:1\n::error::later\n", + ); + assert_eq!(first_failing_line(&p), "::error::later"); + } + + #[test] + fn results_mode_reads_only_blocking_failed_sections() { + let d = tmp("f"); + log(&d, "bad.log", "::error::bad cause\n"); + log(&d, "good.log", "::error::noise in a green section\n"); + log( + &d, + "lab.log", + "::error::noise in a continue-on-error section\n", + ); + log(&d, "determinism_X64_.log", "error: link failed\n"); + let results = log( + &d, + "results.json", + r#"{"bad":{"result":"failure"},"good":{"result":"success"}, + "lab":{"result":"failure","continue_on_error":true}, + "determinism[X64]":{"result":"cancelled"}}"#, + ); + let out = report_results(&results).expect("results parse"); + assert_eq!( + out, + "::error::section bad first failing line: ::error::bad cause\n\ + ::error::section determinism[X64] first failing line: error: link failed\n" + ); + } + + #[test] + fn results_mode_rejects_unreadable_results() { + let d = tmp("g"); + assert!(report_results(&d.join("absent.json")).is_err()); + assert!(report_results(&log(&d, "r.json", "not json")).is_err()); + } + + #[test] + fn clean_and_missing_logs_have_none() { + let d = tmp("d"); + assert_eq!(first_failing_line(&log(&d, "s.log", "ok\nfine\n")), ""); + assert_eq!(first_failing_line(&d.join("absent.log")), ""); + } + + #[test] + fn report_names_the_section_by_log_stem_and_cuts_long_lines() { + let d = tmp("e"); + let long = format!("::error::{}", "x".repeat(400)); + let a = log(&d, "guard-cargo.log", &format!("{long}\n")); + let b = log(&d, "guard-tree.log", "ok\n"); + let out = report(&[a, b]); + let want = format!( + "::error::section guard-cargo first failing line: {}\n", + &long[..300] + ); + assert_eq!(out, want); + } +} diff --git a/docs/roadmaps/entries/PMAT-5740.yaml b/docs/roadmaps/entries/PMAT-5740.yaml new file mode 100644 index 0000000000..8e19b2cef2 --- /dev/null +++ b/docs/roadmaps/entries/PMAT-5740.yaml @@ -0,0 +1,19 @@ +- id: PMAT-5740 + github_issue: 5740 + item_type: task + title: 'ci: fat_driver prints the first failing line of a failed section into the job log' + status: planned + priority: high + assigned_to: null + created: 2026-10-19T10:08:50Z + updated: 2026-10-19T10:08:50Z + spec: null + acceptance_criteria: + - 'Print the first ::error:: / failing-step line of a failed section next to the result line and in the verdict (GH #5740).' + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + notes: null + epic: 5510 From 39aea30226bf34db08e6b4f791f821bec8271d04 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 19 Oct 2026 13:16:43 +0200 Subject: [PATCH 2/5] ci(tools): section-first-fail names a rustc killed by signal under sccache (exit 2) (Refs #5750) Co-Authored-By: Claude Sonnet 5.5 --- .../ci-fat-driver-first-failing-line-v1.yaml | 12 +++++ .../src/section_first_fail.rs | 44 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/contracts/ci-fat-driver-first-failing-line-v1.yaml b/contracts/ci-fat-driver-first-failing-line-v1.yaml index e49f2d696f..2a072ec5fa 100644 --- a/contracts/ci-fat-driver-first-failing-line-v1.yaml +++ b/contracts/ci-fat-driver-first-failing-line-v1.yaml @@ -67,5 +67,17 @@ falsification_tests: mutation: >- starts_with("::error") -> "::errorX" fails 3 tests; dropping the continue_on_error skip fails 1; each restored GREEN (2026-10-19). + - id: CFF-F-002 + rule: a rustc killed by a signal under sccache is named, not shown as a compile error (GH #5750) + prediction: >- + A log with `could not compile` then an sccache `(exit status: 2)` yields + `rustc killed by signal ...` naming the crate; an `(exit status: 1)` compile + error keeps its own `error:` line. + test: 'cargo test -p aprender-ci-tools --lib section_first_fail' + if_fails: >- + an OOM-killed shard reads as a compile error and nobody looks at the runner + mutation: >- + "(exit status: 2)" -> "(exit status: 3)" fails 1 test; restored GREEN (2026-10-19). + non_goals: - "Changing which sections block, or the per-section log dump." diff --git a/crates/aprender-ci-tools/src/section_first_fail.rs b/crates/aprender-ci-tools/src/section_first_fail.rs index 6a0029394d..d9d7640392 100644 --- a/crates/aprender-ci-tools/src/section_first_fail.rs +++ b/crates/aprender-ci-tools/src/section_first_fail.rs @@ -13,6 +13,9 @@ pub fn first_failing_line(path: &Path) -> String { }; let text = String::from_utf8_lossy(&bytes); let lines: Vec<&str> = text.lines().collect(); + if let Some(k) = killed_rustc(&lines) { + return k; + } let first = lines .iter() .find(|l| l.starts_with("::error")) @@ -20,6 +23,23 @@ pub fn first_failing_line(path: &Path) -> String { first.map_or_else(String::new, |l| (*l).to_owned()) } +/// sccache exits 2 when its rustc is killed by a signal (OOM or host kill), so cargo prints +/// `could not compile` and `(exit status: 2)` with no rustc diagnostic (#5750). Name that, +/// not a compile error: the first `could not compile` line before an sccache `exit status: 2`. +fn killed_rustc(lines: &[&str]) -> Option { + let at = lines + .iter() + .position(|l| l.contains("sccache") && l.contains("(exit status: 2)"))?; + let krate = lines[..at] + .iter() + .rev() + .find(|l| l.contains("could not compile")) + .map_or("", |l| l.trim_start_matches("error: ").trim()); + Some(format!( + "rustc killed by signal (sccache exit status 2, not a compile error): {krate}" + )) +} + fn is_panic_or_error(line: &str) -> bool { let t = line.trim_start(); line.contains("panicked at") @@ -114,6 +134,30 @@ mod tests { d } + #[test] + fn sccache_exit_2_is_named_a_killed_rustc_not_a_compile_error() { + let d = tmp("k"); + let p = log( + &d, + "s.log", + "warning: x\nerror: could not compile `aprender-core` (lib test); 3 warnings emitted\n\nCaused by:\n process didn't exit successfully: `/usr/local/cargo/bin/sccache rustc --crate-name aprender` (exit status: 2)\n", + ); + let got = first_failing_line(&p); + assert!(got.starts_with("rustc killed by signal"), "{got}"); + assert!(got.contains("aprender-core"), "{got}"); + } + + #[test] + fn plain_rustc_exit_1_stays_a_compile_error() { + let d = tmp("k1"); + let p = log( + &d, + "s.log", + "error: could not compile `a` (lib)\n\nCaused by:\n process didn't exit successfully: `sccache rustc --crate-name a` (exit status: 1)\n", + ); + assert_eq!(first_failing_line(&p), "error: could not compile `a` (lib)"); + } + #[test] fn first_error_line_wins() { let d = tmp("a"); From 3f0bc413a9252d5052fe33046435e3ff3fff9c7c Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 19 Oct 2026 14:11:28 +0200 Subject: [PATCH 3/5] ci(tools): a real error before the sccache exit-2 line is not masked (Refs #5750) Co-Authored-By: Claude Sonnet 5.5 --- .../ci-fat-driver-first-failing-line-v1.yaml | 2 +- .../src/section_first_fail.rs | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/contracts/ci-fat-driver-first-failing-line-v1.yaml b/contracts/ci-fat-driver-first-failing-line-v1.yaml index 2a072ec5fa..c292c60b21 100644 --- a/contracts/ci-fat-driver-first-failing-line-v1.yaml +++ b/contracts/ci-fat-driver-first-failing-line-v1.yaml @@ -77,7 +77,7 @@ falsification_tests: if_fails: >- an OOM-killed shard reads as a compile error and nobody looks at the runner mutation: >- - "(exit status: 2)" -> "(exit status: 3)" fails 1 test; restored GREEN (2026-10-19). + "(exit status: 2)" -> "(exit status: 3)" fails 1 test; restored GREEN (2026-10-19). Dropping the real-error-first guard fails 1 test (a real error before the exit-2 line is not masked); restored GREEN. non_goals: - "Changing which sections block, or the per-section log dump." diff --git a/crates/aprender-ci-tools/src/section_first_fail.rs b/crates/aprender-ci-tools/src/section_first_fail.rs index d9d7640392..c005774bb4 100644 --- a/crates/aprender-ci-tools/src/section_first_fail.rs +++ b/crates/aprender-ci-tools/src/section_first_fail.rs @@ -25,11 +25,18 @@ pub fn first_failing_line(path: &Path) -> String { /// sccache exits 2 when its rustc is killed by a signal (OOM or host kill), so cargo prints /// `could not compile` and `(exit status: 2)` with no rustc diagnostic (#5750). Name that, -/// not a compile error: the first `could not compile` line before an sccache `exit status: 2`. +/// not a compile error: the first `could not compile` line before an sccache `exit status: 2`. A real error +/// (`::error`, panic, `error:`) earlier in the log wins instead, so it is never masked. fn killed_rustc(lines: &[&str]) -> Option { let at = lines .iter() .position(|l| l.contains("sccache") && l.contains("(exit status: 2)"))?; + let real_error_first = lines[..at].iter().any(|l| { + l.starts_with("::error") || (is_panic_or_error(l) && !l.contains("could not compile")) + }); + if real_error_first { + return None; + } let krate = lines[..at] .iter() .rev() @@ -147,6 +154,26 @@ mod tests { assert!(got.contains("aprender-core"), "{got}"); } + #[test] + fn a_real_error_before_the_exit_2_line_stays_the_first_failing_line() { + let d = tmp("k2"); + let p = log( + &d, + "s.log", + "error[E0425]: cannot find value `x`\nerror: could not compile `a` (lib test)\n\nCaused by:\n process didn't exit successfully: `sccache rustc --crate-name a` (exit status: 2)\n", + ); + assert_eq!( + first_failing_line(&p), + "error[E0425]: cannot find value `x`" + ); + let q = log( + &d, + "t.log", + "thread 'x' panicked at a.rs:1\nerror: could not compile `a` (lib)\n process didn't exit successfully: `sccache rustc` (exit status: 2)\n", + ); + assert_eq!(first_failing_line(&q), "thread 'x' panicked at a.rs:1"); + } + #[test] fn plain_rustc_exit_1_stays_a_compile_error() { let d = tmp("k1"); From 07554e73049f7f0b720647469562b4e18a4461fc Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 19 Oct 2026 19:59:38 +0200 Subject: [PATCH 4/5] fix(contracts): regenerate contracts.nt, shapes.ttl and witness after merging main (WitnessStale) Co-Authored-By: Claude Sonnet 5.5 --- contracts/contracts.nt | 8 +++ contracts/shapes.ttl | 68 +++++++++---------- ...5f0be2f90923120a3027c176ad56723d0d850.json | 6 +- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/contracts/contracts.nt b/contracts/contracts.nt index 218e0a585d..9b0ddc8a9a 100644 --- a/contracts/contracts.nt +++ b/contracts/contracts.nt @@ -9493,6 +9493,14 @@ "ci-determinism-wait"^^ . "active"^^ . "1.0.0"^^ . + . + . + "contracts/ci-fat-driver-first-failing-line-v1.yaml"^^ . + "ci-fat-driver-first-failing-line-v1"^^ . + "pattern"^^ . + "ci-fat-driver-first-failing-line"^^ . + "active"^^ . + "1.0.0"^^ . . . "contracts/ci-gate-integrity-v1.yaml"^^ . diff --git a/contracts/shapes.ttl b/contracts/shapes.ttl index bed403e57d..a718856fa0 100644 --- a/contracts/shapes.ttl +++ b/contracts/shapes.ttl @@ -1983,11 +1983,11 @@ ] ; sh:property [ sh:path ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:in ( "8.9" "12.1" ) ; @@ -2067,11 +2067,11 @@ sh:maxCount 1 ; sh:class ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2084,11 +2084,11 @@ sh:targetClass ; sh:property [ sh:path ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2103,11 +2103,11 @@ sh:targetClass ; sh:property [ sh:path ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2194,7 +2194,7 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2217,7 +2217,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2239,7 +2239,7 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2257,7 +2257,7 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2269,7 +2269,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2283,7 +2283,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2347,11 +2347,11 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2392,11 +2392,11 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2448,11 +2448,11 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2511,11 +2511,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2543,11 +2543,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2569,11 +2569,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2595,11 +2595,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2626,11 +2626,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2721,7 +2721,7 @@ sh:minCount 1 ; sh:class ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2733,7 +2733,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2768,11 +2768,11 @@ sh:maxCount 1 ; sh:class ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; diff --git a/contracts/witness/811c78184eb2609244c59534f015f0be2f90923120a3027c176ad56723d0d850.json b/contracts/witness/811c78184eb2609244c59534f015f0be2f90923120a3027c176ad56723d0d850.json index 13582518f0..91ffe5dc17 100644 --- a/contracts/witness/811c78184eb2609244c59534f015f0be2f90923120a3027c176ad56723d0d850.json +++ b/contracts/witness/811c78184eb2609244c59534f015f0be2f90923120a3027c176ad56723d0d850.json @@ -1,7 +1,7 @@ { - "census_id_set_sha256": "063d371c514532fccc38497ed2f0d85711be3c35061de05df561446f16e93d99", + "census_id_set_sha256": "de6d7a4351b5395a3042b4d66553329dbe45d28a9b11f7af1cfcd1a682b68931", "relations_sha256": "811c78184eb2609244c59534f015f0be2f90923120a3027c176ad56723d0d850", - "reasoner_git_sha": "56ae1edeaf9f0761d3e0f9bf0754fb735e7c3d05", + "reasoner_git_sha": "445a23c7d98a3d7263d95bd6b16808226cb28a86", "checkable_n": 87, "result": { "kind": "model", @@ -10,5 +10,5 @@ } }, "pc_reasoner": "fired", - "cpu_ms": 2 + "cpu_ms": 1 } From 8ec6ff852f818786a27f8ed85755ab5b7fd48537 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 19 Oct 2026 22:28:09 +0200 Subject: [PATCH 5/5] fix(contracts): regenerate shapes.ttl and witness with the in-tree pv (ont4b freshness gate) Co-Authored-By: Claude Sonnet 5.5 --- contracts/shapes.ttl | 68 ++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/contracts/shapes.ttl b/contracts/shapes.ttl index a718856fa0..bed403e57d 100644 --- a/contracts/shapes.ttl +++ b/contracts/shapes.ttl @@ -1983,11 +1983,11 @@ ] ; sh:property [ sh:path ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:in ( "8.9" "12.1" ) ; @@ -2067,11 +2067,11 @@ sh:maxCount 1 ; sh:class ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2084,11 +2084,11 @@ sh:targetClass ; sh:property [ sh:path ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2103,11 +2103,11 @@ sh:targetClass ; sh:property [ sh:path ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2194,7 +2194,7 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2217,7 +2217,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2239,7 +2239,7 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2257,7 +2257,7 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2269,7 +2269,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2283,7 +2283,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2347,11 +2347,11 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2392,11 +2392,11 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2448,11 +2448,11 @@ sh:path ; sh:minCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2511,11 +2511,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2543,11 +2543,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2569,11 +2569,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2595,11 +2595,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2626,11 +2626,11 @@ sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2721,7 +2721,7 @@ sh:minCount 1 ; sh:class ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; sh:property [ sh:path ; @@ -2733,7 +2733,7 @@ ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ; @@ -2768,11 +2768,11 @@ sh:maxCount 1 ; sh:class ; sh:nodeKind sh:IRI ; - sh:node ; + sh:node ; ] ; . - a sh:NodeShape ; + a sh:NodeShape ; sh:property [ sh:path ; sh:minCount 1 ;