From b5495d0cc8b90f5307c3bdabb3ea4baecae3db66 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 15:07:56 +0100 Subject: [PATCH] CAT-BN2: mesh-build-node regenerates a contracts module's TypeScript and names each file out of step The unit level fails on one, a module with no tests of its own too; a build ask answers them in its out-of-step through buildstep's compare (lead's ruling on A2k finding 1). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../cmd/mesh-build-node/asks.go | 2 ++ .../cmd/mesh-build-node/check.go | 26 +++++++++++++++++++ .../cmd/mesh-build-node/check_test.go | 23 ++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/modules/mesh-build-node/cmd/mesh-build-node/asks.go b/modules/mesh-build-node/cmd/mesh-build-node/asks.go index c723ecaf..dad04a1d 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/asks.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/asks.go @@ -2,6 +2,7 @@ package main import ( "context" + "path/filepath" "time" "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" @@ -29,6 +30,7 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps { } return buildstep.AskSteps{ Fetch: byName["build_node_fetch"], Build: byName["build_node_build"], Test: byName["build_node_test"], + Compare: buildstep.CompareStep(buildstep.GeneratedRootTS, func(ws string) string { return filepath.Join(m.Work, ws, "tree") }), Toolchain: func(ctx context.Context) (delivery.ToolchainUsed, error) { installed, err := m.Probe(ctx) if err != nil { diff --git a/modules/mesh-build-node/cmd/mesh-build-node/check.go b/modules/mesh-build-node/cmd/mesh-build-node/check.go index 651688ac..f632563d 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/check.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/check.go @@ -59,6 +59,9 @@ type TestResult struct { // By is the build module and the SDK it was built with (sdkstep.By): the checker names it when a failure hides // its cause. By string `json:"by,omitempty"` + // OutOfStep are the generated TypeScript files of a contracts module out of step with the seat generator (CAT-BN2, + // buildstep.OutOfStep): the unit level fails on any, Why naming each. Absent when none is. + OutOfStep []string `json:"out-of-step,omitempty"` } // Failure is the SDK's failure contract (sdkstep.Failure, issue 1901): a failing test of the test script, read from @@ -89,6 +92,29 @@ func (m *Module) testTool() StepTool { } func (m *Module) test(ctx context.Context, args map[string]any) (any, error) { + res, err := m.testLevel(ctx, args) + if err != nil || res.Level != "unit" { + return res, err + } + // CAT-BN2: a contracts module's generated TypeScript is regenerated and compared; a file out of step fails the level. + _, dir, err := m.moduleDirOf(res.Workspace, res.Module) + if err != nil { + return nil, err + } + if res.OutOfStep, err = buildstep.OutOfStep(dir, buildstep.GeneratedRootTS); err != nil { + return nil, err + } + if len(res.OutOfStep) > 0 { + why := "generated files out of step with the seat generator (regenerate them): " + strings.Join(res.OutOfStep, ", ") + if res.Why != "" && !res.NoTests { + why = res.Why + "; " + why + } + res.Passed, res.NoTests, res.Why = false, false, why + } + return res, nil +} + +func (m *Module) testLevel(ctx context.Context, args map[string]any) (*TestResult, error) { workspace, _ := args["workspace"].(string) module, _ := args["module"].(string) level, _ := args["level"].(string) diff --git a/modules/mesh-build-node/cmd/mesh-build-node/check_test.go b/modules/mesh-build-node/cmd/mesh-build-node/check_test.go index f1329d34..54b2e533 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/check_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/check_test.go @@ -79,6 +79,29 @@ func TestAPackageWithNeitherScriptNorTypeScriptHasNoTests(t *testing.T) { if len(f.calls) != 0 { t.Errorf("%d commands ran for a package with no tests", len(f.calls)) } + // CAT-BN2: a contracts module (a package artifact naming generated-from) with no tests of its own still fails its + // unit level on a generated TypeScript file out of step with the seat generator, each named; its Go files are + // mesh-build-go's to compare. + m, _, work = newMachine(t) + workspaceWith(t, work, map[string]string{ + "module.json": `{"module":"c","build":{"artifacts":[{"name":"ts","kind":"package","language":"typescript","paths":["ts"],"generated-from":"."}]}}`, + "seats/index.json": `{"format":1,"seats":["one-store"]}`, + "seats/one-store/seat.json": `{"format":1,"seat":"one-store","scope":"mesh","grain":"seat-list","decision":"ADR 1","verbs":[{"name":"query","input":{"type":"object"}}]}`, + "go/go.mod": "module example.org/c/go\n", "go/stray.go": "package stray\n", + "ts/package.json": `{"name":"@example/c"}`, "ts/stray.ts.txt": "not written by the generator\n", + }) + out, err := m.test(context.Background(), testArgs("unit")) + r := out.(*TestResult) + stray := false + for _, p := range r.OutOfStep { + stray = stray || p == "ts/stray.ts.txt" + if !strings.HasPrefix(p, "ts/") { + t.Errorf("%s is not under the TypeScript root", p) + } + } + if err != nil || r.Passed || r.NoTests || !stray || !strings.Contains(r.Why, "ts/stray.ts.txt") { + t.Errorf("a contracts module out of step = %+v, %v", r, err) + } } // 1491/AC4: with test:integration present the integration level runs it, and reports the setup (the type-check) and