diff --git a/modules/mesh-build-go/cmd/mesh-build-go/announce.go b/modules/mesh-build-go/cmd/mesh-build-go/announce.go index 3550c87d1..4ee60ceae 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/announce.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/announce.go @@ -15,6 +15,8 @@ var Kept = []announce.State{ {Name: "keys", Readable: true}, {Name: "steps", Readable: true}, {Name: "pauses", Readable: true}, + // The steps detected for each artifact, kept by its fingerprint (design 47 G §326, §414). + {Name: "detected-steps", Readable: true}, // The condition keeper raises a first view of the indexer's manifests it could not get (ADR 0615 §6) from this. {Name: "findings", Readable: true}, } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go b/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go index adeda47db..bd3fa0e36 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go @@ -10,7 +10,7 @@ import ( "encoding/json" "os" "path/filepath" - "slices" + "reflect" "strings" "testing" @@ -147,7 +147,9 @@ func TestManifestRequiresForgeAccountKeepsKeysAndTakesFilesFromTemplates(t *test m := readForgeManifest(t) // node-tools: the module pulls its asks through the tool runner's seat pull (1911). // go-package-registry: the Go seat's read side, the fetch's first source (design 51, CAT-BG3, E2). - if want := []any{"forge-account", "go-toolchain", "go-package-registry", "node-tools"}; !slices.Equal(m.Requires, want) { + // detected-steps: the state of the verb steps (design 47 G §326, §414), asked by the template state. + detected := map[string]any{"name": "detected-steps", "storage": "key-value", "template": "state", "options": map[string]any{"history": float64(1)}} + if want := []any{detected, "forge-account", "go-toolchain", "go-package-registry", "node-tools"}; !reflect.DeepEqual(m.Requires, want) { t.Errorf("requires = %v, want %v", m.Requires, want) } keeps := false diff --git a/modules/mesh-build-go/cmd/mesh-build-go/main.go b/modules/mesh-build-go/cmd/mesh-build-go/main.go index 15b60c48d..3eae5fca1 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/main.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/main.go @@ -41,6 +41,8 @@ func main() { Toolchains: stdio.State(seat.StateToolchains).Put, Emit: func(t string, b any) error { return stdio.Emit(t, b) }, Steps: stdio.State(seat.StateSteps).Put, + Detected: stdio.State(seat.StateDetectedSteps).Put, + ReadDetected: readOf(stdio.State(seat.StateDetectedSteps)).Get, ReadSteps: readOf(stdio.State(seat.StateSteps)), Pauses: stdio.State(sdkstep.PausesState).Put, ReadPauses: readOf(stdio.State(sdkstep.PausesState)), diff --git a/modules/mesh-build-go/cmd/mesh-build-go/module.go b/modules/mesh-build-go/cmd/mesh-build-go/module.go index 5fc8f74a2..1c57a3d28 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/module.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/module.go @@ -33,6 +33,10 @@ type Module struct { // Held is the puller whose running asks a step-end tool will not end (issue 1890); nil in a test. Held *buildstep.Puller ReadSteps, ReadToolchainsState StateRead + // Detected and ReadDetected are the Put and the read of the state detected-steps (design 47 G §326, §414): the + // answer of the verb steps, one key per artifact. + Detected buildstep.Put + ReadDetected func(key string) (json.RawMessage, error) // Pauses is the Put of the pauses state, which keeps the node's pause key without an age; ReadPauses reads it back. Pauses buildstep.Put ReadPauses StateRead diff --git a/modules/mesh-build-go/cmd/mesh-build-go/steps.go b/modules/mesh-build-go/cmd/mesh-build-go/steps.go index 2aa35e58d..7745cc695 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/steps.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/steps.go @@ -158,11 +158,35 @@ func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) { if str("artifact") == "" || str("module") == "" { return nil, fmt.Errorf("steps: the module and the artifact are asked") } + artifact, fp := str("artifact"), str("fingerprint") + key := seat.DetectedStepsByModuleArtifactKey(str("module"), artifactName(artifact)) + // The kept answer is the artifact's newest fingerprint's: used for the same fingerprint, replaced by a newer one + // and by again (§326). + if again, _ := args["again"].(bool); !again && fp != "" && m.ReadDetected != nil { + if raw, err := m.ReadDetected(key); err == nil && len(raw) > 0 { + if kept, err := seat.DecodeDetectedStepsByModuleArtifactValue(raw); err == nil && kept.Fingerprint == fp { + return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: kept.Steps, Kept: true}, nil + } + } + } steps, err := m.stepsOfTree(ctx, req) if err != nil { return nil, err } - return StepsAnswer{Artifact: str("artifact"), Fingerprint: str("fingerprint"), Steps: steps}, nil + if fp != "" && m.Detected != nil { + if _, err := m.Detected(key, seat.DetectedSteps{V: 1, Fingerprint: fp, Steps: steps, At: ptr(m.now())}); err != nil { + return nil, fmt.Errorf("steps: keeping the answer: %w", err) + } + } + return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil +} + +// artifactName is the artifact of an artifact reference :. +func artifactName(ref string) string { + if _, name, ok := strings.Cut(ref, ":"); ok { + return name + } + return ref } // stepsOfTree fetches the commit into a workspace removed at the end and detects the steps of the module's directory. @@ -196,3 +220,5 @@ func (m *Module) stepsOfTree(ctx context.Context, req sdkstep.FetchRequest) ([]D } return detectSteps(dir) } + +func ptr[T any](v T) *T { return &v } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/steps_test.go b/modules/mesh-build-go/cmd/mesh-build-go/steps_test.go index c2a118fbf..b60021fff 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/steps_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/steps_test.go @@ -1,6 +1,8 @@ package main import ( + "context" + "encoding/json" "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" "reflect" "testing" @@ -48,3 +50,40 @@ func TestTheStepsVerbAnswersTheStepsOfAFixtureArtifact(t *testing.T) { t.Fatalf("a module with no test: %+v, %v", got, err) } } + +// Design 47 G §326 (BLD2): steps answers from the kept key for the same fingerprint, a newer fingerprint and again +// replace it. +func TestTheStepsVerbKeepsItsAnswerByFingerprint(t *testing.T) { + f := &fakeStep{files: map[string]string{"go.mod": "module example.test/mod\n", "a/a_test.go": "package a\n"}} + m, _ := fetchModule(t, f) + kept := map[string]json.RawMessage{} + m.Detected = func(key string, v any) (uint64, error) { + raw, err := json.Marshal(v) + kept[key] = raw + return 1, err + } + m.ReadDetected = func(key string) (json.RawMessage, error) { return kept[key], nil } + call := func(fp string, again bool) StepsAnswer { + t.Helper() + got, err := m.steps(context.Background(), map[string]any{"module": "mod", "artifact": "mod:prog", "fingerprint": fp, "again": again, + "fetch": map[string]any{"repository": "novox/mod", "commit": commitSHA}}) + if err != nil { + t.Fatal(err) + } + return got.(StepsAnswer) + } + if a := call("fp1", false); a.Kept || len(kept) != 1 || kept["mod.prog"] == nil { + t.Fatalf("first ask: %+v, keys %v", a, kept) + } + f.files = map[string]string{"go.mod": "module example.test/mod\n"} // the tree changed, the kept answer still answers fp1 + if a := call("fp1", false); !a.Kept || len(a.Steps) != 2 { + t.Fatalf("same fingerprint: %+v", a) + } + if a := call("fp1", true); a.Kept || len(a.Steps) != 1 { + t.Fatalf("again: %+v", a) + } + f.files = map[string]string{"go.mod": "module example.test/mod\n", "a/a_test.go": "package a\n"} + if a := call("fp2", false); a.Kept || len(a.Steps) != 2 { + t.Fatalf("newer fingerprint: %+v", a) + } +} diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index fb3655599..b447e70c4 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -82,6 +82,7 @@ } ], "requires": [ + {"name": "detected-steps", "storage": "key-value", "template": "state", "options": {"history": 1}}, "forge-account", "go-toolchain", "go-package-registry",