BG9, BN9, BI3: the build seats' steps with drop writes the hand-act log first
A drop of a kept detected-steps key records the act with the caller the bus proved, then deletes the key; a log that refuses deletes nothing and is answered error; a drop without why is refused; fetch is asked unless drop. Builds: design 47 G §463, §480 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
package buildstep
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DropDetected is the drop of the build seats' `steps` (design 47 G §463, §480): a person's word deletes the kept key of
|
||||||
|
// an artifact's detected steps. The act is written in the hand-act log first, with the caller the bus proved; a drop
|
||||||
|
// whose log write fails deletes nothing and is answered by the text it returns, which names the log. A drop with no
|
||||||
|
// `why`, or whose call the runtime proved no caller for, is refused (an error, nothing recorded).
|
||||||
|
//
|
||||||
|
// record writes the act in the hand-act log of the seat mesh-hand-acts; forget deletes the key (nil where the seat keeps
|
||||||
|
// none, as an image's).
|
||||||
|
func DropDetected(artifact, why, caller, key string, record func(what, why string) error, forget func(key string) error) (string, error) {
|
||||||
|
switch {
|
||||||
|
case strings.TrimSpace(why) == "":
|
||||||
|
return "", errors.New("steps: a drop says why")
|
||||||
|
case caller == "" || caller == "unproven":
|
||||||
|
return "", errors.New("steps: a drop names who drops it, and the runtime proved no caller")
|
||||||
|
}
|
||||||
|
if record == nil {
|
||||||
|
return "the hand-act log is not served here: nothing was deleted", nil
|
||||||
|
}
|
||||||
|
if err := record(fmt.Sprintf("dropped the detected steps of %s (by %s)", artifact, caller), why); err != nil {
|
||||||
|
return fmt.Sprintf("the hand-act log refused the drop of %s, so nothing was deleted: %v", artifact, err), nil
|
||||||
|
}
|
||||||
|
if forget != nil {
|
||||||
|
if err := forget(key); err != nil {
|
||||||
|
return fmt.Sprintf("the drop of %s was recorded in the hand-act log and the key could not be deleted: %v", artifact, err), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||||
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handActRecorder writes an act by hand in the hand-act log of the seat mesh-hand-acts (design 47 G §480); a log that
|
||||||
|
// does not take it is an error, never a quiet success.
|
||||||
|
func handActRecorder(what, why string) error {
|
||||||
|
_, err := meshhandactsclient.Recorder{Ask: stdio.Ask}.Record(meshhandactsclient.Request{What: what, Why: why, Cause: "build-steps-drop", Verb: "steps drop"})
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -44,6 +44,8 @@ func main() {
|
|||||||
Steps: stdio.State(seat.StateSteps).Put,
|
Steps: stdio.State(seat.StateSteps).Put,
|
||||||
Detected: stdio.State(seat.StateDetectedSteps).Put,
|
Detected: stdio.State(seat.StateDetectedSteps).Put,
|
||||||
ReadDetected: readOf(stdio.State(seat.StateDetectedSteps)).Get,
|
ReadDetected: readOf(stdio.State(seat.StateDetectedSteps)).Get,
|
||||||
|
ForgetDetected: stdio.State(seat.StateDetectedSteps).Delete,
|
||||||
|
RecordAct: handActRecorder,
|
||||||
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
||||||
Pauses: stdio.State(sdkstep.PausesState).Put,
|
Pauses: stdio.State(sdkstep.PausesState).Put,
|
||||||
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
|
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ type Module struct {
|
|||||||
// answer of the verb steps, one key per artifact.
|
// answer of the verb steps, one key per artifact.
|
||||||
Detected buildstep.Put
|
Detected buildstep.Put
|
||||||
ReadDetected func(key string) (json.RawMessage, error)
|
ReadDetected func(key string) (json.RawMessage, error)
|
||||||
|
// ForgetDetected deletes one key of detected-steps; RecordAct writes an act in the hand-act log of the seat
|
||||||
|
// mesh-hand-acts (§463, §480): a drop writes it first.
|
||||||
|
ForgetDetected func(key string) error
|
||||||
|
RecordAct func(what, why string) error
|
||||||
// Pauses is the Put of the pauses state, which keeps the node's pause key without an age; ReadPauses reads it back.
|
// 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
|
Pauses buildstep.Put
|
||||||
ReadPauses StateRead
|
ReadPauses StateRead
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ type StepsAnswer struct {
|
|||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
Steps []DetectedStep `json:"steps"`
|
Steps []DetectedStep `json:"steps"`
|
||||||
Kept bool `json:"kept,omitempty"`
|
Kept bool `json:"kept,omitempty"`
|
||||||
|
// Dropped is the artifact whose key a drop deleted; Error why a drop deleted nothing, naming the hand-act log.
|
||||||
|
Dropped string `json:"dropped,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// stepsTool is the seat's verb `steps`: it fetches the artifact's commit into a workspace of its own, only the tree (no
|
// stepsTool is the seat's verb `steps`: it fetches the artifact's commit into a workspace of its own, only the tree (no
|
||||||
@@ -138,26 +141,31 @@ func (m *Module) stepsTool() stdio.Tool {
|
|||||||
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
||||||
Description: "Which pipeline steps a Go artifact can run, found by Go's convention: lint always, unit tests where a package holds a test file, " +
|
Description: "Which pipeline steps a Go artifact can run, found by Go's convention: lint always, unit tests where a package holds a test file, " +
|
||||||
"integration tests where one holds a test file built with the tag integration. A level with no test is not answered.",
|
"integration tests where one holds a test file built with the tag integration. A level with no test is not answered.",
|
||||||
Input: map[string]any{"type": "object", "required": []string{"module", "artifact", "fetch"}, "properties": map[string]any{
|
Input: map[string]any{"type": "object", "required": []string{"module", "artifact"}, "properties": map[string]any{
|
||||||
"module": map[string]any{"type": "string"},
|
"module": map[string]any{"type": "string"},
|
||||||
"artifact": map[string]any{"type": "string", "description": "the artifact reference, <module>:<artifact>"},
|
"artifact": map[string]any{"type": "string", "description": "the artifact reference, <module>:<artifact>"},
|
||||||
"fingerprint": map[string]any{"type": "string"},
|
"fingerprint": map[string]any{"type": "string"},
|
||||||
"fetch": map[string]any{"type": "object", "description": "what the fetch tool is asked: the repository, the full commit and the module's directory"},
|
"fetch": map[string]any{"type": "object", "description": "what the fetch tool is asked: the repository, the full commit and the module's directory"},
|
||||||
"again": map[string]any{"type": "boolean", "description": "find them anew, not from the kept answer"},
|
"again": map[string]any{"type": "boolean", "description": "find them anew, not from the kept answer"},
|
||||||
|
"drop": map[string]any{"type": "boolean", "description": "delete the artifact's kept key instead of detecting; needs why (written in the hand-act log first)"},
|
||||||
|
"why": map[string]any{"type": "string", "description": "with drop, why: a non-empty text"},
|
||||||
}},
|
}},
|
||||||
Run: m.Short(m.steps)}
|
Run: m.Short(m.steps)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
||||||
str := func(k string) string { s, _ := args[k].(string); return s }
|
str := func(k string) string { s, _ := args[k].(string); return s }
|
||||||
|
if str("artifact") == "" || str("module") == "" {
|
||||||
|
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
||||||
|
}
|
||||||
|
if drop, _ := args["drop"].(bool); drop {
|
||||||
|
return m.dropDetected(args, stdio.CallerOf(args))
|
||||||
|
}
|
||||||
fetch, _ := args["fetch"].(map[string]any)
|
fetch, _ := args["fetch"].(map[string]any)
|
||||||
req, err := sdkstep.ParseFetchRequest(fetch)
|
req, err := sdkstep.ParseFetchRequest(fetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if str("artifact") == "" || str("module") == "" {
|
|
||||||
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
|
||||||
}
|
|
||||||
artifact, fp := str("artifact"), str("fingerprint")
|
artifact, fp := str("artifact"), str("fingerprint")
|
||||||
key := seat.DetectedStepsByModuleArtifactKey(str("module"), artifactName(artifact))
|
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
|
// The kept answer is the artifact's newest fingerprint's: used for the same fingerprint, replaced by a newer one
|
||||||
@@ -181,6 +189,23 @@ func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
|||||||
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil
|
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dropDetected is `steps` with drop (§463, §480): the act is written in the hand-act log with the caller the bus proved,
|
||||||
|
// then the kept key is deleted; a log that refuses leaves the key.
|
||||||
|
func (m *Module) dropDetected(args map[string]any, caller string) (any, error) {
|
||||||
|
artifact, _ := args["artifact"].(string)
|
||||||
|
why, _ := args["why"].(string)
|
||||||
|
module, _ := args["module"].(string)
|
||||||
|
key := seat.DetectedStepsByModuleArtifactKey(module, artifactName(artifact))
|
||||||
|
failed, err := buildstep.DropDetected(artifact, why, caller, key, m.RecordAct, m.ForgetDetected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if failed != "" {
|
||||||
|
return StepsAnswer{Artifact: artifact, Error: failed}, nil
|
||||||
|
}
|
||||||
|
return StepsAnswer{Artifact: artifact, Dropped: artifact}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// artifactName is the artifact of an artifact reference <module>:<artifact>.
|
// artifactName is the artifact of an artifact reference <module>:<artifact>.
|
||||||
func artifactName(ref string) string {
|
func artifactName(ref string) string {
|
||||||
if _, name, ok := strings.Cut(ref, ":"); ok {
|
if _, name, ok := strings.Cut(ref, ":"); ok {
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,3 +89,33 @@ func TestTheStepsVerbKeepsItsAnswerByFingerprint(t *testing.T) {
|
|||||||
t.Fatalf("newer fingerprint: %+v", a)
|
t.Fatalf("newer fingerprint: %+v", a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Design 47 G §463, §480 (BG9): steps with drop writes the hand-act log first, then deletes the key; a log that
|
||||||
|
// refuses deletes nothing; a drop without why, or with no caller the bus proved, is refused; fetch is not asked.
|
||||||
|
func TestTheStepsDropWritesTheHandActLogFirst(t *testing.T) {
|
||||||
|
var order []string
|
||||||
|
kept := map[string]bool{"mod.prog": true}
|
||||||
|
m := &Module{
|
||||||
|
ForgetDetected: func(key string) error { order = append(order, "delete "+key); delete(kept, key); return nil },
|
||||||
|
RecordAct: func(what, why string) error { order = append(order, "log "+why); return nil },
|
||||||
|
}
|
||||||
|
drop := func(why, caller string) (any, error) {
|
||||||
|
return m.dropDetected(map[string]any{"module": "mod", "artifact": "mod:prog", "drop": true, "why": why}, caller)
|
||||||
|
}
|
||||||
|
got, err := drop("a stale answer", "shanks.person")
|
||||||
|
if a, _ := got.(StepsAnswer); err != nil || a.Dropped != "mod:prog" || kept["mod.prog"] || !reflect.DeepEqual(order, []string{"log a stale answer", "delete mod.prog"}) {
|
||||||
|
t.Fatalf("drop = %+v, %v, order %v, kept %v", got, err, order, kept)
|
||||||
|
}
|
||||||
|
if _, err := drop("", "shanks.person"); err == nil {
|
||||||
|
t.Error("a drop without why was taken")
|
||||||
|
}
|
||||||
|
if _, err := drop("why", "unproven"); err == nil {
|
||||||
|
t.Error("a drop with no proven caller was taken")
|
||||||
|
}
|
||||||
|
kept["mod.prog"], order = true, nil
|
||||||
|
m.RecordAct = func(string, string) error { return errors.New("log down") }
|
||||||
|
got, err = drop("again", "shanks.person")
|
||||||
|
if a, _ := got.(StepsAnswer); err != nil || a.Dropped != "" || !strings.Contains(a.Error, "hand-act log") || !kept["mod.prog"] || len(order) != 0 {
|
||||||
|
t.Fatalf("a refusing log: %+v, %v, kept %v, order %v", got, err, kept, order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ module meshbuildgo
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||||
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"mesh-conditions.findings-asked"
|
"mesh-conditions.findings-asked"
|
||||||
],
|
],
|
||||||
"invokes": [
|
"invokes": [
|
||||||
|
"seat:mesh-hand-acts.hand-act",
|
||||||
"mesh-delivery-indexer.indexer_read_manifests",
|
"mesh-delivery-indexer.indexer_read_manifests",
|
||||||
"seat:oci-artifact-registry.publish",
|
"seat:oci-artifact-registry.publish",
|
||||||
"seat:oci-artifact-registry.publish-part",
|
"seat:oci-artifact-registry.publish-part",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
|||||||
|
|
||||||
// SeatTools are the seat's verbs on this node (ADR 0525 §10).
|
// SeatTools are the seat's verbs on this node (ADR 0525 §10).
|
||||||
func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
|
func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
|
||||||
return append(buildstep.SeatTools(Seat, "image", p), stepsTool(), m.keptArtifactVerb())
|
return append(buildstep.SeatTools(Seat, "image", p), m.stepsTool(), m.keptArtifactVerb())
|
||||||
}
|
}
|
||||||
|
|
||||||
// StepEndTool ends one step left running (issue 1890); the module's steps state is the one it reads and writes.
|
// StepEndTool ends one step left running (issue 1890); the module's steps state is the one it reads and writes.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestTheModuleInvokesTheImageSeatAndHoldsNoRegistry(t *testing.T) {
|
|||||||
if err != nil || json.Unmarshal(raw, &m) != nil {
|
if err != nil || json.Unmarshal(raw, &m) != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if want := []string{"seat:container-image-registry.publish", "seat:container-image-registry.publish-part",
|
if want := []string{"seat:mesh-hand-acts.hand-act", "seat:container-image-registry.publish", "seat:container-image-registry.publish-part",
|
||||||
"seat:container-image-registry.mirror", "seat:mesh-module-assigner.assignments", "seat:mesh-catalog.module"}; !slices.Equal(m.Invokes, want) {
|
"seat:container-image-registry.mirror", "seat:mesh-module-assigner.assignments", "seat:mesh-catalog.module"}; !slices.Equal(m.Invokes, want) {
|
||||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||||
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handActRecorder writes an act by hand in the hand-act log of the seat mesh-hand-acts (design 47 G §480); a log that
|
||||||
|
// does not take it is an error, never a quiet success.
|
||||||
|
func handActRecorder(what, why string) error {
|
||||||
|
_, err := meshhandactsclient.Recorder{Ask: stdio.Ask}.Record(meshhandactsclient.Request{What: what, Why: why, Cause: "build-steps-drop", Verb: "steps drop"})
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ func main() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
m := &Module{
|
m := &Module{
|
||||||
Node: strings.TrimSpace(os.Getenv("MESH_NODE")),
|
Node: strings.TrimSpace(os.Getenv("MESH_NODE")),
|
||||||
|
RecordAct: handActRecorder,
|
||||||
Steps: stdio.State(seat.StateSteps).Put,
|
Steps: stdio.State(seat.StateSteps).Put,
|
||||||
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
||||||
Pauses: stdio.State(sdkstep.PausesState).Put,
|
Pauses: stdio.State(sdkstep.PausesState).Put,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const CheckRound = 10 * time.Minute
|
|||||||
// Module is the image build module's one running state.
|
// Module is the image build module's one running state.
|
||||||
type Module struct {
|
type Module struct {
|
||||||
Node string
|
Node string
|
||||||
|
// RecordAct writes an act in the hand-act log of the seat mesh-hand-acts (§463, §480): a drop writes it first.
|
||||||
|
RecordAct func(what, why string) error
|
||||||
// Account and UID are the account the module runs as; Home is its home.
|
// Account and UID are the account the module runs as; Home is its home.
|
||||||
Account, UID, Home string
|
Account, UID, Home string
|
||||||
// Steps is the Put of the steps state; ReadSteps reads it back for the read tools.
|
// Steps is the Put of the steps state; ReadSteps reads it back for the read tools.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
|
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
|
||||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
)
|
)
|
||||||
@@ -16,10 +17,13 @@ type StepsAnswer struct {
|
|||||||
Artifact string `json:"artifact"`
|
Artifact string `json:"artifact"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
Steps []seat.DetectedStep `json:"steps"`
|
Steps []seat.DetectedStep `json:"steps"`
|
||||||
|
// Dropped is the artifact a drop was recorded for; Error why a drop deleted nothing, naming the hand-act log.
|
||||||
|
Dropped string `json:"dropped,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// stepsTool is the seat's verb `steps`.
|
// stepsTool is the seat's verb `steps`.
|
||||||
func stepsTool() stdio.Tool {
|
func (m *Module) stepsTool() stdio.Tool {
|
||||||
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
||||||
Description: "Which pipeline steps an image artifact can run: none (an image has no lint, unit or integration test step).",
|
Description: "Which pipeline steps an image artifact can run: none (an image has no lint, unit or integration test step).",
|
||||||
Input: map[string]any{"type": "object", "required": []string{"module", "artifact"}, "properties": map[string]any{
|
Input: map[string]any{"type": "object", "required": []string{"module", "artifact"}, "properties": map[string]any{
|
||||||
@@ -28,14 +32,34 @@ func stepsTool() stdio.Tool {
|
|||||||
"fingerprint": map[string]any{"type": "string"},
|
"fingerprint": map[string]any{"type": "string"},
|
||||||
"fetch": map[string]any{"type": "object", "description": "not read: an image's steps need no tree"},
|
"fetch": map[string]any{"type": "object", "description": "not read: an image's steps need no tree"},
|
||||||
"again": map[string]any{"type": "boolean", "description": "not read: the answer is the same anew"},
|
"again": map[string]any{"type": "boolean", "description": "not read: the answer is the same anew"},
|
||||||
|
"drop": map[string]any{"type": "boolean", "description": "delete the artifact's kept key instead (an image keeps none); needs why, written in the hand-act log first"},
|
||||||
|
"why": map[string]any{"type": "string", "description": "with drop, why: a non-empty text"},
|
||||||
}},
|
}},
|
||||||
Run: steps}
|
Run: m.steps}
|
||||||
}
|
}
|
||||||
|
|
||||||
func steps(args map[string]any) (any, error) {
|
func (m *Module) steps(args map[string]any) (any, error) {
|
||||||
str := func(k string) string { s, _ := args[k].(string); return s }
|
str := func(k string) string { s, _ := args[k].(string); return s }
|
||||||
if str("artifact") == "" || str("module") == "" {
|
if str("artifact") == "" || str("module") == "" {
|
||||||
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
||||||
}
|
}
|
||||||
|
if drop, _ := args["drop"].(bool); drop {
|
||||||
|
return m.dropDetected(args, stdio.CallerOf(args))
|
||||||
|
}
|
||||||
return StepsAnswer{Artifact: str("artifact"), Fingerprint: str("fingerprint"), Steps: []seat.DetectedStep{}}, nil
|
return StepsAnswer{Artifact: str("artifact"), Fingerprint: str("fingerprint"), Steps: []seat.DetectedStep{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dropDetected is `steps` with drop (§463, §480): the act is written in the hand-act log with the caller the bus proved;
|
||||||
|
// an image keeps no detected steps, so there is no key to delete.
|
||||||
|
func (m *Module) dropDetected(args map[string]any, caller string) (any, error) {
|
||||||
|
artifact, _ := args["artifact"].(string)
|
||||||
|
why, _ := args["why"].(string)
|
||||||
|
failed, err := buildstep.DropDetected(artifact, why, caller, "", m.RecordAct, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if failed != "" {
|
||||||
|
return StepsAnswer{Artifact: artifact, Error: failed}, nil
|
||||||
|
}
|
||||||
|
return StepsAnswer{Artifact: artifact, Dropped: artifact}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
// Design 47 G §22, §328 (BLD8): the verb steps is served and answers none for an image.
|
// Design 47 G §22, §328 (BLD8): the verb steps is served and answers none for an image.
|
||||||
func TestTheStepsVerbAnswersNoStepForAnImage(t *testing.T) {
|
func TestTheStepsVerbAnswersNoStepForAnImage(t *testing.T) {
|
||||||
@@ -22,3 +26,23 @@ func TestTheStepsVerbAnswersNoStepForAnImage(t *testing.T) {
|
|||||||
t.Fatal("the module serves no verb steps")
|
t.Fatal("the module serves no verb steps")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Design 47 G §463, §480 (BI3): steps with drop writes the hand-act log; a log that refuses answers error; a drop
|
||||||
|
// without why is refused.
|
||||||
|
func TestTheStepsDropWritesTheHandActLog(t *testing.T) {
|
||||||
|
var logged []string
|
||||||
|
m := &Module{RecordAct: func(what, why string) error { logged = append(logged, why); return nil }}
|
||||||
|
args := map[string]any{"module": "m", "artifact": "m:server", "drop": true, "why": "stale"}
|
||||||
|
got, err := m.dropDetected(args, "shanks.person")
|
||||||
|
if a, _ := got.(StepsAnswer); err != nil || a.Dropped != "m:server" || len(logged) != 1 {
|
||||||
|
t.Fatalf("drop = %+v, %v, logged %v", got, err, logged)
|
||||||
|
}
|
||||||
|
if _, err := m.dropDetected(map[string]any{"module": "m", "artifact": "m:server", "drop": true}, "shanks.person"); err == nil {
|
||||||
|
t.Error("a drop without why was taken")
|
||||||
|
}
|
||||||
|
m.RecordAct = func(string, string) error { return errors.New("log down") }
|
||||||
|
got, _ = m.dropDetected(args, "shanks.person")
|
||||||
|
if a, _ := got.(StepsAnswer); !strings.Contains(a.Error, "hand-act log") || a.Dropped != "" {
|
||||||
|
t.Fatalf("a refusing log: %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ module meshbuildimage
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||||
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"node-tools"
|
"node-tools"
|
||||||
],
|
],
|
||||||
"invokes": [
|
"invokes": [
|
||||||
|
"seat:mesh-hand-acts.hand-act",
|
||||||
"seat:container-image-registry.publish",
|
"seat:container-image-registry.publish",
|
||||||
"seat:container-image-registry.publish-part",
|
"seat:container-image-registry.publish-part",
|
||||||
"seat:container-image-registry.mirror",
|
"seat:container-image-registry.mirror",
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
|
||||||
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handActRecorder writes an act by hand in the hand-act log of the seat mesh-hand-acts (design 47 G §480); a log that
|
||||||
|
// does not take it is an error, never a quiet success.
|
||||||
|
func handActRecorder(what, why string) error {
|
||||||
|
_, err := meshhandactsclient.Recorder{Ask: stdio.Ask}.Record(meshhandactsclient.Request{What: what, Why: why, Cause: "build-steps-drop", Verb: "steps drop"})
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -50,6 +50,8 @@ func main() {
|
|||||||
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
ReadSteps: readOf(stdio.State(seat.StateSteps)),
|
||||||
Detected: stdio.State(seat.StateDetectedSteps).Put,
|
Detected: stdio.State(seat.StateDetectedSteps).Put,
|
||||||
ReadDetected: readOf(stdio.State(seat.StateDetectedSteps)).Get,
|
ReadDetected: readOf(stdio.State(seat.StateDetectedSteps)).Get,
|
||||||
|
ForgetDetected: stdio.State(seat.StateDetectedSteps).Delete,
|
||||||
|
RecordAct: handActRecorder,
|
||||||
Pauses: stdio.State(sdkstep.PausesState).Put,
|
Pauses: stdio.State(sdkstep.PausesState).Put,
|
||||||
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
|
ReadPauses: readOf(stdio.State(sdkstep.PausesState)),
|
||||||
ForgetPause: stdio.State(sdkstep.PausesState).Delete,
|
ForgetPause: stdio.State(sdkstep.PausesState).Delete,
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ type Module struct {
|
|||||||
// Detected and ReadDetected are the Put and the read of the state detected-steps (design 47 G §326, §414).
|
// Detected and ReadDetected are the Put and the read of the state detected-steps (design 47 G §326, §414).
|
||||||
Detected buildstep.Put
|
Detected buildstep.Put
|
||||||
ReadDetected func(key string) (json.RawMessage, error)
|
ReadDetected func(key string) (json.RawMessage, error)
|
||||||
|
// ForgetDetected deletes one key of detected-steps; RecordAct writes an act in the hand-act log of the seat
|
||||||
|
// mesh-hand-acts (§463, §480): a drop writes it first.
|
||||||
|
ForgetDetected func(key string) error
|
||||||
|
RecordAct func(what, why string) error
|
||||||
// Pauses is the Put of the pauses state, which keeps the node's pause key without an age; ReadPauses reads it back.
|
// 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
|
Pauses buildstep.Put
|
||||||
ReadPauses StateRead
|
ReadPauses StateRead
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ type StepsAnswer struct {
|
|||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
Steps []DetectedStep `json:"steps"`
|
Steps []DetectedStep `json:"steps"`
|
||||||
Kept bool `json:"kept,omitempty"`
|
Kept bool `json:"kept,omitempty"`
|
||||||
|
// Dropped is the artifact whose key a drop deleted; Error why a drop deleted nothing, naming the hand-act log.
|
||||||
|
Dropped string `json:"dropped,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectSteps finds the steps of the package directories under dir.
|
// detectSteps finds the steps of the package directories under dir.
|
||||||
@@ -77,26 +80,31 @@ func (m *Module) stepsTool() stdio.Tool {
|
|||||||
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
return stdio.Tool{Name: Seat + "." + seat.VerbSteps,
|
||||||
Description: "Which pipeline steps a TypeScript artifact can run: the type-check as lint, always; unit tests where a package has a test script; " +
|
Description: "Which pipeline steps a TypeScript artifact can run: the type-check as lint, always; unit tests where a package has a test script; " +
|
||||||
"integration tests where one has a test:integration script. A level with no test is not answered.",
|
"integration tests where one has a test:integration script. A level with no test is not answered.",
|
||||||
Input: map[string]any{"type": "object", "required": []string{"module", "artifact", "fetch"}, "properties": map[string]any{
|
Input: map[string]any{"type": "object", "required": []string{"module", "artifact"}, "properties": map[string]any{
|
||||||
"module": map[string]any{"type": "string"},
|
"module": map[string]any{"type": "string"},
|
||||||
"artifact": map[string]any{"type": "string", "description": "the artifact reference, <module>:<artifact>"},
|
"artifact": map[string]any{"type": "string", "description": "the artifact reference, <module>:<artifact>"},
|
||||||
"fingerprint": map[string]any{"type": "string"},
|
"fingerprint": map[string]any{"type": "string"},
|
||||||
"fetch": map[string]any{"type": "object", "description": "what the fetch tool is asked: the repository, the full commit and the module's directory"},
|
"fetch": map[string]any{"type": "object", "description": "what the fetch tool is asked: the repository, the full commit and the module's directory"},
|
||||||
"again": map[string]any{"type": "boolean", "description": "find them anew, not from the kept answer"},
|
"again": map[string]any{"type": "boolean", "description": "find them anew, not from the kept answer"},
|
||||||
|
"drop": map[string]any{"type": "boolean", "description": "delete the artifact's kept key instead of detecting; needs why (written in the hand-act log first)"},
|
||||||
|
"why": map[string]any{"type": "string", "description": "with drop, why: a non-empty text"},
|
||||||
}},
|
}},
|
||||||
Run: m.Short(m.steps)}
|
Run: m.Short(m.steps)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
||||||
str := func(k string) string { s, _ := args[k].(string); return s }
|
str := func(k string) string { s, _ := args[k].(string); return s }
|
||||||
|
if str("artifact") == "" || str("module") == "" {
|
||||||
|
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
||||||
|
}
|
||||||
|
if drop, _ := args["drop"].(bool); drop {
|
||||||
|
return m.dropDetected(args, stdio.CallerOf(args))
|
||||||
|
}
|
||||||
fetch, _ := args["fetch"].(map[string]any)
|
fetch, _ := args["fetch"].(map[string]any)
|
||||||
req, err := sdkstep.ParseFetchRequest(fetch)
|
req, err := sdkstep.ParseFetchRequest(fetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if str("artifact") == "" || str("module") == "" {
|
|
||||||
return nil, fmt.Errorf("steps: the module and the artifact are asked")
|
|
||||||
}
|
|
||||||
artifact, fp := str("artifact"), str("fingerprint")
|
artifact, fp := str("artifact"), str("fingerprint")
|
||||||
name := artifact
|
name := artifact
|
||||||
if _, n, ok := strings.Cut(artifact, ":"); ok {
|
if _, n, ok := strings.Cut(artifact, ":"); ok {
|
||||||
@@ -126,6 +134,27 @@ func (m *Module) steps(ctx context.Context, args map[string]any) (any, error) {
|
|||||||
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil
|
return StepsAnswer{Artifact: artifact, Fingerprint: fp, Steps: steps}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dropDetected is `steps` with drop (§463, §480): the act is written in the hand-act log with the caller the bus proved,
|
||||||
|
// then the kept key is deleted; a log that refuses leaves the key.
|
||||||
|
func (m *Module) dropDetected(args map[string]any, caller string) (any, error) {
|
||||||
|
artifact, _ := args["artifact"].(string)
|
||||||
|
why, _ := args["why"].(string)
|
||||||
|
module, _ := args["module"].(string)
|
||||||
|
name := artifact
|
||||||
|
if _, n, ok := strings.Cut(artifact, ":"); ok {
|
||||||
|
name = n
|
||||||
|
}
|
||||||
|
key := seat.DetectedStepsByModuleArtifactKey(module, name)
|
||||||
|
failed, err := buildstep.DropDetected(artifact, why, caller, key, m.RecordAct, m.ForgetDetected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if failed != "" {
|
||||||
|
return StepsAnswer{Artifact: artifact, Error: failed}, nil
|
||||||
|
}
|
||||||
|
return StepsAnswer{Artifact: artifact, Dropped: artifact}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// stepsOfTree fetches the commit alone (nothing installed) into a workspace removed at the end.
|
// stepsOfTree fetches the commit alone (nothing installed) into a workspace removed at the end.
|
||||||
func (m *Module) stepsOfTree(ctx context.Context, req sdkstep.FetchRequest) ([]DetectedStep, error) {
|
func (m *Module) stepsOfTree(ctx context.Context, req sdkstep.FetchRequest) ([]DetectedStep, error) {
|
||||||
if err := os.MkdirAll(m.Work, 0o700); err != nil {
|
if err := os.MkdirAll(m.Work, 0o700); err != nil {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,3 +53,27 @@ func TestTheStepsVerbIsServedAndRefusesAnAskWithoutAnArtifact(t *testing.T) {
|
|||||||
t.Error("an ask with no artifact was answered")
|
t.Error("an ask with no artifact was answered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Design 47 G §463, §480 (BN9): steps with drop writes the hand-act log first, then deletes the key; a log that
|
||||||
|
// refuses deletes nothing; a drop without why is refused.
|
||||||
|
func TestTheStepsDropWritesTheHandActLogFirst(t *testing.T) {
|
||||||
|
var order []string
|
||||||
|
m := &Module{
|
||||||
|
ForgetDetected: func(key string) error { order = append(order, "delete "+key); return nil },
|
||||||
|
RecordAct: func(what, why string) error { order = append(order, "log "+why); return nil },
|
||||||
|
}
|
||||||
|
args := map[string]any{"module": "mod", "artifact": "mod:web", "drop": true, "why": "stale"}
|
||||||
|
got, err := m.dropDetected(args, "shanks.person")
|
||||||
|
if a, _ := got.(StepsAnswer); err != nil || a.Dropped != "mod:web" || !reflect.DeepEqual(order, []string{"log stale", "delete mod.web"}) {
|
||||||
|
t.Fatalf("drop = %+v, %v, order %v", got, err, order)
|
||||||
|
}
|
||||||
|
if _, err := m.dropDetected(map[string]any{"module": "mod", "artifact": "mod:web", "drop": true}, "shanks.person"); err == nil {
|
||||||
|
t.Error("a drop without why was taken")
|
||||||
|
}
|
||||||
|
order = nil
|
||||||
|
m.RecordAct = func(string, string) error { return errors.New("log down") }
|
||||||
|
got, err = m.dropDetected(args, "shanks.person")
|
||||||
|
if a, _ := got.(StepsAnswer); err != nil || !strings.Contains(a.Error, "hand-act log") || len(order) != 0 {
|
||||||
|
t.Fatalf("a refusing log: %+v, %v, order %v", got, err, order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ module meshbuildnode
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||||
|
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"npm-package-registry": "${dir:state}/package-registry.json"
|
"npm-package-registry": "${dir:state}/package-registry.json"
|
||||||
},
|
},
|
||||||
"invokes": [
|
"invokes": [
|
||||||
|
"seat:mesh-hand-acts.hand-act",
|
||||||
"seat:oci-artifact-registry.publish",
|
"seat:oci-artifact-registry.publish",
|
||||||
"seat:oci-artifact-registry.publish-part",
|
"seat:oci-artifact-registry.publish-part",
|
||||||
"seat:npm-package-registry.publish",
|
"seat:npm-package-registry.publish",
|
||||||
|
|||||||
Reference in New Issue
Block a user