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
This commit is contained in:
2026-11-04 15:07:56 +01:00
parent c5bec10bb5
commit b5495d0cc8
3 changed files with 51 additions and 0 deletions
@@ -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 {
@@ -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)
@@ -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