Merge feat/47-G-fix11-c1: the build modules say steprun, the catalog's buildstep stays gone

Builds: design 47 G §464, §680

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:29:50 +01:00
46 changed files with 1006 additions and 123 deletions
@@ -7,6 +7,7 @@ import (
stdio "git.novox.be/novox/mesh-sdk/go"
"net/http"
"os"
"os/exec"
"sync"
"time"
@@ -51,6 +52,12 @@ type Module struct {
// Detected and ReadDetected are the Put and the read of the state detected-steps (design 47 G §326, §414).
Detected steprun.Put
ReadDetected func(key string) (json.RawMessage, error)
// ArtifactStoreFile is the binding of the provision artifact-store, read for a group run's package members (§465).
ArtifactStoreFile string
// 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 steprun.Put
ReadPauses StateRead
@@ -58,9 +65,15 @@ type Module struct {
ForgetPause func(key string) error
// WatchPauses follows the drops other holders write in the shared pauses state (stdio.State(...).Watch).
WatchPauses func(key string, handle func(stdio.StateChange) error) error
// Emit tells the bus an event (stdio.Emit); nil emits nothing. The guard emits toolchain.reported after each write of
// the toolchains key, so the indexer, which holds no timer, learns that the bucket exists (issue 1444).
Emit func(eventType string, body any) error
// SayToolchain says an event on a seat's subject (stdio.SeatPublish; nil says none, a test's); ToolchainProgram finds the
// node program whose digest the toolchain's is (exec.LookPath when nil).
SayToolchain func(subject string, body any, id string) error
ToolchainProgram func() (string, error)
reporterOnce sync.Once
reporter *toolchainReporter
digestMu sync.Mutex
digestFor string
progDigest string
// Ctx is the process's context, which a step runs under (the tool call's is cut at 30 s, the step's is not).
Ctx context.Context
Say func(format string, a ...any)
@@ -260,20 +273,38 @@ func pathOrDefault() string {
return defaultPath
}
// ToolchainReported is the event the guard emits after it writes the toolchains key.
const ToolchainReported = "toolchain.reported"
// reportingPut is the toolchains Put that then emits toolchain.reported with the node and the language. A write that
// failed emits nothing; an emit that failed is said and does not refuse the step.
// reportingPut is the toolchains Put that then says toolchain-reported on the Node build seat's subject of this node when
// the toolchain changed (design 47 G §492): at the module's start and on each change of the key. A write that failed
// says nothing; an event that failed is said and does not refuse the step.
func (m *Module) reportingPut() steprun.Put {
return func(key string, value any) (uint64, error) {
rev, err := m.Toolchains(key, value)
if err != nil || m.Emit == nil {
return rev, err
m.reporterOnce.Do(func() {
m.reporter = &toolchainReporter{AskLanguage: sdkstep.AskNode, Node: m.Node, Publish: m.SayToolchain}
})
return putReporting(m.Toolchains, m.reporter, m.programDigest, m.Say)
}
// programDigest is the digest of the node program this node's node-toolchain provides, the `node` found on the path: read
// again only when the installed version changed (design 47 G §472; the lead's answer A-152).
func (m *Module) programDigest(version string) string {
m.digestMu.Lock()
defer m.digestMu.Unlock()
if m.digestFor == version && m.progDigest != "" {
return m.progDigest
}
find := m.ToolchainProgram
if find == nil {
find = func() (string, error) { return exec.LookPath("node") }
}
path, err := find()
if err == nil {
var d string
if d, err = programFileDigest(path); err == nil {
m.digestFor, m.progDigest = version, d
return d
}
if eerr := m.Emit(ToolchainReported, map[string]string{"node": m.Node, "language": "typescript"}); eerr != nil && m.Say != nil {
m.Say("the event %s could not be emitted: %v", ToolchainReported, eerr)
}
return rev, nil
}
if m.Say != nil {
m.Say("the digest of the node toolchain program could not be read: %v", err)
}
return ""
}