package main import ( "fmt" "sort" "strings" "time" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-delivery/sdk/buildstep" stdio "git.novox.be/novox/mesh-sdk/go" ) // heardState is the builder's state toolchains-heard as the tools bundle reads and deletes in it. type heardState interface { Keys() ([]string, error) Get(key string) (*stdio.StateEntry, error) Delete(key string) error } // toolchainsTools are builder_toolchains, which lists the toolchains the builder heard from the build seats, and // builder_toolchains_drop, which deletes one key and writes it in the hand-act log (design 47 G §604, §546). The drop is // of class act, with the default callers: the bus refuses another caller. func toolchainsTools(st heardState, acts meshhandactsclient.Recorder, now func() time.Time) []stdio.Tool { return []stdio.Tool{ {Name: buildrun.ToolchainsTool, Description: "The toolchains the builder heard from the build seats' toolchain-reported, one key . each: its version, " + "recipe, the digest of its toolchain program and when it was said. The builder agrees a language's version from these, and forgets a node's " + "when the node-engine seat says the build module stopped there. Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{}}, Run: func(map[string]any) (any, error) { keys, err := st.Keys() if err != nil { return nil, err } sort.Strings(keys) rows := []map[string]any{} for _, k := range keys { e, err := st.Get(k) if err != nil || e == nil { continue } row := map[string]any{"key": k} if tc, err := buildstep.DecodeToolchain(e.Value); err != nil { row["error"] = err.Error() } else { row["version"], row["recipe"], row["digest"], row["said"] = tc.Version, tc.Recipe, tc.Digest, tc.At } rows = append(rows, row) } return map[string]any{"toolchains": rows}, nil }}, {Name: buildrun.ToolchainsDropTool, Description: "Drop one node's toolchain of a language from what the builder heard, by hand: a build module that left a node and said nothing. " + "The builder agrees without it; the node's build module says it again at its start and on each change. Written in the hand-act log (mesh-hand-acts).", Input: map[string]any{"type": "object", "required": []string{"key", "why"}, "properties": map[string]any{ "key": map[string]any{"type": "string", "description": "., as builder_toolchains lists it"}, "why": map[string]any{"type": "string", "description": "why, in plain words"}}}, Run: func(args map[string]any) (any, error) { str := func(k string) string { s, _ := args[k].(string); return strings.TrimSpace(s) } key, why := str("key"), str("why") if _, _, err := buildstep.ParseToolchainKey(key); err != nil { return nil, err } if why == "" { return nil, fmt.Errorf("why is required") } if e, err := st.Get(key); err != nil || e == nil { return nil, fmt.Errorf("%s holds no key %s", buildrun.ToolchainsHeardState, key) } if _, err := acts.Record(meshhandactsclient.Request{What: fmt.Sprintf("dropped the toolchain %s from what the builder heard", key), Why: why, Cause: buildrun.ToolchainsDropTool, Verb: "toolchains-drop"}); err != nil { return nil, fmt.Errorf("the hand act was not recorded through mesh-hand-acts, so nothing was dropped: %w", err) } if err := st.Delete(key); err != nil { return nil, err } return map[string]any{"key": key, "dropped": true, "at": now().UTC()}, nil }}, } }