From 4a28f7a7321e077f9e07b3ef63312ee3efec5eed Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 01:36:07 +0700 Subject: [PATCH 1/3] Count the seals that record no generation: 213 of 1311 A seal stores a spec's hash and the sha256 of what the compiler generates from it for four backends. When the spec does not parse, `t27c seal` exits 0 and writes `gen_hash=none` four times. Every check then agrees with it. Freshness compares spec_hash: it matches. Drift recomputes and compares: `none` equals `none`, zero drift, and the summary reads "Every seal of every spec that exists matches what the compiler produces from it right now". Coverage sees a seal file and counts the spec as covered. Three correct checks, three greens, and the record says generation did not happen. The repository already refuses to WRITE one -- `is_sealable()` guards `drift --fix`, `sync-twins` will not propagate one -- and nothing read the ones on disk. tri seals hollow the census, by directory tri seals hollow --why the compiler's error on each, grouped by kind 104 distinct specs, 39 error kinds, largest covering 23: a dozen parser gaps, not 104 repairs. Three of the seals name files that are not specs at all -- two Markdown and a `.tri`. Also: `t27c validate` printed "VALIDATION: FAILED" and exited 0. It is the command that produced the first reading here. No workflow runs it, which is why this was never noticed; the reason to fix it anyway is that whoever wires it up next would inherit a gate that cannot fail. FAILED now exits 1, PASSED still exits 0, both verified. Controls: a real seal forced to `none` moves the count 213 -> 214; a hollow seal given four hashes moves it 213 -> 212. Five unit tests on the error-kind normaliser, including the one that catches over-merging. Refs #2864, #2767, #2774 Co-Authored-By: Claude Opus 5 --- .claude/skills/ci-gates/SKILL.md | 56 ++++++ bootstrap/src/main.rs | 5 + cli/tri/src/seals.rs | 293 ++++++++++++++++++++++++++++++- 3 files changed, 352 insertions(+), 2 deletions(-) diff --git a/.claude/skills/ci-gates/SKILL.md b/.claude/skills/ci-gates/SKILL.md index 41e0aacc17..78aa0fb268 100644 --- a/.claude/skills/ci-gates/SKILL.md +++ b/.claude/skills/ci-gates/SKILL.md @@ -7788,3 +7788,59 @@ Controlled both ways: removing a kind's entry gives `MISSING: gen-drift` and exit 1; adding an entry nothing attaches gives `UNREACHABLE: invented-kind` and exit 1. **An explanation for a state that cannot occur is the same defect as a state with no explanation** — one wastes the reader, the other strands them. + +## 273. `none == none` is agreement, not health + +A seal in this repository stores a spec's hash and the sha256 of each of four +generated outputs. When the spec does not parse, `t27c seal` exits 0 and writes +`gen_hash_zig=none` — four times. Then: + +- freshness compares `spec_hash` against the file: it matches. +- drift recomputes and compares: `none` equals `none`, zero drift. +- coverage checks a seal file exists and its hashes agree: covered. + +All three are correct. All three are green. And the file records that +generation did not happen. **213 of 1311 seals on master.** + +The general shape: when a computation can fail, and the failure is written down +as a sentinel, every EQUALITY check downstream compares the sentinel against +itself and agrees. The sentinel is invisible precisely to the checks built to +notice change, because it never changes. + +Two defences, and only the second one worked here: + +1. **Refuse to write it.** This repository already does — `is_sealable()` + blocks `drift --fix`, and `sync-twins` will not propagate one. It is the + right guard and it is worthless for the records already on disk. +2. **Count the sentinels, separately, as their own question.** Not "did this + change?" but "how many of these claim nothing?" Nothing asked that until + `tri seals hollow`, and the answer was six times larger than anyone would + have guessed. + +If a value in your data means *absence*, write the census before you write the +comparator. The comparator cannot see it, and will report health forever. + +## 274. The kind, not the coordinates + +104 specs failed to parse. Reported one line each, that is 104 problems and an +owner who does not start. Reported by error kind — with `near line 38`, `at +line 38:45` and `in fn 'git_commit'` stripped — it is 39 kinds, the largest +covering 23 specs, and the work is a dozen parser gaps. + +Normalising is where this goes wrong. The compiler nests its own prefix: + +``` +parse error in fn 'f' near line 100: parse error near line 100: parse error near line 100: Expected RParen +``` + +A collapse that only merges ADJACENT repeats leaves two, because the compiler +interleaves `in fn X` between them. The unit test that caught it asserted the +count, not the appearance: + +```rust +assert_eq!(k.matches("parse error").count(), 1, "{k}"); +``` + +And its control — two DIFFERENT causes must not collapse into one bucket — +matters more than the merge test. Over-merging turns 39 kinds into 4 and reads +as excellent grouping. diff --git a/bootstrap/src/main.rs b/bootstrap/src/main.rs index 491ae89a4d..43f8b11828 100644 --- a/bootstrap/src/main.rs +++ b/bootstrap/src/main.rs @@ -9572,6 +9572,11 @@ fn run_validate(repo_root: &str) -> anyhow::Result<()> { println!("VALIDATION: PASSED"); } else { println!("VALIDATION: FAILED"); + // The word FAILED and a zero exit are two answers to one question, and + // a caller that reads the code hears the wrong one. No workflow runs + // this command today; the reason it is worth fixing anyway is that the + // next one to wire it up would inherit a gate that cannot fail. + std::process::exit(1); } Ok(()) } diff --git a/cli/tri/src/seals.rs b/cli/tri/src/seals.rs index 20eaf0b3f3..cb5e751ce1 100644 --- a/cli/tri/src/seals.rs +++ b/cli/tri/src/seals.rs @@ -81,6 +81,27 @@ pub enum SealsCmd { #[arg(long)] dry_run: bool, }, + /// Seals that record `gen_hash=none` -- a spec that produced no output, sealed. + /// + /// Two commands already REFUSE to write one: `drift --fix` calls + /// `is_sealable`, and `sync-twins` will not propagate one. Nothing counts + /// the ones already on disk, and every check reads them as healthy: the + /// spec_hash matches, so freshness passes; `none` equals `none`, so drift + /// reports zero; the file exists, so coverage counts it as covered. + /// + /// What such a seal actually records is that generation did not happen. + /// Measured on this repository the day the command was written: 218 seals + /// of 1316, and the split is total -- either all four hashes are real or + /// all four are `none`, never a mix, because a spec that fails to parse + /// fails all four backends at once. + Hollow { + /// Also run the compiler on each spec and group the parse errors. + /// + /// Turns a list of specs into a list of CAUSES. Costs one compiler + /// invocation per distinct spec. + #[arg(long)] + why: bool, + }, } /// The five fields a seal makes a claim with. A pair that agrees on all five is @@ -197,7 +218,9 @@ pub fn binary_is_stale(root: &std::path::Path, bin: &std::path::Path) -> Option< let mut newest = bin_t; let mut stack = vec![root.join("bootstrap/src")]; while let Some(d) = stack.pop() { - let Ok(rd) = std::fs::read_dir(&d) else { continue }; + let Ok(rd) = std::fs::read_dir(&d) else { + continue; + }; for e in rd.flatten() { let p = e.path(); if p.is_dir() { @@ -396,6 +419,174 @@ pub fn run(cmd: &SealsCmd) -> Result<()> { println!(" listed above is not consulted and its age decides nothing."); return Ok(()); } + SealsCmd::Hollow { why } => { + let by = collect(&dir)?; + // claims[0] is spec_hash; the four that follow are the generation + // claims, and only those decide hollowness. A seal whose spec_hash + // is `none` is a different, louder kind and is not this command's. + let mut hollow: Vec<(&String, &String)> = Vec::new(); + let mut total = 0usize; + for (spec, seals) in by.iter() { + for (name, c) in seals { + total += 1; + if c.len() == CLAIMS.len() && c[1..].iter().all(|v| v.trim() == "none") { + hollow.push((spec, name)); + } + } + } + let mut specs: Vec<&String> = hollow.iter().map(|(s, _)| *s).collect(); + specs.sort(); + specs.dedup(); + let (present, missing): (Vec<&String>, Vec<&String>) = + specs.iter().partition(|s| root.join(s.as_str()).is_file()); + + println!( + " seals that claim no generation {} of {total}", + hollow.len() + ); + println!(" distinct specs behind them {}", specs.len()); + if !missing.is_empty() { + println!( + " ... whose spec is gone {} (dangling: the gate's kind)", + missing.len() + ); + } + if hollow.is_empty() { + println!(); + println!(" Every seal on disk names four hashes. Nothing here records a"); + println!(" generation that did not happen."); + return Ok(()); + } + + println!(); + println!(" A hollow seal passes every check this repository has. spec_hash"); + println!(" matches the file, so `seals fresh` is green. `none` equals `none`,"); + println!(" so `seals drift` reports zero. The file exists, so Seal Coverage"); + println!(" counts the spec as covered. The one thing it does not record is"); + println!(" the output, because there was none."); + + // A seal on a file that is not a spec at all. Found by this census on + // its first run: two of them name Markdown. `none` is the honest + // answer for a .md, and the question should not have been asked. + let odd: Vec<&&String> = present.iter().filter(|s| !s.ends_with(".t27")).collect(); + if !odd.is_empty() { + println!(); + println!(" sealed, but not a spec {}", odd.len()); + for s in odd.iter().take(6) { + println!(" {s}"); + } + if odd.len() > 6 { + println!(" ... and {} more", odd.len() - 6); + } + } + + let mut dirs: BTreeMap = BTreeMap::new(); + for s in &specs { + let top = s.split('/').take(2).collect::>().join("/"); + *dirs.entry(top).or_default() += 1; + } + let mut rows: Vec<(&String, &usize)> = dirs.iter().collect(); + rows.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0))); + println!(); + println!(" where they are"); + for (d, n) in rows.iter().take(10) { + println!(" {n:>4} {d}"); + } + if rows.len() > 10 { + println!( + " ... and {} more director{}", + rows.len() - 10, + if rows.len() - 10 == 1 { "y" } else { "ies" } + ); + } + + if !*why { + println!(); + println!(" --why runs the compiler on each and groups the errors: it turns"); + println!( + " {} specs into the handful of parser gaps behind them.", + present.len() + ); + return Ok(()); + } + + let bin = ["target/release/t27c", "target/debug/t27c"] + .iter() + .map(|p| root.join(p)) + .find(|p| p.is_file()); + let Some(bin) = bin else { + anyhow::bail!( + "--why needs a compiler, and its absence is not a clean bill.\n \ + cargo build --release -p t27c" + ); + }; + let mut kinds: BTreeMap = BTreeMap::new(); + let mut parsed_fine = 0usize; + for spec in &present { + let out = std::process::Command::new(&bin) + .arg("check") + .arg(spec.as_str()) + .current_dir(&root) + .output(); + let Ok(out) = out else { continue }; + if out.status.success() { + parsed_fine += 1; + continue; + } + let text = String::from_utf8_lossy(&out.stderr); + let line = text.lines().find(|l| !l.trim().is_empty()).unwrap_or(""); + *kinds.entry(error_kind(line)).or_default() += 1; + } + let mut ks: Vec<(&String, &usize)> = kinds.iter().collect(); + ks.sort_by(|a, b| b.1.cmp(a.1).then(a.0.cmp(b.0))); + let named: usize = ks.iter().take(12).map(|(_, n)| **n).sum(); + println!(); + println!( + " why they are hollow -- {} spec{}, {} distinct error{}", + present.len(), + if present.len() == 1 { "" } else { "s" }, + ks.len(), + if ks.len() == 1 { "" } else { "s" } + ); + for (k, n) in ks.iter().take(12) { + println!(" {n:>4} {k}"); + } + if ks.len() > 12 { + println!( + " ... {} more kind{}, {} spec{} between them", + ks.len() - 12, + if ks.len() - 12 == 1 { "" } else { "s" }, + present + .len() + .saturating_sub(named) + .saturating_sub(parsed_fine), + if present + .len() + .saturating_sub(named) + .saturating_sub(parsed_fine) + == 1 + { + "" + } else { + "s" + } + ); + } + if parsed_fine > 0 { + println!(); + println!( + " {parsed_fine} of them PARSE today. Their seal was written when they" + ); + println!(" did not, and no re-seal has happened since. `seals drift` cannot"); + println!(" see it: it compares `none` against a fresh reading only when the"); + println!(" compiler succeeds, and here the stored side is the stale one."); + } + println!(); + println!(" This command reports. Which of these specs should generate and which"); + println!(" are prose the compiler was never meant to accept is not a question a"); + println!(" counter may answer."); + return Ok(()); + } SealsCmd::Drift { fix } => { let bin = ["target/release/t27c", "target/debug/t27c"] .iter() @@ -436,7 +627,10 @@ pub fn run(cmd: &SealsCmd) -> Result<()> { } } - println!(" specs whose seals no longer describe them {}", drifted.len()); + println!( + " specs whose seals no longer describe them {}", + drifted.len() + ); if unreadable > 0 { println!(" NOT COMPUTED, nothing claimed {unreadable}"); } @@ -633,4 +827,99 @@ mod tests { let b = vec!["sha256:abc".to_string()]; assert_ne!(a, b); } + + // The invariant the hollow census rests on: the SAME cause at DIFFERENT + // coordinates is one kind. Without it, 104 specs read as 104 problems. + #[test] + fn same_cause_different_lines_is_one_kind() { + let a = "Error: parse error in fn 'git_commit' near line 38: Unexpected \ + token in expression: Pipe ('|') at line 38:45"; + let b = "Error: parse error in fn 'spec_show' near line 912: Unexpected \ + token in expression: Pipe ('|') at line 912:7"; + assert_eq!(error_kind(a), error_kind(b)); + assert!(!error_kind(a).contains("38"), "{}", error_kind(a)); + assert!(error_kind(a).contains("Pipe")); + } + + // The compiler nests its own prefix; seen three deep on specs/hslm. + #[test] + fn nested_prefixes_collapse() { + let k = error_kind( + "Error: parse error in fn 'f' near line 100: parse error near line 100: \ + parse error near line 100: Expected RParen, got Bang ('!')", + ); + assert!(!k.contains("100"), "{k}"); + assert!(k.contains("Expected RParen"), "{k}"); + assert_eq!(k.matches("parse error").count(), 1, "{k}"); + } + + // A different cause must NOT collapse into the same bucket -- the control + // that makes the test above mean something. + #[test] + fn different_causes_stay_apart() { + let a = error_kind("Error: parse error near line 1: Expected LBrace, got Semicolon (';')"); + let b = error_kind("Error: parse error near line 1: Expected LBrace, got LParen ('(')"); + assert_ne!(a, b); + } + + #[test] + fn a_message_without_coordinates_survives_whole() { + let m = "Error: unterminated string literal opened"; + assert_eq!(error_kind(m), m); + } + + // is_sealable is what REFUSES to write one; the census counts what is + // already written. They must agree on what "hollow" means. + #[test] + fn census_and_refusal_agree() { + let four_none: Vec = vec!["none".into(); 4]; + assert!(!is_sealable(&four_none)); + assert!(four_none.iter().all(|v| v.trim() == "none")); + } +} + +/// The shape of a parse error, with the coordinates removed. +/// +/// Two specs failing at different lines for the same reason are ONE gap in the +/// parser, and a census that reports them as two invites 98 fixes where a dozen +/// would do. The compiler nests its own prefix -- "parse error near line 100:" +/// appears three deep on some specs -- so every occurrence is stripped, not the +/// first. +fn error_kind(line: &str) -> String { + let mut s = line.trim().to_string(); + // "near line 100: " and " at line 36:38", wherever and however often. + loop { + let Some(i) = s.find("near line ") else { break }; + let rest = &s[i..]; + let Some(j) = rest.find(": ") else { + s.truncate(i); + break; + }; + s = format!("{}{}", &s[..i], &rest[j + 2..]); + } + if let Some(i) = s.find(" at line ") { + s.truncate(i); + } + // A function name is the location too. + while let Some(a) = s.find(" in fn '") { + let Some(b) = s[a + 8..].find("'") else { break }; + s = format!("{} in fn X{}", &s[..a], &s[a + 8 + b + 1..]); + } + // The nested prefix collapses to one. The repeats are NOT adjacent -- the + // compiler interleaves context ("in fn X") between them -- so a + // neighbour-only collapse leaves two, which is how this was caught. + const P: &str = "parse error"; + if let Some(first) = s.find(P) { + let head = s[..first + P.len()].to_string(); + let mut tail = s[first + P.len()..].to_string(); + while let Some(i) = tail.find(P) { + let mut rest = tail[i + P.len()..].to_string(); + while rest.starts_with(' ') || rest.starts_with(':') { + rest.remove(0); + } + tail = format!("{} {}", tail[..i].trim_end(), rest); + } + s = format!("{head}{tail}"); + } + s.trim().trim_end_matches(':').trim().to_string() } From d7223ffaa02b82574ab93c6f23f889de59386036 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 01:48:34 +0700 Subject: [PATCH 2/3] Reconcile the hollow census against the debt ledger it duplicated The first commit implied nothing counted these specs. That is wrong: `tools/specs_generate_baseline.txt` is a debt ledger of exactly this, opening with "Each line is a debt", and 101 of the 104 hollow-sealed specs are already in it. One grep before claiming novelty would have caught it. What survives measurement is narrower and still worth the tool: the same fact is recorded twice, one record calls it debt and the seal side reads it as covered, fresh and undrifted. So the census reconciles rather than competes: against tools/specs_generate_baseline.txt, which calls each line a debt 101 already recorded there 3 hollow seal, NOT in the ledger 2 in the ledger with no hollow seal The 3 are not specs -- two Markdown and a `.tri` -- and the ledger is right to omit them. The 2 have no seal file at all; checked, not inferred, and both still fail to parse, so it is a coverage gap and not a contradiction between the two records. The summary no longer says a hollow seal "passes every check this repository has". It passes every SEAL check, which is the accurate claim. Control: a line removed from the ledger moves 101 -> 100, tree restored clean. Two more unit tests: the ledger's error column carries pipes of its own on specs whose failing token IS `|`, so the path column splits on the FIRST pipe. Refs #2864 Co-Authored-By: Claude Opus 5 --- .claude/skills/ci-gates/SKILL.md | 13 ++++++ cli/tri/src/seals.rs | 75 +++++++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/.claude/skills/ci-gates/SKILL.md b/.claude/skills/ci-gates/SKILL.md index 78aa0fb268..eba850d4d2 100644 --- a/.claude/skills/ci-gates/SKILL.md +++ b/.claude/skills/ci-gates/SKILL.md @@ -7802,6 +7802,19 @@ generated outputs. When the spec does not parse, `t27c seal` exits 0 and writes All three are correct. All three are green. And the file records that generation did not happen. **213 of 1311 seals on master.** +**And the fact was already written down.** `tools/specs_generate_baseline.txt` +is a debt ledger of exactly these specs -- 101 of the 104 are in it, under a +header that says "Each line is a debt". The first version of this section +claimed nobody counted them; that was wrong, and grepping for an existing +ledger BEFORE claiming novelty would have caught it in one command. What is +true is narrower and still worth the tool: the same fact is recorded twice, one +record calls it debt and the other reads as health, and the census now +reconciles against the ledger instead of competing with it. + +The general rule this leaves: before reporting a count as new, search the +repository for a file that already holds it. A second, disagreeing ledger is +worse than no ledger. + The general shape: when a computation can fail, and the failure is written down as a sentinel, every EQUALITY check downstream compares the sentinel against itself and agrees. The sentinel is invisible precisely to the checks built to diff --git a/cli/tri/src/seals.rs b/cli/tri/src/seals.rs index cb5e751ce1..db7e0b0d4d 100644 --- a/cli/tri/src/seals.rs +++ b/cli/tri/src/seals.rs @@ -459,11 +459,42 @@ pub fn run(cmd: &SealsCmd) -> Result<()> { } println!(); - println!(" A hollow seal passes every check this repository has. spec_hash"); - println!(" matches the file, so `seals fresh` is green. `none` equals `none`,"); - println!(" so `seals drift` reports zero. The file exists, so Seal Coverage"); - println!(" counts the spec as covered. The one thing it does not record is"); - println!(" the output, because there was none."); + println!(" A hollow seal passes every SEAL check. spec_hash matches the file,"); + println!(" so `seals fresh` is green. `none` equals `none`, so `seals drift`"); + println!(" reports zero. The file exists, so Seal Coverage counts the spec as"); + println!(" covered. The one thing it does not record is the output."); + println!(); + println!(" Most of these are already written down as debt elsewhere -- the"); + println!(" reconciliation below says how many. The seal side is where the same"); + println!(" fact reads as health."); + + // The same fact is recorded in tools/specs_generate_baseline.txt, and + // THAT file calls it a debt. Reconcile against it rather than + // reporting a second, competing count: a census that duplicates an + // existing ledger is noise, and the interesting rows are the ones + // where the two disagree. + let ledger = std::fs::read_to_string(root.join("tools/specs_generate_baseline.txt")) + .ok() + .map(|t| ledger_paths(&t)); + if let Some(ledger) = &ledger { + let known = specs.iter().filter(|s| ledger.contains(**s)).count(); + println!(); + println!( + " against tools/specs_generate_baseline.txt, which calls each line a debt" + ); + println!(" {known:>4} already recorded there"); + println!( + " {:>4} hollow seal, NOT in the ledger", + specs.len() - known + ); + println!( + " {:>4} in the ledger with no hollow seal", + ledger + .iter() + .filter(|l| !specs.iter().any(|s| *s == *l)) + .count() + ); + } // A seal on a file that is not a spec at all. Found by this census on // its first run: two of them name Markdown. `none` is the honest @@ -876,6 +907,40 @@ mod tests { assert!(!is_sealable(&four_none)); assert!(four_none.iter().all(|v| v.trim() == "none")); } + + // The ledger's error column contains pipes of its own -- specs whose + // failing token IS `|`. Splitting on the last pipe, or on every pipe, + // silently drops those rows and the reconciliation under-reports. + #[test] + fn ledger_splits_on_the_first_pipe_only() { + let t = "# comment\n\ + specs/a.t27 | Error: Unexpected token in expression: Pipe ('|') at line 3:5\n\ + \n\ + specs/b.t27 | Error: Expected LBrace\n"; + let set = ledger_paths(t); + assert_eq!(set.len(), 2); + assert!(set.contains("specs/a.t27"), "{set:?}"); + assert!(set.contains("specs/b.t27"), "{set:?}"); + } + + #[test] + fn ledger_ignores_comments_and_blanks() { + assert!(ledger_paths("# only a comment\n\n \n").is_empty()); + } +} + +/// The spec paths in `tools/specs_generate_baseline.txt`. +/// +/// Each line is ` | `, and the file opens with +/// comments. Splitting on the FIRST pipe matters: the error text carries pipes +/// of its own on specs whose failing token is `|`. +fn ledger_paths(text: &str) -> std::collections::BTreeSet { + text.lines() + .map(|l| l.trim()) + .filter(|l| !l.is_empty() && !l.starts_with('#')) + .map(|l| l.split('|').next().unwrap_or("").trim().to_string()) + .filter(|l| !l.is_empty()) + .collect() } /// The shape of a parse error, with the coordinates removed. From a3515999e712637c8b88fde8d2712782be37b220 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 02:22:04 +0700 Subject: [PATCH 3/3] NOW entry for the hollow-seal census Refs rather than Closes #2864: the census is delivered, the 104 specs behind it are not repaired, and the owner calls in that issue stand. Refs #2864 --- ...-record-no-generation-and-every-seal-check-reads-th.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docs/now/2026-08-30-213-seals-record-no-generation-and-every-seal-check-reads-th.md diff --git a/docs/now/2026-08-30-213-seals-record-no-generation-and-every-seal-check-reads-th.md b/docs/now/2026-08-30-213-seals-record-no-generation-and-every-seal-check-reads-th.md new file mode 100644 index 0000000000..d89462f1fc --- /dev/null +++ b/docs/now/2026-08-30-213-seals-record-no-generation-and-every-seal-check-reads-th.md @@ -0,0 +1,8 @@ +# NOW -- 213 seals record no generation, and every seal check reads them as healthy (2026-08-30) + +## 213 seals record no generation, and every seal check reads them as healthy (Refs #2864) + +- tri seals hollow: 213 of 1311 spec seals carry gen_hash=none for all four backends -- a spec that does not parse, sealed. Freshness matches spec_hash, drift compares none against none and reports zero, coverage counts the file as covered. +- Reconciled against tools/specs_generate_baseline.txt rather than competing with it: 101 of the 104 specs are already recorded there as debt. What is new is that the same fact reads as health on the seal side. +- --why groups the compiler's error by kind with coordinates stripped: 39 kinds over 104 specs, largest covering 23. A dozen parser gaps, not 104 repairs. +- t27c validate printed VALIDATION: FAILED and exited 0; FAILED now exits 1, PASSED still exits 0.