Files
mesh-delivery/modules/mesh-delivery-sdk/steprun/phase.go
T
jschoubben 00b52e3c1f BG4, BN4, BI4: the puller says ask-taken, step-progress and the end of any ask
A holder says ask-taken when an ask starts, step-progress every minute while it runs (phase fetch, build or run) and
step-ended for an ask outside a pipeline, each on the seat's subject of its node (SayOn); the asker is the last token of
the subject the ask came on (Pulled.Subject).

Builds: design 47 G §496, §602, §603, §481

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:33:26 +01:00

75 lines
2.2 KiB
Go

package steprun
import (
"context"
"strings"
"sync"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
)
// **A running ask says its phases** (issue 1890, 2026-10-26 19:09: a mesh-catalog try's step on the workstation ran 30 minutes
// and its module's journal held no line naming its ask, so nobody could tell whether it fetched, linted or tested). The
// puller hands every ask's run a phase reporter in its context; RunAsk and a module's own step bodies call Phase as they
// move (fetch and merge, a level's digest, lint, a level's tests), and each phase is logged with the ask's id and its
// time since the start, and written to the ask's running step key (sdkstep.Step.Phase), so its asker and the delivery
// view read where it stands from the bus.
type phaseKey struct{}
// Phase says that the ask whose run ctx belongs to entered phase; nothing outside an ask's run (a tool called by hand).
func Phase(ctx context.Context, phase string) {
if r, ok := ctx.Value(phaseKey{}).(*phases); ok && r != nil {
r.enter(strings.TrimSpace(phase))
}
}
// phases is one running ask's reporter: it writes the step key again with each new phase until the step ends.
type phases struct {
mu sync.Mutex
put Put
key string
step sdkstep.Step
ended bool
logf func(format string, args ...any)
prefix string
}
func (r *phases) enter(phase string) {
if phase == "" {
return
}
r.mu.Lock()
defer r.mu.Unlock()
if r.ended || phase == r.step.Phase {
return
}
at := now().UTC()
r.step.Phase, r.step.PhaseAt = phase, at
if r.logf != nil {
r.logf("%s: %s, %s after its start", r.prefix, phase, at.Sub(r.step.Started).Round(time.Second))
}
if r.put == nil {
return
}
if err := write(r.put, r.key, r.step); err != nil && r.logf != nil {
r.logf("%s: the phase %s could not be written to %s: %v", r.prefix, phase, r.key, err)
}
}
// end stops the writes (the step's end is written after it) and answers the step with its last phase, for the end.
func (r *phases) end() sdkstep.Step {
r.mu.Lock()
defer r.mu.Unlock()
r.ended = true
return r.step
}
// phase is the phase the ask is in now, "" before its first.
func (r *phases) phase() string {
r.mu.Lock()
defer r.mu.Unlock()
return r.step.Phase
}