From 7f9f71ee1a7686ff792b18b68b2bdf6da2f00bd3 Mon Sep 17 00:00:00 2001 From: Oscar Villavicencio <9220505+odvcencio@users.noreply.github.com> Date: Sun, 23 Aug 2026 13:50:02 -0700 Subject: [PATCH] add(wgsl): Add WGSL blocker receipt coverage - Add a cgo-gated WGSL probe for raw, production, compact, forest, incremental, and locked-C routes. - Record route digests, dispatcher receipts, divergence points, and reuse metrics for seven WGSL witnesses. - Document the WGSL blocker evidence and keep `dispatch.wgsl` live until all routes match locked C. - Guard the retirement ledger and changelog markers against accidental removal. Buckley-Change-Hash: sha256:8a1b5d713f72f9439389bef0bdf37d90fe1b721e0b93561417fcfd86a14c6d41 Buckley-Change-Stats: files=3 insertions=596 deletions=0 binaries=0 --- CHANGELOG.md | 11 + cgo_harness/wgsl_next_live_probe_test.go | 440 +++++++++++++++++++++++ docs/root-normalization-retirement.md | 145 ++++++++ 3 files changed, 596 insertions(+) create mode 100644 cgo_harness/wgsl_next_live_probe_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4120932..d99f95f54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -226,6 +226,17 @@ for tags and release notes while still in `0.x`. The authenticated HLSL corpus is unavailable. No registry or production code changes are included. See `docs/root-normalization-retirement.md`. +- Record the `dispatch.wgsl` blocker receipt at base + `7498a678c52029a82f312e9637ecb66b15defa0b`. Keep the arm live. The A0 + manifest records three WGSL files, three checks, three runs, and 171 + rewrites. The `normalMap` and `radiosity` witnesses diverge from locked C + on raw, production, compact, and incremental routes. Both malformed + controls rewrite five nodes on normalized routes. The production, compact, + and incremental routes for both controls diverge from locked C. The raw + missing-expression control matches locked C. The full authenticated corpus + is unavailable. No registry or production code changes are included. See + `docs/root-normalization-retirement.md`. + - Reconfirm the DTD locked-C blocker at base `30f470f5c2bf18540f7a18b2b22a7e33b88d4e10`. Keep `dispatch.dtd` live. The raw route matches the production route by deep digest for all four diff --git a/cgo_harness/wgsl_next_live_probe_test.go b/cgo_harness/wgsl_next_live_probe_test.go new file mode 100644 index 000000000..80095d6fc --- /dev/null +++ b/cgo_harness/wgsl_next_live_probe_test.go @@ -0,0 +1,440 @@ +//go:build cgo && treesitter_c_parity + +package cgoharness + +import ( + "bytes" + "crypto/sha256" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + gotreesitter "github.com/odvcencio/gotreesitter" + "github.com/odvcencio/gotreesitter/grammars" + "github.com/odvcencio/gotreesitter/internal/benchfixtures" + sitter "github.com/tree-sitter/go-tree-sitter" +) + +type wgslNextWitness struct { + name string + source []byte + class string +} + +type wgslNextRouteExpectation struct { + digest string + receipt string + diff *DumpV1Divergence + rootError bool + rootErrorSet bool +} + +type wgslNextExpectation struct { + cDigest string + wantError bool + compactRoute string + forestRoute string + reuseSubtrees uint64 + reuseBytes uint64 + routes map[string]wgslNextRouteExpectation + forest *wgslNextRouteExpectation +} + +// TestWGSLNextLiveArmLockedCRoutes records every WGSL route for A0 and controls. +func TestWGSLNextLiveArmLockedCRoutes(t *testing.T) { + t.Setenv("GTS_DISPATCHER_CENSUS", "1") + t.Setenv("GOT_PARSE_PHASE_TIMING", "1") + goLanguage := grammars.WgslLanguage() + cLanguage, err := COracleLanguage("wgsl") + if err != nil { + t.Fatal(err) + } + + witnesses := []wgslNextWitness{ + {name: "a0-small-fragmentTextureQuad", source: wgslReadA0(t, "small__fragmentTextureQuad.wgsl"), class: "a0"}, + {name: "a0-medium-normalMap", source: wgslReadA0(t, "medium__normalMap.wgsl"), class: "a0"}, + {name: "a0-medium-radiosity", source: wgslReadA0(t, "medium__radiosity.wgsl"), class: "a0"}, + {name: "recovery-empty-return", source: []byte("fn malformed() { return; }\n"), class: "recovery"}, + {name: "malformed-missing-expression", source: []byte("fn malformed() { let value: f32 = ; }\n"), class: "malformed"}, + {name: "malformed-argument-list", source: []byte("fn malformed() { textureLoad(texture, coord,); }\n"), class: "malformed"}, + {name: "positive-control", source: []byte("fn identity(value: f32) -> f32 { return value; }\n"), class: "control"}, + } + + for _, witness := range witnesses { + witness := witness + t.Run(witness.name, func(t *testing.T) { + cParser := sitter.NewParser() + t.Cleanup(cParser.Close) + if err := cParser.SetLanguage(cLanguage); err != nil { + t.Fatal(err) + } + cTree := cParser.Parse(witness.source, nil) + if cTree == nil || cTree.RootNode() == nil { + t.Fatal("locked C parser returned no tree") + } + t.Cleanup(cTree.Close) + cDigest, err := COracleDeepDigest(cTree) + if err != nil { + t.Fatal(err) + } + want := wgslNextWant(witness.name) + if cDigest != want.cDigest { + t.Fatalf("locked C digest=%s, want %s", cDigest, want.cDigest) + } + + raw := wgslParseRoute(t, goLanguage, witness.source, "raw", func(p *gotreesitter.Parser, source []byte) (*gotreesitter.Tree, error) { + return p.ParseNoResultCompatibilityBenchmarkOnly(source) + }) + production := wgslParseRoute(t, goLanguage, witness.source, "production", func(p *gotreesitter.Parser, source []byte) (*gotreesitter.Tree, error) { + return p.Parse(source) + }) + compactParser := gotreesitter.NewParser(goLanguage) + compactParser.SetAdmissionCandidateRoute(true) + routedBefore, fallbackBefore := gotreesitter.AdmissionCandidateCounters() + compact := wgslParseRoute(t, goLanguage, witness.source, "compact", func(_ *gotreesitter.Parser, source []byte) (*gotreesitter.Tree, error) { + return compactParser.Parse(source) + }) + routedAfter, fallbackAfter := gotreesitter.AdmissionCandidateCounters() + compactRoute := "accepted" + if fallbackAfter > fallbackBefore { + compactRoute = "fallback:" + gotreesitter.AdmissionCandidateLastFallbackReason() + } + + forestParser := gotreesitter.NewParser(goLanguage) + forest, forestOK := forestParser.ParseForestExperimental(witness.source) + forestRoute := "declined" + if forestOK && forest != nil { + forestRoute = "accepted" + t.Cleanup(forest.Release) + } else { + offset, symbol, reason, _ := forestParser.ForestDeclineInfo() + forestRoute = fmt.Sprintf("declined:%s@%d/%d", reason, offset, symbol) + } + + base := bytes.TrimSuffix(witness.source, []byte{'\n'}) + incrementalParser := gotreesitter.NewParser(goLanguage) + oldTree, err := incrementalParser.Parse(base) + if err != nil { + t.Fatalf("incremental base parse: %v", err) + } + t.Cleanup(oldTree.Release) + oldTree.Edit(gotreesitter.InputEdit{ + StartByte: uint32(len(base)), + OldEndByte: uint32(len(base)), + NewEndByte: uint32(len(witness.source)), + StartPoint: wgslPointAtByte(base), + OldEndPoint: wgslPointAtByte(base), + NewEndPoint: wgslPointAtByte(witness.source), + }) + incremental, profile, err := incrementalParser.ParseIncrementalProfiled(witness.source, oldTree) + if err != nil { + t.Fatalf("incremental parse: %v", err) + } + t.Cleanup(incremental.Release) + + for _, route := range []struct { + name string + tree *gotreesitter.Tree + }{ + {name: "raw", tree: raw}, + {name: "production", tree: production}, + {name: "compact", tree: compact}, + {name: "incremental", tree: incremental}, + } { + if route.tree == nil || route.tree.RootNode() == nil { + t.Fatalf("%s route returned no root", route.name) + } + inspection, err := benchfixtures.InspectGoTree(route.tree.RootNode(), goLanguage) + if err != nil { + t.Fatalf("%s inspect Go tree: %v", route.name, err) + } + diff := FirstDivergenceDumpV1(route.tree.RootNode(), goLanguage, cTree.RootNode()) + expectation, ok := want.routes[route.name] + if !ok { + t.Fatalf("missing %s expectation", route.name) + } + wantError := want.wantError + if expectation.rootErrorSet { + wantError = expectation.rootError + } + if route.tree.RootNode().HasError() != wantError { + t.Fatalf("%s root error=%t, want %t", route.name, route.tree.RootNode().HasError(), wantError) + } + if inspection.SHA256 != expectation.digest { + t.Fatalf("%s digest=%s, want %s", route.name, inspection.SHA256, expectation.digest) + } + if got := wgslDispatchReceipt(route.tree); got != expectation.receipt { + t.Fatalf("%s dispatch receipt=%s, want %s", route.name, got, expectation.receipt) + } + wgslRequireDivergence(t, route.name, diff, expectation.diff) + t.Logf("witness=%s class=%s route=%s bytes=%d source_sha256=%x root_error=%t digest=%s c_digest=%s divergence=%+v dispatch=%s", witness.name, witness.class, route.name, len(witness.source), sha256.Sum256(witness.source), route.tree.RootNode().HasError(), inspection.SHA256, cDigest, diff, wgslDispatchReceipt(route.tree)) + } + if forest != nil { + inspection, err := benchfixtures.InspectGoTree(forest.RootNode(), goLanguage) + if err != nil { + t.Fatalf("forest inspect Go tree: %v", err) + } + diff := FirstDivergenceDumpV1(forest.RootNode(), goLanguage, cTree.RootNode()) + if want.forest == nil { + t.Fatalf("forest returned a tree, want decline %s", want.forestRoute) + } + if forest.RootNode().HasError() != want.wantError { + t.Fatalf("forest root error=%t, want %t", forest.RootNode().HasError(), want.wantError) + } + if inspection.SHA256 != want.forest.digest { + t.Fatalf("forest digest=%s, want %s", inspection.SHA256, want.forest.digest) + } + if got := wgslDispatchReceipt(forest); got != want.forest.receipt { + t.Fatalf("forest dispatch receipt=%s, want %s", got, want.forest.receipt) + } + wgslRequireDivergence(t, "forest", diff, want.forest.diff) + t.Logf("witness=%s class=%s route=forest bytes=%d source_sha256=%x root_error=%t digest=%s c_digest=%s divergence=%+v dispatch=%s", witness.name, witness.class, len(witness.source), sha256.Sum256(witness.source), forest.RootNode().HasError(), inspection.SHA256, cDigest, diff, wgslDispatchReceipt(forest)) + } else if want.forest != nil { + t.Fatalf("forest declined, want digest %s", want.forest.digest) + } + if compactRoute != want.compactRoute { + t.Fatalf("compact route=%q, want %q", compactRoute, want.compactRoute) + } + if forestRoute != want.forestRoute { + t.Fatalf("forest route=%q, want %q", forestRoute, want.forestRoute) + } + if profile.OldTreeReuseRoute != true || profile.ReuseUnsupported { + t.Fatalf("incremental reuse route=%t unsupported=%t reason=%q, want reuse without fallback", profile.OldTreeReuseRoute, profile.ReuseUnsupported, profile.ReuseUnsupportedReason) + } + if profile.ReusedSubtrees != want.reuseSubtrees || profile.ReusedBytes != want.reuseBytes { + t.Fatalf("incremental reuse=%d subtrees/%d bytes, want %d/%d", profile.ReusedSubtrees, profile.ReusedBytes, want.reuseSubtrees, want.reuseBytes) + } + t.Logf("witness=%s compact=%s counters=%d/%d->%d/%d forest=%s incremental_reuse=%t reuse_unsupported=%t reuse_reason=%q reused_subtrees=%d reused_bytes=%d", witness.name, compactRoute, routedBefore, fallbackBefore, routedAfter, fallbackAfter, forestRoute, profile.OldTreeReuseRoute, profile.ReuseUnsupported, profile.ReuseUnsupportedReason, profile.ReusedSubtrees, profile.ReusedBytes) + }) + } +} + +func wgslRequireDivergence(t *testing.T, route string, got, want *DumpV1Divergence) { + t.Helper() + if got == nil || want == nil { + if got != want { + t.Fatalf("%s divergence=%+v, want %+v", route, got, want) + } + return + } + if *got != *want { + t.Fatalf("%s divergence=%+v, want %+v", route, got, want) + } +} + +func wgslDiff(path, category, goValue, cValue string) *DumpV1Divergence { + return &DumpV1Divergence{Path: path, Category: category, GoValue: goValue, CValue: cValue} +} + +func wgslNextWant(name string) wgslNextExpectation { + switch name { + case "a0-small-fragmentTextureQuad": + const digest = "d3e58954c750ed560edd3177a165bbf701c159467a1b4677996bec620c377804" + return wgslNextExpectation{ + cDigest: digest, + compactRoute: "accepted", + forestRoute: "accepted", + reuseSubtrees: 49, + reuseBytes: 192, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: digest, receipt: "none"}, + "production": {digest: digest, receipt: "none"}, + "compact": {digest: digest, receipt: "none"}, + "incremental": {digest: digest, receipt: "1/1/112/51"}, + }, + forest: &wgslNextRouteExpectation{digest: digest, receipt: "1/1/111/0"}, + } + case "a0-medium-normalMap": + return wgslNextExpectation{ + cDigest: "231e10ca2215945a5fb51670620c9f5ba2ea1ca7d445cb2c9443fb51b8e0e18a", + wantError: true, + compactRoute: "fallback:compact route declined at recovery [mechanism=recovery-entered]: did not accept EOF: generic scheduler has no table action for the elected token", + forestRoute: "declined:dead_end@0/1", + reuseSubtrees: 452, + reuseBytes: 2318, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: "77fdfd002d6937e6f5784fc19e21a6f63ab8f2280ca8ba0dcfc1ee5b1d3d42cc", receipt: "none", diff: wgslDiff("/source_file", "shape", "children=70", "children=79")}, + "production": {digest: "9d802a0e9af71176c0520496ae99425406aa76dc8560f8c7e939e366f9fbbd44", receipt: "1/1/1289/120", diff: wgslDiff("/source_file", "shape", "children=70", "children=79")}, + "compact": {digest: "9d802a0e9af71176c0520496ae99425406aa76dc8560f8c7e939e366f9fbbd44", receipt: "1/1/1289/120", diff: wgslDiff("/source_file", "shape", "children=70", "children=79")}, + "incremental": {digest: "fe0cb9f758eaace140619c81a9ef3347d89633580b18492e46dcfae6fb8f57c7", receipt: "1/1/1287/684", diff: wgslDiff("/source_file", "shape", "children=69", "children=79")}, + }, + } + case "a0-medium-radiosity": + return wgslNextExpectation{ + cDigest: "22b9d004c33c6a8229b56876282125e04efddf59deef6224eddd61f38c9952b2", + wantError: true, + compactRoute: "fallback:compact route declined at recovery [mechanism=recovery-entered]: did not accept EOF: generic scheduler has no table action for the elected token", + forestRoute: "declined:dead_end@247/43", + reuseSubtrees: 185, + reuseBytes: 1197, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: "c591b9329ad2fc946b6b8b7c4bc80adb7305f41934dd51d4505e4f606787b127", receipt: "none", diff: wgslDiff("/source_file/global_variable_declaration[3]/variable_declaration[2]/variable_identifier_declaration[2]/type_declaration[2]/ERROR[1]", "type", "ERROR", "<")}, + "production": {digest: "592abfad21a9a3170c11fd2e18888a9c5ecac7f681af5cf81e2d1c352873df63", receipt: "1/1/1230/51", diff: wgslDiff("/source_file/function_declaration[29]", "error", "false", "true")}, + "compact": {digest: "592abfad21a9a3170c11fd2e18888a9c5ecac7f681af5cf81e2d1c352873df63", receipt: "1/1/1230/51", diff: wgslDiff("/source_file/function_declaration[29]", "error", "false", "true")}, + "incremental": {digest: "9e0113134b748e66ba22390bdf09e6e083a7f09dc0fc3b1dec67a407fed979cd", receipt: "1/1/1230/56", diff: wgslDiff("/source_file", "shape", "children=46", "children=48")}, + }, + } + case "recovery-empty-return": + const digest = "909c5e3b5efd2201372daf83848ea91cc459c1069537f89fcebf1f7c49cd58f4" + return wgslNextExpectation{ + cDigest: digest, + compactRoute: "accepted", + forestRoute: "accepted", + reuseSubtrees: 6, + reuseBytes: 20, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: digest, receipt: "none"}, + "production": {digest: digest, receipt: "none"}, + "compact": {digest: digest, receipt: "none"}, + "incremental": {digest: digest, receipt: "1/1/12/0"}, + }, + forest: &wgslNextRouteExpectation{digest: digest, receipt: "1/1/12/0"}, + } + case "malformed-missing-expression": + const digest = "dcdd782dbf759ef627faf475817ba14b68ae5b0783e431fc06a1bee5a879e73b" + const normalizedDigest = "88373bc4d0bc650c42c159273d5cc2a6915b2c69334adc3d5e8923a421fce02b" + return wgslNextExpectation{ + cDigest: digest, + wantError: true, + compactRoute: "fallback:compact route declined at recovery [mechanism=recovery-entered]: did not accept EOF: generic scheduler has no table action for the elected token", + forestRoute: "declined:dead_end@34/3", + reuseSubtrees: 9, + reuseBytes: 26, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: digest, receipt: "none"}, + "production": {digest: normalizedDigest, receipt: "1/1/19/5", diff: wgslDiff("/source_file", "error", "false", "true"), rootErrorSet: true, rootError: false}, + "compact": {digest: normalizedDigest, receipt: "1/1/19/5", diff: wgslDiff("/source_file", "error", "false", "true"), rootErrorSet: true, rootError: false}, + "incremental": {digest: normalizedDigest, receipt: "1/1/19/5", diff: wgslDiff("/source_file", "error", "false", "true"), rootErrorSet: true, rootError: false}, + }, + } + case "malformed-argument-list": + return wgslNextExpectation{ + cDigest: "4a43e477628bba014be5e5863dc87ab460c14c7232c96aa737414f299a407e81", + wantError: true, + compactRoute: "fallback:compact route declined at recovery [mechanism=recovery-entered]: did not accept EOF: generic scheduler has no table action for the elected token", + forestRoute: "declined:dead_end@28/7", + reuseSubtrees: 6, + reuseBytes: 25, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: "40a24a35095668102b40bd1bd92f5576211eaf92b3aac01f24b5e81a6ab755a9", receipt: "none", diff: wgslDiff("/source_file/function_declaration[0]/compound_statement[4]/assignment_statement[1]/parenthesized_expression[2]/ERROR[2]/,[0]", "error", "true", "false")}, + "production": {digest: "2555f0b34dfb5e295bc72d8d5532ceadfd6807686edc2e417a5e8ae911aa9df7", receipt: "1/1/23/5", diff: wgslDiff("/source_file/function_declaration[0]/compound_statement[4]/assignment_statement[1]/compound_assignment_operator[1]", "error", "false", "true")}, + "compact": {digest: "2555f0b34dfb5e295bc72d8d5532ceadfd6807686edc2e417a5e8ae911aa9df7", receipt: "1/1/23/5", diff: wgslDiff("/source_file/function_declaration[0]/compound_statement[4]/assignment_statement[1]/compound_assignment_operator[1]", "error", "false", "true")}, + "incremental": {digest: "2555f0b34dfb5e295bc72d8d5532ceadfd6807686edc2e417a5e8ae911aa9df7", receipt: "1/1/23/5", diff: wgslDiff("/source_file/function_declaration[0]/compound_statement[4]/assignment_statement[1]/compound_assignment_operator[1]", "error", "false", "true")}, + }, + } + case "positive-control": + const digest = "7868a6b43efc8d14b73746e2472a596c9eeb3cd215f734021528aaa3668f05ea" + return wgslNextExpectation{ + cDigest: digest, + compactRoute: "accepted", + forestRoute: "accepted", + reuseSubtrees: 11, + reuseBytes: 37, + routes: map[string]wgslNextRouteExpectation{ + "raw": {digest: digest, receipt: "none"}, + "production": {digest: digest, receipt: "none"}, + "compact": {digest: digest, receipt: "none"}, + "incremental": {digest: digest, receipt: "1/1/24/0"}, + }, + forest: &wgslNextRouteExpectation{digest: digest, receipt: "1/1/24/0"}, + } + default: + panic("missing WGSL expectation for " + name) + } +} + +func wgslReadA0(t *testing.T, name string) []byte { + t.Helper() + source, err := os.ReadFile(filepath.Join("..", "testdata", "dispatcher_census_a0", "wgsl", name)) + if err != nil { + t.Fatal(err) + } + return source +} + +func wgslParseRoute(t *testing.T, language *gotreesitter.Language, source []byte, route string, parse func(*gotreesitter.Parser, []byte) (*gotreesitter.Tree, error)) *gotreesitter.Tree { + t.Helper() + parser := gotreesitter.NewParser(language) + tree, err := parse(parser, source) + if err != nil { + t.Fatalf("%s parse: %v", route, err) + } + if tree == nil || tree.RootNode() == nil { + t.Fatalf("%s parse returned no tree", route) + } + t.Cleanup(tree.Release) + return tree +} + +func wgslDispatchReceipt(tree *gotreesitter.Tree) string { + if tree == nil || tree.ParseRuntime().NormalizationPasses == nil { + return "none" + } + for _, pass := range *tree.ParseRuntime().NormalizationPasses { + if pass.Name == "dispatch.wgsl" { + return fmt.Sprintf("%d/%d/%d/%d", pass.Checked, pass.Run, pass.NodesVisited, pass.NodesRewritten) + } + } + return "none" +} + +func wgslPointAtByte(source []byte) gotreesitter.Point { + var point gotreesitter.Point + for _, value := range source { + if value == '\n' { + point.Row++ + point.Column = 0 + } else { + point.Column++ + } + } + return point +} + +// TestWGSLNextLiveArmReceiptDocument guards the blocker markers. +func TestWGSLNextLiveArmReceiptDocument(t *testing.T) { + raw, err := os.ReadFile("../docs/root-normalization-retirement.md") + if err != nil { + t.Fatal(err) + } + document := strings.Join(strings.Fields(string(raw)), " ") + for _, marker := range []string{ + "Status: NO-GO. KEEP LIVE: `dispatch.wgsl`.", + "The WGSL A0 receipt contains three files, three checks, three runs, 2,630 visited nodes, 171 rewrites, two error roots, and zero parse errors.", + "The tracked census has seven fixtures across six languages. It excludes WGSL.", + "The focused receipt uses seven witnesses.", + "The `normalMap` A0 witness exposes the first live blocker.", + "The malformed missing-expression control matches locked C on its raw route.", + "The malformed argument-list control keeps a live rewrite.", + "The two malformed controls each add five rewrites.", + "The probe changes only the receipt test, changelog, and retirement ledger.", + "Keep `dispatch.wgsl` live until the producer emits the locked-C trees for all registered witnesses and all listed routes.", + } { + marker = strings.Join(strings.Fields(marker), " ") + if !strings.Contains(document, marker) { + t.Fatalf("WGSL blocker receipt lacks marker %q", marker) + } + } +} + +// TestWGSLNextLiveChangelogReceipt guards the changelog marker. +func TestWGSLNextLiveChangelogReceipt(t *testing.T) { + raw, err := os.ReadFile("../CHANGELOG.md") + if err != nil { + t.Fatal(err) + } + document := strings.Join(strings.Fields(string(raw)), " ") + for _, marker := range []string{ + "Record the `dispatch.wgsl` blocker receipt at base `7498a678c52029a82f312e9637ecb66b15defa0b`.", + "The A0 manifest records three WGSL files, three checks, three runs, and 171 rewrites.", + "Both malformed controls rewrite five nodes on normalized routes. The production, compact, and incremental routes for both controls diverge from locked C.", + "No registry or production code changes are included.", + } { + marker = strings.Join(strings.Fields(marker), " ") + if !strings.Contains(document, marker) { + t.Fatalf("WGSL changelog lacks marker %q", marker) + } + } +} diff --git a/docs/root-normalization-retirement.md b/docs/root-normalization-retirement.md index f23bbb266..1ed9f5f32 100644 --- a/docs/root-normalization-retirement.md +++ b/docs/root-normalization-retirement.md @@ -932,6 +932,151 @@ count over a three-file corpus is a lead, not proof. Only a native-parse regression test — run after removing the candidate code, not before — can confirm dead code. +## 2026-08-24 WGSL blocker receipt + +Status: NO-GO. KEEP LIVE: `dispatch.wgsl`. + +Base commit: `7498a678c52029a82f312e9637ecb66b15defa0b`. + +Select WGSL after the Templ investigation because its A0 receipt records the +next live dispatcher evidence. The ownership registry keeps the arm live. + +The registry names `normalizeWGSLCompatibility` in +`parser_result_wgsl.go`. Its authoritative owner is +`scheduler_action_semantics`. Its registered witness source is +`cgo_harness/parity_cgo_test.go`. Its retirement condition requires exact +production, compact, forest, incremental, and C-oracle trees. + +The registry contains 88 entries: 78 dispatcher arms, three dispatcher +subpasses, one dispatcher predicate, three generic passes, and three fixpoint +passes. The live denominator contains 31 dispatcher arms, 33 dispatcher +language labels, one predicate, 32 live entries, and 56 retired entries. + +A0 means the authenticated initial dispatcher census. A0 contains 14 +languages, 42 files, and 14 receipts. It records 44 checks, 44 runs, 313,572 +visited nodes, 3,267 rewrites, 20 error roots, and zero parse errors. + +The WGSL A0 receipt contains three files, three checks, three runs, 2,630 +visited nodes, 171 rewrites, two error roots, and zero parse errors. + +| A0 file | Bytes | Source SHA-256 | +| --- | ---: | --- | +| `small__fragmentTextureQuad.wgsl` | 272 | `7af39dd8fd0e00f911fafaef4e2b40e2b0314f586049acef74c0fc451dd73286` | +| `medium__normalMap.wgsl` | 5,737 | `999d93539ed738ab5041d75fe28e7b9d4da7e7ef25c345f2a1ef52239320268b` | +| `medium__radiosity.wgsl` | 5,444 | `2d5630364c6667404abeb05ead49255f6538998ec66bf20cd5337a0eb5a26783` | + +The tracked census has seven fixtures across six languages. It excludes WGSL. +The authenticated real corpus is unavailable because +`cgo_harness/corpus_real` is absent. The real-corpus test skips for that +reason. + +The focused receipt uses seven witnesses. It covers the three A0 files, one +clean recovery control, two malformed recovery controls, and one positive +control. It tests raw, production, compact, forest, incremental, and locked-C +routes. + +The locked-C grammar uses the pinned WGSL commit +`40259f3c77ea856841a4e0c4c807705f3e4a2b65`. + +The small A0 witness matches locked C on every route. Its digest is +`d3e58954c750ed560edd3177a165bbf701c159467a1b4677996bec620c377804`. +The forest receipt is `1/1/111/0`. The incremental receipt is `1/1/112/51`. +The production and compact routes report zero dispatcher rewrites. + +The `normalMap` A0 witness exposes the first live blocker. Its locked-C digest +is `231e10ca2215945a5fb51670620c9f5ba2ea1ca7d445cb2c9443fb51b8e0e18a`. +Its raw digest is +`77fdfd002d6937e6f5784fc19e21a6f63ab8f2280ca8ba0dcfc1ee5b1d3d42cc`. +Production and compact produce +`9d802a0e9af71176c0520496ae99425406aa76dc8560f8c7e939e366f9fbbd44`. +Incremental produces +`fe0cb9f758eaace140619c81a9ef3347d89633580b18492e46dcfae6fb8f57c7`. +Production and compact report `1/1/1289/120`. Incremental reports +`1/1/1287/684`. Forest declines at `dead_end@0/1`. + +The raw, production, and compact routes first differ from C at +`/source_file`, with `children=70` in Go and `children=79` in C. +The incremental route first differs at `/source_file`, with `children=69` in +Go and `children=79` in C. + +The `radiosity` A0 witness also blocks retirement. Its locked-C digest is +`22b9d004c33c6a8229b56876282125e04efddf59deef6224eddd61f38c9952b2`. +Its raw digest is +`c591b9329ad2fc946b6b8b7c4bc80adb7305f41934dd51d4505e4f606787b127`. +Production and compact produce +`592abfad21a9a3170c11fd2e18888a9c5ecac7f681af5cf81e2d1c352873df63`. +Incremental produces +`9e0113134b748e66ba22390bdf09e6e083a7f09dc0fc3b1dec67a407fed979cd`. +Production and compact report `1/1/1230/51`. Incremental reports +`1/1/1230/56`. Forest declines at `dead_end@247/43`. + +The raw route first differs from C at +`/source_file/global_variable_declaration[3]/variable_declaration[2]/variable_identifier_declaration[2]/type_declaration[2]/ERROR[1]`. +Go has `ERROR`. C has `<`. The production and compact routes first differ at +`/source_file/function_declaration[29]`. Go reports no error. C reports an +error. The incremental route first differs at `/source_file`, with +`children=46` in Go and `children=48` in C. + +The clean empty-return recovery control matches locked C on every route. Its digest +is `909c5e3b5efd2201372daf83848ea91cc459c1069537f89fcebf1f7c49cd58f4`. +The forest and incremental receipts are both `1/1/12/0`. + +The malformed missing-expression control matches locked C on its raw route. +Its source SHA-256 is +`40fa64204b69aded167403d87ddd86ced1068117d41ffa7b2fda5250d0e44f96`. +Its raw and C digest is +`dcdd782dbf759ef627faf475817ba14b68ae5b0783e431fc06a1bee5a879e73b`. +Production, compact, and incremental produce +`88373bc4d0bc650c42c159273d5cc2a6915b2c69334adc3d5e8923a421fce02b`. +They first differ from C at `/source_file`: Go reports no error and C reports +an error. Each normalized route reports `1/1/19/5`. Compact falls back, and +forest declines at `dead_end@34/3`. + +The malformed argument-list control keeps a live rewrite. Its raw digest is +`40a24a35095668102b40bd1bd92f5576211eaf92b3aac01f24b5e81a6ab755a9`. +Production, compact, and incremental produce +`2555f0b34dfb5e295bc72d8d5532ceadfd6807686edc2e417a5e8ae911aa9df7`. +Locked C produces +`4a43e477628bba014be5e5863dc87ab460c14c7232c96aa737414f299a407e81`. +The raw route differs at +`/source_file/function_declaration[0]/compound_statement[4]/assignment_statement[1]/parenthesized_expression[2]/ERROR[2]/,[0]`. +Go reports an error. C does not. The normalized routes differ at +`/source_file/function_declaration[0]/compound_statement[4]/assignment_statement[1]/compound_assignment_operator[1]`. +Go reports no error. C reports an error. Each normalized route reports +`1/1/23/5`. + +The compact route falls back for `normalMap`, `radiosity`, and both malformed +controls. Each fallback reports no table action for the elected token. +The forest route declines those witnesses at the recorded dead ends. + +Incremental reuse remains supported for all seven witnesses. It reuses 49/192, +452/2,318, 185/1,197, 6/20, 9/26, 6/25, and 11/37 subtrees and bytes, in +witness order. The clean positive control matches locked C on every route with digest +`7868a6b43efc8d14b73746e2472a596c9eeb3cd215f734021528aaa3668f05ea`. +It reports zero dispatcher rewrites. The eight existing WGSL normalizer unit +tests remain live producer controls. + +The A0 production rewrites total 171: 120 for `normalMap` and 51 for +`radiosity`. The two malformed controls each add five rewrites. The route +receipts also record 51, 684, and 56 incremental rewrites for the small, +`normalMap`, and `radiosity` witnesses. + +The successful focused Docker artifacts are: + +- `/tmp/wgsl-next-artifacts/20260823T011913Z-wgsl-registry` — registry gate; +- `/tmp/wgsl-next-artifacts/20260823T011923Z-wgsl-a0` — A0 gate; +- `/tmp/wgsl-next-artifacts/20260823T011934Z-wgsl-tracked` — tracked census gate; +- `/tmp/wgsl-next-artifacts/20260823T011941Z-wgsl-real` — unavailable corpus receipt; +- `/tmp/wgsl-next-artifacts/20260823T012037Z-wgsl-unit` — WGSL unit tests; +- `/tmp/wgsl-next-artifacts/20260823T014757Z-wgsl-blocker-final-seven-corrected` — final route, changelog, and document guard. + +The probe changes only the receipt test, changelog, and retirement ledger. +It does not change production or registry state. + +Keep `dispatch.wgsl` live until the producer emits the locked-C trees for all +registered witnesses and all listed routes. Require the authenticated corpus. +Move the recovery controls to their owning subsystem before retirement. + ### R3 — move materialization invariants upstream Status: in progress.