Skip to content

Spec Explorer: 81 of the 338 "broken" specs were never specs - #1089

Open
gHashTag wants to merge 1 commit into
mainfrom
feat/source-kind-chip
Open

gHashTag wants to merge 1 commit into
mainfrom
feat/source-kind-chip

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

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 .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 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 classify answers "is this file source?" before the parser is asked. 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 and needs that PR merged first.

Result

health: 1001 ok · 176 warn · 156 fail   (of 1333 modules)
not a module: 121  — 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. Nothing is hidden: a ◇ Not a module chip carries the 121, and the five chips still add up to All.

It is an axis, not a fourth health state

notSource.byHealth is {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 why sourceKind is a second field rather than a fourth value of health, and why Health was 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.mjs asserted HEALTH_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 while SPEC_COUNT said 1454 — green, and publishing a number that accounts for 121 fewer files than it claims.

HEALTH_FAIL_UNPARSED, HEALTH_FAIL_JS_ONLY and HEALTH_PARTIAL are 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, and isSourceEntry is imported from t27-corpus.mjs rather 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 that filtered and tagCounts had 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-level Semicolon, and ~30 distinct one-offs. Two general parser gaps are worth filing on their own (}; after a struct/enum declaration, and multi-line r#"…"# 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 reports spec tests 9, asserts 51, all hold.
  • npx tsc --noEmit — 0 errors.
  • public/t27/ regenerated by sync-t27-specs.mjs against the t27 branch, including the vendored t27_compiler.wasm that 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."
    ]
  }
}

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>
@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Sep 21, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:in-progress 🔵 Agent working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant