Files
mesh-delivery/modules/mesh-delivery-builder/internal/build/waits.go
T
jschoubben 524fbb02ef WM13 (builder half): a key queued behind a package says wait-held and wait-ended in the one wait form
The builder says wait-held for a key queued behind a package, the package as its part and on (package:<ref>), on its
stream waits, and wait-ended published once the key is claimed; a toolchain wait never ends by that, and a package wait
never by a toolchain's end. Files: internal/build/waits.go (Package, On, WaitPublished), internal/build/run.go (Hold at
the queued result, End at the claim), cmd/mesh-delivery-builder/waits.go (Part in the body, the End rule).

Builds: design 47 G A-192, §499

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

136 lines
5.1 KiB
Go

package build
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
)
// **The builder's toolchain wait** (design 47 G §672, §267, §276): the builder holds an entry while its fingerprint names a
// toolchain version of its language other than the one the agreed toolchain of that language names.
const (
// WaitsTool is builder_waits, which reads the waits; ReleaseTool is builder_release, which builds the entry with the
// running build module (§276), a hand act written in the hand-act log.
WaitsTool = "builder_waits"
ReleaseTool = "builder_release"
// WaitsState is the builder's old state section of the waits, one key <module>.<fingerprint>.
WaitsState = "waits"
WaitsBucket = builds.BuilderModule + "_" + WaitsState
// ModeRelease is the Mode of a reasks value of builder_release.
ModeRelease = "release"
// WaitRan ends a wait when a toolchain-agreed names the version; WaitReleased when builder_release ended it.
WaitRan = "ran"
// WaitPublished ends the wait of a key queued behind a package, when the key is claimed (the package is published).
WaitPublished = "published"
WaitReleased = "released"
)
// ToolchainWait is one wait: the entry's module and fingerprint, and the toolchain it waits for.
type ToolchainWait struct {
Module string `json:"module"`
Fingerprint string `json:"fingerprint"`
Language string `json:"language"`
Version string `json:"version"`
Since time.Time `json:"since"`
// Package is the package version a key queued behind it waits for, `<name>@<version>` (ADR 0603 §8; design 47 G A-192): the wait is the
// package's, not a toolchain's, and Language and Version are empty.
Package string `json:"package,omitempty"`
}
// On is what the wait waits for, as the wait's `on` says it.
func (w ToolchainWait) On() string {
if w.Package != "" {
return "package:" + w.Package
}
return "toolchain:" + w.Language + "@" + w.Version
}
// Waits keeps the builder's waits: Hold writes one in the state waits and says wait-held, once per wait; End deletes it and
// says wait-ended with how, and answers whether one was held.
type Waits interface {
Hold(ctx context.Context, w ToolchainWait)
End(ctx context.Context, module, fp, how string) bool
}
// toolchainWait is the toolchain wait of a module's fingerprint, or false when no language of its plan names a version
// other than the agreed one (a language with no agreed toolchain is not a wait of §672).
func (b *Builder) toolchainWait(module string, m meshcatalog.ModulesByModuleValue, plan Plan) (ToolchainWait, bool) {
for _, bm := range plan.Modules() {
if !plan.NeedsToolchain(bm) {
continue
}
language, _ := b.Seats.LanguageOf(bm)
ag := sdkstep.ToolchainAgreement(b.Toolchains.Entries(language))
if want, ok := m.Toolchains[language]; ag.Wait == "" && ok && want.Version != ag.Toolchain.Version {
return ToolchainWait{Module: module, Fingerprint: m.Fp2, Language: language, Version: want.Version, Since: b.now()}, true
}
}
return ToolchainWait{}, false
}
// Release builds the held entry of module at fp with the running build module, now, and ends its wait released (design 47
// G §276, §672). It answers why nothing was done, or empty.
func (b *Builder) Release(ctx context.Context, module, fp, why, by string) string {
b.init()
key, err := builds.BuilderBuildKey(module, fp)
if err != nil {
return err.Error()
}
if b.Waits == nil {
return "this builder keeps no waits"
}
b.mu.Lock()
m, known := b.known[module]
b.mu.Unlock()
if !known || m.Fp2 != fp || m.Manifest == "" {
return fmt.Sprintf("module %s does not have the fingerprint %s now: the builder builds the module's current fingerprint", module, fp)
}
b.mu.Lock()
if b.released == nil {
b.released = map[string]bool{}
}
b.released[key] = true
b.mu.Unlock()
if !b.Waits.End(ctx, module, fp, WaitReleased) {
b.mu.Lock()
delete(b.released, key)
b.mu.Unlock()
return fmt.Sprintf("%s holds no toolchain wait: %s builds a failed key again", key, BuildAgainTool)
}
b.logf("%s: released by %s (%s): built with the running build module", key, by, why)
b.startOnce(ctx, key, module, m)
return ""
}
func (b *Builder) isReleased(key string) bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.released[key]
}
func (b *Builder) unrelease(key string) {
b.mu.Lock()
delete(b.released, key)
b.mu.Unlock()
}
// NewRelease is the value builder_release writes for a module's fingerprint: its key and the value.
func NewRelease(module, fp, why, by string, at time.Time) (string, []byte, error) {
key, err := builds.BuilderBuildKey(module, fp)
if err != nil {
return "", nil, err
}
if strings.TrimSpace(why) == "" {
return "", nil, errors.New("say why the entry is released (why)")
}
data, err := json.Marshal(ReAsk{V: 1, Why: strings.TrimSpace(why), By: by, At: at.UTC(), Mode: ModeRelease, Module: module, FP: fp})
return key, data, err
}