diff --git a/.claude/skills/ci-gates/SKILL.md b/.claude/skills/ci-gates/SKILL.md index 7d39647e4c..b2eb7a6e8c 100644 --- a/.claude/skills/ci-gates/SKILL.md +++ b/.claude/skills/ci-gates/SKILL.md @@ -5633,3 +5633,62 @@ asked to make. **A mangled commit message is not worth rewriting history for.** Push a follow-up commit, or fix it before pushing. Reported rather than quietly left in the log. + +## 156. "Blocked" and "amnestied" are not the same excuse, and I confused them three times + +I wrote — in a commit, a PR body, and a dashboard — that the phase which would +notice a spec losing its assertions "sits in the suite's BLOCKED column, so +nothing reports it". Then I read the column: + + phase corpus scratch blocked + parse-no-discard 87 0 0 + no-vacuous-invariant 0 0 72 + +`parse-no-discard` reports 87 PRIMARY failures. What is blocked is a different +phase. The suite is green not because the check is gated away but because **every +failure it can report is in the ledger by name** — 91 `parse` + 87 +`parse-no-discard` is the whole 178. + +The two look alike from a green run and are opposite in what to do: + +| | what it means | the fix | +|---|---|---| +| **blocked** | never evaluated, gated behind an upstream phase | un-gate it and face what it says | +| **amnestied** | evaluated, failed, and excused by name | make the excuse *specific*, then shrink it | + +**Check which one before writing either word.** A wrong diagnosis here sends the +next iteration to un-gate a phase that was never gated. + +## 157. An amnesty by identity is blind to magnitude + +`(path, phase)` says "this spec discards". It does not say how much, so a spec +could go from one discarded token to 682 without moving a gate — and 1 292 +recovered tokens could not be priced, because the population was 87 either way. + +Adding the number takes three rules, and only the first is obvious: + +- **more is a failure** — the missing regression signal +- **less is ALSO a failure** — same reason an unexpected PASS is one. Slack that + nobody claims is where the next regression hides. +- **no reading is WORSE, never an improvement** — a spec that stopped being + measured and a spec that discards nothing are identical from the comparator's + side. A map that defaults to zero reports every unreadable item as a triumph. + +The third is this repository's oldest lesson wearing a new hat, and it is the one +a `Default::default()` on a lookup silently gets wrong. + +## 158. The "what this does NOT cover" section rots first, and rots worst + +Two claims in `docs/CORPUS-RATCHET.md` had gone stale: + +- *"`parse-complete` is not among the phases; appending `))) … (((` leaves the + ratchet CLEAN"* — it is a phase now, and I re-verified by appending the garbage: + `UNEXPECTED FAILURES: 1`. +- *"5 standing unit-test failures"* — zero, measured. + +Both were in the section headed *what the ratchet does not cover*. That section is +read at exactly one moment: when somebody is deciding how far to trust a green +run. **A stale limitation there is worse than a stale feature list** — it either +frightens people away from a check that works, or excuses them from one that does. + +Re-verify that section by running its claims, not by reading them. diff --git a/bootstrap/src/suite.rs b/bootstrap/src/suite.rs index c7a38419ab..2e74501c6f 100644 --- a/bootstrap/src/suite.rs +++ b/bootstrap/src/suite.rs @@ -356,6 +356,21 @@ struct ExpectationEntry { /// `YYYY-MM-DD`. A past-due entry fails the run -- this is the only thing /// in the design that pushes back on normalisation of deviance. expires: String, + /// W699: for `parse-no-discard`, HOW MUCH this spec is amnestied to throw + /// away. + /// + /// The amnesty was an identity: `(path, phase)`. A spec could go from + /// discarding one token to discarding six hundred and eighty-two and no + /// gate would move, because the entry says only "this spec discards". The + /// same blindness runs the other way: two parser fixes recovered 1 292 + /// tokens across the corpus and the ledger could not price it -- the + /// population stayed at 87 either way. + /// + /// `None` for phases where it means nothing (`parse` cannot discard; it + /// never finished). For `parse-no-discard` it is REQUIRED: an amnesty with + /// no bound is the thing this field exists to end. + #[serde(default, skip_serializing_if = "Option::is_none")] + discard_tokens: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] @@ -417,6 +432,18 @@ struct RatchetVerdict { unexpected_passes: Vec, /// Entries whose `expires` is in the past. expired: Vec, + /// W699: entries discarding MORE than the ledger pinned. Regressions that + /// the identity-only comparison could not see. + #[serde(default)] + discard_worsened: Vec, + /// Entries discarding LESS than pinned. A failure, for the same reason an + /// unexpected PASS is: unclaimed slack is where the next regression hides. + /// Re-bless to pin the new number. + #[serde(default)] + discard_improved: Vec, + /// `parse-no-discard` entries with no bound at all. + #[serde(default)] + discard_unpinned: Vec, /// True when `entries.len()` exceeds the declared cap. over_cap: bool, ledger_size: usize, @@ -428,16 +455,32 @@ impl RatchetVerdict { self.unexpected_failures.is_empty() && self.unexpected_passes.is_empty() && self.expired.is_empty() + && self.discard_worsened.is_empty() + && self.discard_improved.is_empty() + && self.discard_unpinned.is_empty() && !self.over_cap } } /// Compare observed primary corpus failures against the ledger. /// `observed` is a set of `(path, phase)`; `today` is `YYYY-MM-DD`. +/// W699: the phase whose amnesty carries a number is `parse-no-discard`. +/// +/// `parse` entries never reach EOF, so they discard nothing by definition and +/// carry no bound. Naming the phase in one place keeps the two rules -- who must +/// be pinned, whose observation is read -- from drifting apart. +const DISCARD_PHASE: &str = "parse-no-discard"; + +/// Compare observed primary corpus failures against the ledger. +/// `observed` is a set of `(path, phase)`; `today` is `YYYY-MM-DD`. +/// `discard` maps a spec path to the tokens this run saw it throw away; a path +/// missing from the map was not measured, which is NOT the same as zero and is +/// treated as "no reading", never as an improvement. fn ratchet_compare( observed: &std::collections::BTreeSet<(String, String)>, exp: &SuiteExpectations, today: &str, + discard: &std::collections::BTreeMap, ) -> RatchetVerdict { let expected: std::collections::BTreeSet<(String, String)> = exp .entries @@ -447,8 +490,39 @@ fn ratchet_compare( let fmt = |(p, ph): &(String, String)| format!("{} [{}]", p, ph); + let mut worsened: Vec = Vec::new(); + let mut improved: Vec = Vec::new(); + let mut unpinned: Vec = Vec::new(); + for e in exp.entries.iter().filter(|e| e.phase == DISCARD_PHASE) { + match (e.discard_tokens, discard.get(&e.path)) { + (None, _) => unpinned.push(format!( + "{} [{}] has no `discard_tokens` -- an amnesty with no bound", + e.path, e.phase + )), + // No reading for a pinned entry. Silence is not agreement: report it + // beside the worsened, because a spec that stopped being measured + // and a spec that discards nothing look identical from here. + (Some(p), None) => worsened.push(format!( + "{} [{}] pinned at {} but this run took NO reading", + e.path, e.phase, p + )), + (Some(p), Some(&o)) if o > p => worsened.push(format!( + "{} [{}] discards {} tokens, pinned at {} (+{})", + e.path, e.phase, o, p, o - p + )), + (Some(p), Some(&o)) if o < p => improved.push(format!( + "{} [{}] discards {} tokens, pinned at {} (-{}) -- re-bless to pin it", + e.path, e.phase, o, p, p - o + )), + _ => {} + } + } + RatchetVerdict { unexpected_failures: observed.difference(&expected).map(fmt).collect(), + discard_worsened: worsened, + discard_improved: improved, + discard_unpinned: unpinned, unexpected_passes: expected.difference(observed).map(fmt).collect(), // Lexicographic comparison is correct for zero-padded ISO-8601 dates. expired: exp @@ -2627,6 +2701,24 @@ pub fn run_comprehensive(repo_root: &Path, opts: SuiteOptions) -> anyhow::Result let exp_path = expectations_path(&repo); let today = chrono::Local::now().format("%Y-%m-%d").to_string(); + // W699: how much each discarding spec threw away THIS run. Read for every + // spec observed failing `parse-no-discard`, in-process -- the phase's own + // comment calls it nearly free, and this is the same call it makes. + // + // A spec that fails the phase but yields no reading here is left OUT of the + // map rather than entered as zero: the ratchet must be able to tell "no + // reading" from "discards nothing", and a default of zero would report every + // unreadable spec as a triumphant improvement. + let observed_discard: std::collections::BTreeMap = observed + .iter() + .filter(|(_, ph)| ph == DISCARD_PHASE) + .filter_map(|(path, _)| { + let src = fs::read_to_string(repo.join(path)).ok()?; + let (_, n) = crate::compiler::Compiler::parse_ast_accounted(&src).ok()?; + Some((path.clone(), n)) + }) + .collect(); + if opts.bless_expectations { let prior = load_expectations(&exp_path)?; // The cap only ever moves DOWN. Blessing a larger population must be a @@ -2651,13 +2743,25 @@ pub fn run_comprehensive(repo_root: &Path, opts: SuiteOptions) -> anyhow::Result let mut entries: Vec = observed .iter() .map(|k| { - prior_by_key.get(k).cloned().unwrap_or(ExpectationEntry { - path: k.0.clone(), - phase: k.1.clone(), - reason: "unclassified: blessed by --bless-expectations".to_string(), - issue: 1959, - expires: "2026-11-30".to_string(), - }) + { + let mut e = prior_by_key.get(k).cloned().unwrap_or(ExpectationEntry { + path: k.0.clone(), + phase: k.1.clone(), + reason: "unclassified: blessed by --bless-expectations".to_string(), + issue: 1959, + expires: "2026-11-30".to_string(), + discard_tokens: None, + }); + // W699: blessing pins the CURRENT volume. It only ever + // writes what this run measured -- so an entry whose spec + // improved is lowered here, and one that worsened is raised + // ONLY by a human running bless and reviewing the diff, + // which is the same reviewable event the cap relies on. + if e.phase == DISCARD_PHASE { + e.discard_tokens = observed_discard.get(&e.path).copied(); + } + e + } }) .collect(); entries.sort(); @@ -2692,7 +2796,7 @@ pub fn run_comprehensive(repo_root: &Path, opts: SuiteOptions) -> anyhow::Result ratchet_clean = false; } Some(exp) => { - let v = ratchet_compare(&observed, &exp, &today); + let v = ratchet_compare(&observed, &exp, &today, &observed_discard); println!(" ledger: {} / {} cap", v.ledger_size, v.max_entries); println!(" observed (primary): {}", observed.len()); // W636: these lists used to stop at 25 with NO indication, and @@ -2721,6 +2825,9 @@ pub fn run_comprehensive(repo_root: &Path, opts: SuiteOptions) -> anyhow::Result " (fixed -- remove from the ledger)", ); show("EXPIRED ENTRIES ", '!', &v.expired, ""); + show("DISCARD WORSENED ", '>', &v.discard_worsened, ""); + show("DISCARD IMPROVED ", '<', &v.discard_improved, ""); + show("DISCARD UNPINNED ", '?', &v.discard_unpinned, ""); if v.over_cap { println!(" OVER CAP: {} > {}", v.ledger_size, v.max_entries); } @@ -2748,7 +2855,7 @@ pub fn run_comprehensive(repo_root: &Path, opts: SuiteOptions) -> anyhow::Result // identity, not the level of a total. This is the whole point: a total // that is already 2614 cannot move when something new breaks (T27). if ratchet_clean { - println!("RATCHET CLEAN -- no unexpected failures, passes, or expiries"); + println!("RATCHET CLEAN -- no unexpected failures, passes, expiries, or discard drift"); println!("phi^2 + 1/phi^2 = 3 | TRINITY"); return Ok(()); } @@ -3176,12 +3283,36 @@ mod tests { reason: "test".into(), issue: 1959, expires: expires.to_string(), + discard_tokens: None, }) .collect(), ..Default::default() } } + /// W699: a ledger of `parse-no-discard` entries with a pinned volume. + fn mk_disc(pairs: &[(&str, Option)], cap: usize) -> super::SuiteExpectations { + super::SuiteExpectations { + max_entries: cap, + entries: pairs + .iter() + .map(|(p, n)| super::ExpectationEntry { + path: p.to_string(), + phase: super::DISCARD_PHASE.to_string(), + reason: "test".into(), + issue: 1959, + expires: "2099-01-01".to_string(), + discard_tokens: *n, + }) + .collect(), + ..Default::default() + } + } + + fn disc_obs(pairs: &[(&str, usize)]) -> std::collections::BTreeMap { + pairs.iter().map(|(p, n)| (p.to_string(), *n)).collect() + } + fn obs(pairs: &[(&str, &str)]) -> std::collections::BTreeSet<(String, String)> { pairs .iter() @@ -3192,7 +3323,7 @@ mod tests { #[test] fn ratchet_is_clean_when_observed_equals_expected() { let e = mk_exp(&[("specs/a.t27", "parse")], "2099-01-01", 1); - let v = super::ratchet_compare(&obs(&[("specs/a.t27", "parse")]), &e, "2026-08-12"); + let v = super::ratchet_compare(&obs(&[("specs/a.t27", "parse")]), &e, "2026-08-12", &Default::default()); assert!(v.clean(), "{:?}", v); } @@ -3205,17 +3336,98 @@ mod tests { &obs(&[("specs/a.t27", "parse"), ("specs/b.t27", "parse")]), &e, "2026-08-12", + &Default::default(), ); assert_eq!(v.unexpected_failures, vec!["specs/b.t27 [parse]".to_string()]); assert!(!v.clean()); } + // ---- W699: the amnesty carries a NUMBER, not just an identity. --------- + // + // Before this, `(path, parse-no-discard)` said "this spec discards" and + // nothing more. A spec could go from one token to six hundred and eighty-two + // without moving a gate, and two parser fixes that recovered 1 292 tokens + // across the corpus could not be priced -- the population was 87 either way. + + #[test] + fn ratchet_fails_when_a_spec_discards_more_than_it_is_pinned_at() { + let e = mk_disc(&[("specs/a.t27", Some(10))], 1); + let v = super::ratchet_compare( + &obs(&[("specs/a.t27", super::DISCARD_PHASE)]), + &e, + "2026-08-12", + &disc_obs(&[("specs/a.t27", 42)]), + ); + assert_eq!(v.discard_worsened.len(), 1, "{:?}", v); + assert!(v.discard_worsened[0].contains("+32"), "{:?}", v.discard_worsened); + assert!(!v.clean(), "a spec throwing away more must fail the run"); + } + + #[test] + fn ratchet_fails_when_a_spec_improves_so_the_slack_cannot_be_banked() { + // Same rule as an unexpected PASS, one level down: unclaimed slack is + // where the next regression hides. Re-bless to pin the new number. + let e = mk_disc(&[("specs/a.t27", Some(100))], 1); + let v = super::ratchet_compare( + &obs(&[("specs/a.t27", super::DISCARD_PHASE)]), + &e, + "2026-08-12", + &disc_obs(&[("specs/a.t27", 40)]), + ); + assert_eq!(v.discard_improved.len(), 1, "{:?}", v); + assert!(v.discard_improved[0].contains("-60"), "{:?}", v.discard_improved); + assert!(!v.clean()); + } + + #[test] + fn ratchet_fails_on_an_amnesty_with_no_bound() { + let e = mk_disc(&[("specs/a.t27", None)], 1); + let v = super::ratchet_compare( + &obs(&[("specs/a.t27", super::DISCARD_PHASE)]), + &e, + "2026-08-12", + &disc_obs(&[("specs/a.t27", 7)]), + ); + assert_eq!(v.discard_unpinned.len(), 1, "{:?}", v); + assert!(!v.clean(), "an unbounded amnesty is the thing this field ends"); + } + + #[test] + fn ratchet_reads_a_missing_reading_as_worse_not_as_an_improvement() { + // The trap this whole repository keeps re-learning: a measurement that + // did not happen is not a measurement of zero. If the map defaulted, + // every unreadable spec would report as a triumphant improvement. + let e = mk_disc(&[("specs/a.t27", Some(10))], 1); + let v = super::ratchet_compare( + &obs(&[("specs/a.t27", super::DISCARD_PHASE)]), + &e, + "2026-08-12", + &Default::default(), + ); + assert_eq!(v.discard_worsened.len(), 1, "{:?}", v); + assert!(v.discard_worsened[0].contains("NO reading"), "{:?}", v.discard_worsened); + assert!(v.discard_improved.is_empty(), "silence is not an improvement"); + assert!(!v.clean()); + } + + #[test] + fn ratchet_is_clean_when_the_volume_matches_exactly() { + let e = mk_disc(&[("specs/a.t27", Some(42))], 1); + let v = super::ratchet_compare( + &obs(&[("specs/a.t27", super::DISCARD_PHASE)]), + &e, + "2026-08-12", + &disc_obs(&[("specs/a.t27", 42)]), + ); + assert!(v.clean(), "{:?}", v); + } + #[test] fn ratchet_treats_a_fix_as_a_failure_so_the_ledger_cannot_rot() { // pytest's `xfail_strict`, made the default. Without this the ledger // accumulates entries for defects that were fixed years ago. let e = mk_exp(&[("specs/a.t27", "parse"), ("specs/b.t27", "parse")], "2099-01-01", 2); - let v = super::ratchet_compare(&obs(&[("specs/a.t27", "parse")]), &e, "2026-08-12"); + let v = super::ratchet_compare(&obs(&[("specs/a.t27", "parse")]), &e, "2026-08-12", &Default::default()); assert_eq!(v.unexpected_passes, vec!["specs/b.t27 [parse]".to_string()]); assert!(!v.clean(), "an unexpected pass must fail the run"); } @@ -3223,7 +3435,7 @@ mod tests { #[test] fn ratchet_fails_on_a_past_due_entry_even_when_the_sets_agree() { let e = mk_exp(&[("specs/a.t27", "parse")], "2026-01-01", 1); - let v = super::ratchet_compare(&obs(&[("specs/a.t27", "parse")]), &e, "2026-08-12"); + let v = super::ratchet_compare(&obs(&[("specs/a.t27", "parse")]), &e, "2026-08-12", &Default::default()); assert!(v.unexpected_failures.is_empty()); assert!(v.unexpected_passes.is_empty()); assert_eq!(v.expired.len(), 1); @@ -3237,6 +3449,7 @@ mod tests { &obs(&[("specs/a.t27", "parse"), ("specs/b.t27", "parse")]), &e, "2026-08-12", + &Default::default(), ); assert!(v.over_cap); assert!(!v.clean()); @@ -3247,7 +3460,7 @@ mod tests { // The identity is (path, phase), not path. A file amnestied at `parse` // that starts failing `gen-c` is a NEW defect. let e = mk_exp(&[("specs/a.t27", "parse")], "2099-01-01", 1); - let v = super::ratchet_compare(&obs(&[("specs/a.t27", "gen-c")]), &e, "2026-08-12"); + let v = super::ratchet_compare(&obs(&[("specs/a.t27", "gen-c")]), &e, "2026-08-12", &Default::default()); assert_eq!(v.unexpected_failures, vec!["specs/a.t27 [gen-c]".to_string()]); assert_eq!(v.unexpected_passes, vec!["specs/a.t27 [parse]".to_string()]); } diff --git a/cli/tri/src/discard.rs b/cli/tri/src/discard.rs new file mode 100644 index 0000000000..ccbc85a080 --- /dev/null +++ b/cli/tri/src/discard.rs @@ -0,0 +1,177 @@ +//! What the parser reads and throws away, ranked, against what it is pinned at. +//! +//! WHY THIS EXISTS +//! --------------- +//! `parse-no-discard` reports 87 corpus failures and every one of them is +//! amnestied by name in `docs/reports/suite_expectations.json`. The suite is +//! green because every failure it can report is pre-approved -- which is the +//! design working, not a defect, but it means the question "where is the next +//! rung" has no answer in any suite output. +//! +//! Ranking is the answer. Two parser fixes in one day took 1 292 tokens out of +//! the corpus by working the largest entries first, and the largest entry is not +//! visible from a count of 87. +//! +//! This reads `t27c parse-complete` -- the same command the suite phase calls +//! into -- rather than re-implementing the accounting. A second implementation +//! of a measurement is a second number to disagree with the first, and this +//! repository has paid for that more than once. +use anyhow::{Context, Result}; +use clap::Subcommand; +use std::collections::BTreeMap; +use std::path::PathBuf; + +#[derive(Subcommand)] +pub enum DiscardCmd { + /// Rank the discarding specs, largest first, against the pinned volume. + Top { + /// How many to print. 0 prints all of them. + #[arg(long, default_value_t = 15)] + n: usize, + }, +} + +fn repo_root() -> Result { + let out = std::process::Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .output() + .context("running `git rev-parse --show-toplevel`")?; + if !out.status.success() { + anyhow::bail!("not inside a git repository"); + } + Ok(PathBuf::from(String::from_utf8(out.stdout)?.trim())) +} + +/// `spec -> tokens`, read out of `t27c parse-complete`. +fn observed(root: &std::path::Path) -> Result> { + let t27c = ["target/release/t27c", "target/debug/t27c"] + .iter() + .map(|p| root.join(p)) + .find(|p| p.is_file()) + .ok_or_else(|| { + anyhow::anyhow!( + "t27c is not built. `cargo build --release -p t27c` first --\n \ + reporting nothing rather than an empty ranking this run did not earn" + ) + })?; + let out = std::process::Command::new(t27c) + .arg("parse-complete") + .current_dir(root) + .output() + .context("running `t27c parse-complete`")?; + let text = String::from_utf8_lossy(&out.stdout); + let mut map = BTreeMap::new(); + for line in text.lines() { + // `specs/base/ternary_add.t27: DISCARDED 208 top-level token(s)` + let Some((path, rest)) = line.split_once(": DISCARDED ") else { + continue; + }; + let Some(n) = rest.split_whitespace().next().and_then(|w| w.parse().ok()) else { + continue; + }; + map.insert(path.trim().to_string(), n); + } + if map.is_empty() && !text.contains("parse but DISCARD") { + anyhow::bail!( + "`t27c parse-complete` produced no recognisable output.\n \ + Nothing was read, so nothing is claimed -- this is not \"zero specs discard\"." + ); + } + Ok(map) +} + +/// `spec -> pinned tokens`, from the ledger. Absent means the entry carries no +/// bound, which the ratchet fails on; here it prints as `--`. +fn pinned(root: &std::path::Path) -> Result>> { + let p = root.join("docs/reports/suite_expectations.json"); + let v: serde_json::Value = serde_json::from_str( + &std::fs::read_to_string(&p).with_context(|| format!("reading {}", p.display()))?, + ) + .with_context(|| format!("parsing {}", p.display()))?; + let mut map = BTreeMap::new(); + for e in v + .get("entries") + .and_then(|x| x.as_array()) + .unwrap_or(&vec![]) + { + if e.get("phase").and_then(|x| x.as_str()) != Some("parse-no-discard") { + continue; + } + let Some(path) = e.get("path").and_then(|x| x.as_str()) else { + continue; + }; + map.insert( + path.to_string(), + e.get("discard_tokens") + .and_then(|x| x.as_u64()) + .map(|n| n as usize), + ); + } + Ok(map) +} + +pub fn run(cmd: &DiscardCmd) -> Result<()> { + let root = repo_root()?; + let obs = observed(&root)?; + let pin = pinned(&root)?; + + let DiscardCmd::Top { n } = cmd; + let mut rows: Vec<(&String, &usize)> = obs.iter().collect(); + rows.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0))); + + let total: usize = obs.values().sum(); + println!(" {} spec(s) discard {} token(s)", obs.len(), total); + println!(); + println!(" {:>7} {:>7} spec", "tokens", "pinned"); + let shown = if *n == 0 { + rows.len() + } else { + (*n).min(rows.len()) + }; + for (path, tokens) in rows.iter().take(shown) { + let p = match pin.get(*path) { + Some(Some(v)) => v.to_string(), + Some(None) => "--".to_string(), + // Observed but not in the ledger at all: an unexpected failure, and + // the ratchet says so far more loudly than this table should. + None => "NOT IN LEDGER".to_string(), + }; + println!(" {:>7} {:>7} {}", tokens, p, path); + } + if shown < rows.len() { + println!( + " ... and {} more not shown (--n 0 for all)", + rows.len() - shown + ); + } + println!(); + println!(" The pinned column is the ledger's bound, not a target. It moves"); + println!(" only when `t27c suite --bless-expectations` re-measures."); + Ok(()) +} + +#[cfg(test)] +mod tests { + /// The line shape this parses is `t27c parse-complete`'s, and it is the only + /// contract between the two. Pin it here so a change to that output is a + /// test failure rather than a silently empty ranking. + #[test] + fn the_parse_complete_line_shape_is_read_correctly() { + let line = "specs/base/ternary_add.t27: DISCARDED 208 top-level token(s)"; + let (path, rest) = line.split_once(": DISCARDED ").expect("shape changed"); + assert_eq!(path, "specs/base/ternary_add.t27"); + let n: usize = rest.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(n, 208); + } + + /// A summary line must not be mistaken for a spec row. + #[test] + fn the_summary_lines_are_not_rows() { + for line in [ + " parse but DISCARD 87 (32485 token(s))", + " specs scanned 650", + ] { + assert!(line.split_once(": DISCARDED ").is_none(), "{line}"); + } + } +} diff --git a/cli/tri/src/main.rs b/cli/tri/src/main.rs index 5054f7f3aa..ae11529cdc 100644 --- a/cli/tri/src/main.rs +++ b/cli/tri/src/main.rs @@ -8,6 +8,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; mod depin; +mod discard; mod cibase; mod fleet; mod fpga; @@ -127,6 +128,11 @@ enum Commands { #[command(subcommand)] action: rtl::RtlCmd, }, + /// What the parser reads and throws away, ranked against its pinned bound. + Discard { + #[command(subcommand)] + action: discard::DiscardCmd, + }, /// What `.trinity/seals` says about a spec, when it says it twice. Seals { #[command(subcommand)] @@ -747,6 +753,7 @@ fn main() -> Result<()> { Commands::Gates { action } => gates::run(action)?, Commands::Vectors { action } => vectors::run(action)?, Commands::Rtl { action } => rtl::run(action)?, + Commands::Discard { action } => discard::run(action)?, Commands::Seals { action } => seals::run(action)?, Commands::Hooks { action } => hooks::run(action)?, } diff --git a/docs/CORPUS-RATCHET.md b/docs/CORPUS-RATCHET.md index 1b46462742..ecd5efc6f0 100644 --- a/docs/CORPUS-RATCHET.md +++ b/docs/CORPUS-RATCHET.md @@ -21,7 +21,7 @@ identity. --- -## The four ways it fails +## The seven ways it fails | verdict | meaning | what to do | |---|---|---| @@ -29,6 +29,9 @@ identity. | **UNEXPECTED PASS** | a ledger entry passed | **you fixed something.** Remove the entry and lower `max_entries` | | **EXPIRED** | an entry is past its `expires` date | fix the spec, or renew the date with a reason in the PR | | **OVER CAP** | the ledger outgrew `max_entries` | the cap only moves down automatically; raising it is a hand edit | +| **DISCARD WORSENED** | a `parse-no-discard` entry threw away MORE tokens than it is pinned at — or this run took no reading at all | fix it, or bless and justify the rise in the PR | +| **DISCARD IMPROVED** | it threw away FEWER | **you fixed something.** Re-bless so the new, lower number is what the next run is held to | +| **DISCARD UNPINNED** | a `parse-no-discard` entry carries no `discard_tokens` | bless once; an amnesty with no bound is what that field exists to end | **An unexpected pass is a failure, and that is deliberate.** Gating only on new breaks makes the ledger *monotone*: entries get added when defects appear and @@ -97,18 +100,32 @@ Read this before trusting a green run. of 612,924,235 bytes (98.89%), generator output the ledger does not gate on. That exclusion is what makes the check 314 s instead of 4057 s with a bit-identical verdict. -- **Only the phases `suite` runs**: `parse`, `typecheck`, `gen-zig`, - `gen-rust`, `gen-verilog`, `gen-c`, `seal-verify`, plus the smoke gates. - **`parse-complete` and `lex-dropped` are not among them**, and `t27c parse` - returns success on a file it did not fully consume — so appended garbage after - the last valid construct is invisible to this gate. Verified: appending - `))) … (((` to a corpus spec leaves the ratchet CLEAN; a mid-file break is - caught and named. +- **Only the phases `suite` runs**: `parse`, `parse-no-discard`, `typecheck`, + `gen-zig`, `gen-rust`, `gen-verilog`, `gen-c`, `seal-verify`, plus the smoke + gates. `lex-dropped` is not among them. + + **This bullet used to say `parse-complete` was not among them either, and that + appending `))) … (((` to a corpus spec left the ratchet CLEAN.** Both are now + false, and the correction is dated 2026-08-29: `parse-no-discard` is a phase and + runs the same accounting `parse-complete` reports. Re-verified by doing it — + appending `))) foo bar (((` to `specs/account/auth.t27` produces + + ``` + UNEXPECTED FAILURES: 1 + + specs/account/auth.t27 [parse] + ``` + + A document that lists what a green run does not cover is the last place a + stale claim should sit: it is read exactly when someone is deciding how far to + trust a pass. - **Seal staleness is reported, not gated.** 1056 of 1064 seals are stale and ~940 carry an unchanged `spec_hash`; a ledger over golden-file drift would be debt, not a defect list. -- **`cargo test` is not run by `suite`.** There are 5 standing unit-test - failures that have never appeared in any suite total. +- **`cargo test` is not run by `suite`.** It still is not — but the five + standing unit-test failures this bullet used to name are gone: measured + 2026-08-29, `cargo test --no-fail-fast` is 2429 passed, 0 failed. The flag + matters: without it `cargo test` stops at the first failing binary, and every + total taken without it was partial. --- @@ -127,3 +144,69 @@ signal), T30 (attribution before amnesty), T31 (blessing on absence), T32–T33 phases cannot see). **φ² + φ⁻² = 3 | TRINITY** + +--- + +## The amnesty carries a number (W699, 2026-08-29) + +An entry is an identity — `(path, phase)` — and for six phases that is the whole +truth: a spec either parses or it does not. For `parse-no-discard` it is not. +That phase's failure message has always carried a magnitude: + +``` +parser reached EOF but DISCARDED 208 top-level token(s); they never reach codegen +``` + +and the ledger threw the number away. A spec could go from discarding one token +to discarding six hundred and eighty-two without moving a gate. The blindness +runs both ways: two parser fixes on 2026-08-29 recovered **1 292 tokens** across +the corpus and nothing could price it, because the population was 87 either way. + +So `parse-no-discard` entries now carry `discard_tokens`, and the ratchet +compares it: + +```json +{ + "path": "specs/isa/ternary_deque.t27", + "phase": "parse-no-discard", + "discard_tokens": 1873 +} +``` + +Three rules, matching the ones already here rather than inventing new ones: + +- **more is a failure** — the regression signal that did not exist +- **less is also a failure** — same reason an unexpected PASS is one. Unclaimed + slack is where the next regression hides. Re-bless to pin the lower number. +- **no reading is treated as WORSE, never as an improvement.** A spec that + stopped being measured and a spec that discards nothing look identical from + the ratchet's side, and defaulting the map to zero would have reported every + unreadable spec as a triumph. + +`t27c suite --bless-expectations` is still the only writer, and it writes what +the run measured — so lowering is automatic on a re-bless, and raising is a diff +a human reads. + +### How this compares to the field + +Notion's eslint ratchet records per-file how many exceptions are allowed and +**decreases the counts automatically** as issues are fixed. That is the same +shape one decision apart: there, an improvement silently tightens the bound; +here it fails the run until someone blesses it. + +The difference is deliberate and it is the same choice `xfail_strict` makes. An +automatic tightening is invisible in review — nobody sees the improvement, and +nobody notices when the tool tightens the wrong thing. A failing run that says +*"you fixed something, pin it"* costs one command and produces a diff. + +### Finding the next one + +```bash +tri discard top --n 15 +``` + +Ranked by tokens thrown away, with the pinned bound beside each. The count of 87 +does not say where to start; `specs/isa/ternary_deque.t27` at 1 873 tokens does. +It reads `t27c parse-complete` rather than re-implementing the accounting: a +second implementation of a measurement is a second number to disagree with the +first. diff --git a/docs/now/2026-08-29-the-amnesty-carries-a-number-now.md b/docs/now/2026-08-29-the-amnesty-carries-a-number-now.md new file mode 100644 index 0000000000..ecff858a2c --- /dev/null +++ b/docs/now/2026-08-29-the-amnesty-carries-a-number-now.md @@ -0,0 +1,8 @@ +# NOW -- The amnesty carries a number now (2026-08-29) + +## The amnesty carries a number now (Refs #2754) + +- parse-no-discard entries were an identity, so a spec could go from 1 discarded token to 682 without moving a gate -- and 1292 recovered tokens could not be priced +- discard_tokens pinned per entry: more fails, LESS fails too (slack is where the next regression hides), and no reading is treated as worse rather than as an improvement +- correction: I claimed parse-no-discard sits in the suite's BLOCKED column. It does not -- 87 primary corpus failures, every one amnestied by name +- tri discard top ranks them: 87 does not say where to start, ternary_deque at 1873 tokens does diff --git a/docs/reports/suite_expectations.json b/docs/reports/suite_expectations.json index 2aad496e88..2e6e17e069 100644 --- a/docs/reports/suite_expectations.json +++ b/docs/reports/suite_expectations.json @@ -78,21 +78,24 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42). Reached this phase for the first time when the generic-application parse landed -- previously this spec failed `parse` outright.", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 208 }, { "path": "specs/base/ternary_encoding.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 472 }, { "path": "specs/base/ternary_memory.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 314 }, { "path": "specs/benchmarks/bench_main.t27", @@ -155,7 +158,8 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 30 }, { "path": "specs/compiler/typechecker.t27", @@ -190,14 +194,16 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 62 }, { "path": "specs/demos/jones_topology_filter.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 16 }, { "path": "specs/enrichment/audio_overview.t27", @@ -225,35 +231,40 @@ "phase": "parse-no-discard", "reason": "unclassified: blessed by --bless-expectations", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 43 }, { "path": "specs/fpga/mac.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 37 }, { "path": "specs/fpga/power_analysis.t27", "phase": "parse-no-discard", "reason": "parses now, but the parse discards tokens (parse-no-discard)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 3 }, { "path": "specs/fpga/spi.t27", "phase": "parse-no-discard", "reason": "unclassified: blessed by --bless-expectations", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 72 }, { "path": "specs/fpga/testbench/bootrom_tb.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1 }, { "path": "specs/fpga/testbench/mac_tb.t27", @@ -288,21 +299,24 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 46 }, { "path": "specs/fpga/uart.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 54 }, { "path": "specs/fpga/vcd_conformance_compare.t27", "phase": "parse-no-discard", "reason": "parses now, but the parse discards tokens (parse-no-discard)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 120 }, { "path": "specs/github/auth.t27", @@ -358,245 +372,280 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 516 }, { "path": "specs/igla/coder/arch.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 331 }, { "path": "specs/igla/coder/bench_proxy.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 738 }, { "path": "specs/igla/coder/benchmark.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 438 }, { "path": "specs/igla/coder/dataset.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 415 }, { "path": "specs/igla/coder/eval.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 441 }, { "path": "specs/igla/coder/pipeline.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 517 }, { "path": "specs/igla/coder/prm.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 521 }, { "path": "specs/igla/coder/tokenizer.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 250 }, { "path": "specs/igla/coder/training.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 647 }, { "path": "specs/igla/coder/weights.t27", "phase": "parse-no-discard", "reason": "unclassified: blessed by --bless-expectations", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 319 }, { "path": "specs/igla/evaluation/multi_lang_harness.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 88 }, { "path": "specs/igla/integration/publication.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 55 }, { "path": "specs/igla/race/adder_tree.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1069 }, { "path": "specs/igla/race/backend.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 481 }, { "path": "specs/igla/race/bram_weights.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1007 }, { "path": "specs/igla/race/cordic.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 583 }, { "path": "specs/igla/race/cordic_fixed.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 213 }, { "path": "specs/igla/race/cordic_top.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1238 }, { "path": "specs/igla/race/eda.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 600 }, { "path": "specs/igla/race/formal.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 485 }, { "path": "specs/igla/race/gemm.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 757 }, { "path": "specs/igla/race/opcodes.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 323 }, { "path": "specs/igla/race/rtl.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 628 }, { "path": "specs/igla/race/systolic_array.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1041 }, { "path": "specs/igla/race/systolic_ternary.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1409 }, { "path": "specs/igla/race/ternary_dot_sw.t27", "phase": "parse-no-discard", "reason": "unclassified: blessed by --bless-expectations", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 205 }, { "path": "specs/igla/race/ternary_gemm.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1566 }, { "path": "specs/igla/race/ternary_inference.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1813 }, { "path": "specs/igla/race/ternary_mac.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1139 }, { "path": "specs/igla/race/yosys.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 584 }, { "path": "specs/igla/training/low_bit_ternary.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 46 }, { "path": "specs/igla/training/pilot_pretraining.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 33 }, { "path": "specs/igla/training/roadmap.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 98 }, { "path": "specs/igla/training/scale_up.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 50 }, { "path": "specs/interop/gf_cross_language.t27", @@ -610,35 +659,40 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 148 }, { "path": "specs/isa/ternary_arithmetic.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 907 }, { "path": "specs/isa/ternary_bitwise.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1223 }, { "path": "specs/isa/ternary_control_flow.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 30 }, { "path": "specs/isa/ternary_deque.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 1873 }, { "path": "specs/isa/ternary_encoding.t27", @@ -652,28 +706,32 @@ "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 737 }, { "path": "specs/isa/ternary_memory.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 211 }, { "path": "specs/isa/ternary_shift.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (pre-existing; the file is byte-identical to its pre-image)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 705 }, { "path": "specs/jit/jit.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42). Reached this phase for the first time when the generic-application parse landed -- previously this spec failed `parse` outright.", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 22 }, { "path": "specs/lsp/language.t27", @@ -687,7 +745,8 @@ "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (forall-quantified properties)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 159 }, { "path": "specs/math/gf_competitive.t27", @@ -701,56 +760,64 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 129 }, { "path": "specs/math/phi_universal_attractor.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 73 }, { "path": "specs/math/property_test_template.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 238 }, { "path": "specs/math/radix_economy.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (forall-quantified properties)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 63 }, { "path": "specs/math/sacred_physics.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 48 }, { "path": "specs/memory/notebooklm.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 467 }, { "path": "specs/ml/optimizer/race_config.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 4 }, { "path": "specs/ml/transformer/feed_forward.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42). Reached this phase for the first time when the generic-application parse landed -- previously this spec failed `parse` outright.", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 62 }, { "path": "specs/ml/transformer/multi_head_attn.t27", @@ -764,7 +831,8 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42). Reached this phase for the first time when the generic-application parse landed -- previously this spec failed `parse` outright.", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 109 }, { "path": "specs/neural/forward_pass.t27", @@ -778,21 +846,24 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 417 }, { "path": "specs/nn/gla.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 151 }, { "path": "specs/nn/hslm.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 153 }, { "path": "specs/nn/phi_rope.t27", @@ -827,35 +898,40 @@ "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens; newly visible now that the parenthesised range for parses", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 31 }, { "path": "specs/numeric/gf20.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens; newly visible now that the parenthesised range for parses", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 31 }, { "path": "specs/numeric/gf24.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens; newly visible now that the parenthesised range for parses", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 31 }, { "path": "specs/numeric/gf32.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens; newly visible now that the parenthesised range for parses", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 31 }, { "path": "specs/numeric/gf8.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens; newly visible now that the parenthesised range for parses", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 37 }, { "path": "specs/numeric/goldenfloat_family.t27", @@ -869,21 +945,24 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 63 }, { "path": "specs/numeric/phi_ratio.t27", "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (forall-quantified properties)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 145 }, { "path": "specs/numeric/posit_ladder_control.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 148 }, { "path": "specs/physics/chimera_best_gamma.t27", @@ -974,14 +1053,16 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 706 }, { "path": "specs/queen/lotus.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 122 }, { "path": "specs/runtime/execute.t27", @@ -1114,7 +1195,8 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42). Reached this phase for the first time when the generic-application parse landed -- previously this spec failed `parse` outright.", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 6 }, { "path": "specs/ternary/hybrid_arithmetic.t27", @@ -1191,7 +1273,8 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 48 }, { "path": "specs/tri/collections/bitset.t27", @@ -1212,14 +1295,16 @@ "phase": "parse-no-discard", "reason": "parser reaches EOF but DISCARDS top-level tokens (forall-quantified properties)", "issue": 2474, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 302 }, { "path": "specs/vsa/ops.t27", "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 40 }, { "path": "specs/vsa/packed_vsa.t27", @@ -1233,7 +1318,8 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 278 }, { "path": "specs/vsa/sequence_hdc.t27", @@ -1247,7 +1333,8 @@ "phase": "parse-no-discard", "reason": "top-level drop-recovery discards tokens; parser reaches EOF so `parse` reports success (T42)", "issue": 1959, - "expires": "2026-11-30" + "expires": "2026-11-30", + "discard_tokens": 415 } ] }