Conversation
The board said `✕ Broken 338`. It was counting Markdown prose that happens to
carry the .t27 extension, TRI-27 assembly, the older `spec Name { }` dialect,
and the fixtures t27 damages on purpose. A parser asked to parse a document
fails, correctly, and the chip then reported that correct failure as a defect
in the corpus.
The compiler has always known the difference -- `t27c classify` answers "is
this file source?" before the parser is asked -- but the wasm bridge exposed
only analyze_source, so nothing downstream could ask. gHashTag/t27#4541 lifts
that rule into bootstrap/src/source_kind.rs and publishes it as `sourceKind`.
This is the consumer side.
What changed here:
corpusEntry carries sourceKind through, defaulting to `source`. A file
leaves the module count only when the classifier positively says it is not
one; an absent field never silently shrinks the denominator.
corpusAggregates counts health over modules and reports the rest as
`notSource` -- total, byKind and byHealth. The last of those is the evidence
that this is a separate axis rather than a fourth health state: 81 of these
files fail, and 31 of them compile cleanly.
SpecExplorer gains a `◇ Not a module` chip. Nothing is hidden: the four
health chips plus this one still add up to All. `inGroup()` replaces the
filter predicate that both `filtered` and `tagCounts` had been spelling out
separately, which is how the two could have disagreed.
The onboarding gate now checks a four-term sum. Adding the term rather than
dropping it matters -- OK+WARN+FAIL alone would hold over 1333 files while
SPEC_COUNT said 1454, green and wrong. HEALTH_FAIL_UNPARSED, _JS_ONLY and
HEALTH_PARTIAL are module-only for the same reason: over every entry the
first counts 228 against a HEALTH_FAIL of 156, and a subset larger than its
set is a denominator mismatch wearing the clothes of a new fact. The gate
now says so directly instead of leaving it to be noticed.
isSourceEntry is imported, not retyped. Two copies of that predicate is
exactly how the two denominators drift apart again.
Measured after regeneration against the new compiler:
1001 ok · 176 warn · 156 fail (of 1333 modules)
121 not a module: unclassified 66 · not-code 46 · alt-syntax 8 · mixed 1
1001 + 176 + 156 + 121 = 1454 ✓
Broken falls 218 -> 156 while the corpus itself grew by 35 files. The 156 that
remain are real: genuine module-level parse failures, a long tail rather than
a few shared gaps, and they are the next piece of work.
public/t27/ is regenerated by sync-t27-specs.mjs against the t27 branch,
including the vendored t27_compiler.wasm that carries the classifier.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The live board reads
◆ Course 9 · ✓ Working 976 · ⚠ Warnings 100 · ✕ Broken 338 · All 1414.81 of that 338 are not modules. Markdown prose that happens to carry the
.t27extension, TRI-27 assembly, the olderspec Name { }dialect, and the fixtures t27 damages on purpose. A parser asked to parse a document fails, correctly — and the chip reported that correct failure as a defect in our corpus, on the public face of the project.The compiler has always known the difference:
t27c classifyanswers "is this file source?" before the parser is asked. The wasm bridge exposed onlyanalyze_source, so nothing downstream could ask. gHashTag/t27#4541 lifts that rule intobootstrap/src/source_kind.rsand publishes it assourceKind. This is the consumer side and needs that PR merged first.Result
Broken falls 218 → 156 while the corpus itself grew by 35 files. Nothing is hidden: a
◇ Not a modulechip carries the 121, and the five chips still add up to All.It is an axis, not a fourth health state
notSource.byHealthis{ok: 31, warn: 9, fail: 81}. Eighty-one of these files fail — and thirty-one compile cleanly. Is this a module and does it compile are two questions, and the corpus answers them independently. That is whysourceKindis a second field rather than a fourth value ofhealth, and whyHealthwas not widened in the other four files that declare it: crons, skills and agents legitimately have three states.The gate had to learn the new denominator
onboarding-from-spec.mjsassertedHEALTH_OK + HEALTH_WARN + HEALTH_FAIL === SPEC_COUNT. Adding the fourth term rather than dropping it is load-bearing: the three-term sum would have held over 1333 files whileSPEC_COUNTsaid 1454 — green, and publishing a number that accounts for 121 fewer files than it claims.HEALTH_FAIL_UNPARSED,HEALTH_FAIL_JS_ONLYandHEALTH_PARTIALare now module-only for the same reason. Over every entry the first counts 228 against a HEALTH_FAIL of 156 — a subset larger than its set, which is a denominator mismatch wearing the clothes of a new fact. The gate now says that in words rather than leaving it to be noticed, andisSourceEntryis imported fromt27-corpus.mjsrather than retyped, because two copies of that predicate is precisely how the two denominators drift apart again.Also folded in
inGroup()replaces the filter predicate thatfilteredandtagCountshad each been spelling out separately — two copies of one rule, which is how a chip's count and its contents could have disagreed.What this does not fix
The 156 that remain are real: genuine module-level parse failures. Measured, they are a long tail rather than a few shared gaps — 55 module-level
Ident, 26 unterminated string, 13 module-levelSemicolon, and ~30 distinct one-offs. Two general parser gaps are worth filing on their own (};after a struct/enum declaration, and multi-liner#"…"#raw strings, which break because#is a t27 line comment). That is the next piece of work, not this PR.Verification
npm run build— exit 0. The onboarding gate reportsspec tests 9, asserts 51, all hold.npx tsc --noEmit— 0 errors.public/t27/regenerated bysync-t27-specs.mjsagainst the t27 branch, including the vendoredt27_compiler.wasmthat carries the classifier.🤖 Generated with Claude Code
{ "version": 1, "head_sha": "c36d330fc96334453af128d71f19360096d5420f", "summary": "The Spec Explorer counted 81 non-modules among its 338 broken specs; the compiler's own classifier is now published to the browser and those files are reported as their own group instead of as defects.", "changes": [ "corpusEntry carries sourceKind through from the compiler, defaulting to source so an absent field never silently shrinks the module denominator", "corpusAggregates counts the three health states over modules only and reports the rest as notSource with total, byKind and byHealth", "SpecExplorer gains a Not-a-module chip; the five chips still add up to All, so nothing is hidden by the reclassification", "inGroup() replaces the filter predicate that filtered and tagCounts had each been spelling out separately", "The onboarding gate checks a four-term sum and makes HEALTH_FAIL_UNPARSED, HEALTH_FAIL_JS_ONLY and HEALTH_PARTIAL module-only, so a subset can no longer exceed its set", "isSourceEntry is imported from t27-corpus.mjs rather than retyped, because two copies of that predicate is how the two denominators drift apart again", "public/t27/ regenerated against the t27 branch, including the vendored t27_compiler.wasm that carries the classifier" ], "tests": [ { "command": "npm run build", "result": "Exit code 0; the full generate-and-bundle chain completed", "status": "passed", "evidence": "The onboarding gate printed 'spec tests 9, asserts 51, all hold' and vite reported 'built in 13.61s'; the same command was exit 1 before the gate was taught the new denominator." }, { "command": "npx tsc --noEmit", "result": "Zero type errors across the website", "status": "passed", "evidence": "The new SourceKind union and the widened SpecGroup state type check against every existing call site without a cast." }, { "command": "node scripts/sync-t27-specs.mjs with T27_ROOT pointing at the classifier branch", "result": "1454 entries; the four groups reconcile exactly", "status": "passed", "evidence": "Reported 1001 ok, 176 warn, 156 fail over 1333 modules plus 121 not-a-module, and 1001+176+156+121 equals the 1454 the manifest claims." }, { "command": "Cross-tabulate health against sourceKind over the corpus", "result": "81 non-modules fail and 31 compile cleanly", "status": "passed", "evidence": "notSource.byHealth reads ok 31, warn 9, fail 81, which is the evidence that being a module and compiling are independent questions rather than one four-valued state." } ], "limitations": [ "The 156 remaining failures are genuine module-level parse errors and this PR fixes none of them; measured, they are a long tail of roughly thirty distinct causes rather than a few shared gaps.", "Two general parser gaps found while measuring are not addressed here and deserve their own issues: a semicolon after a struct or enum declaration, and multi-line raw strings, which break because the hash character opens a t27 line comment.", "This PR depends on gHashTag/t27#4541; until that merges, the vendored wasm here is built from a branch rather than from master.", "The Health type is still declared separately in five files. Widening it was deliberately avoided because crons, skills and agents genuinely have three states, but the duplication itself remains unfixed.", "Counts are a snapshot of the corpus at this commit and will move as upstream repositories land specs; the gate compares the document against the manifest on every build rather than trusting these figures." ], "tags": ["t27", "compiler", "tooling", "observability"], "blog": { "title": "Eighty-one of the broken specs were never specs at all", "summary": "A public dashboard reported 338 broken specifications. A quarter of them were Markdown documents, assembly listings and fixtures broken on purpose, and the compiler had known the difference the whole time.", "outline": [ "The Spec Explorer on the project's public site showed a red chip reading 338 broken. That number was doing real work: it is the first thing a visitor sees about the health of the corpus, and it was wrong in a way that flattered nobody.", "A file carrying the .t27 extension is not necessarily a t27 module. The corpus contains Markdown prose, TRI-27 assembly listings, an older dialect of the specification syntax, and a directory of fixtures that the compiler's own test suite damages deliberately. A parser asked to parse a document fails, and it is right to fail.", "The compiler already had the answer. Its classify subcommand decides whether a file is source before the parser is ever invoked. But the WebAssembly bridge that the browser talks to exposed only the analysis entry point, so nothing downstream of it could ask the question.", "The repair was to move the rule rather than copy it. The classifier now lives in its own module, and both the command-line tool and the WebAssembly bridge include that same file directly, so there is no second copy to drift out of step with the first.", "The corpus then settled an open question about the design. If non-modules were simply a fourth kind of failure, they would all fail. They do not: eighty-one fail and thirty-one compile cleanly. Whether a file is a module and whether it compiles are two independent questions, so they became two fields rather than one.", "Teaching the consistency gate about the change mattered more than the change itself. The document that describes the corpus asserted that three health counts sum to the total. Adding a fourth term was load-bearing, because the three-term sum would have gone on holding over a smaller population while the published total claimed a larger one.", "What remains is the honest number. A hundred and fifty-six modules genuinely fail to parse, and they are a long tail of distinct causes rather than a handful of shared gaps. That is a smaller headline and a harder problem, which is the usual shape of a measurement getting more accurate." ] } }