A build module runs in the directory its build was unpacked in. When the node-engine replaced that build, every command the module started inherited a deleted working directory, and the toolchain probe's failure became a final refusal of the check that asked it (three checks on ace on 2026-10-26). forgekey: ExecRunnerClean starts its command in the HOME it is given, or the temporary directory; the other runners keep the process's directory while it exists and use the temporary directory once it is gone. buildstep: a guard whose probe failed answers a Refusal with Again set; the puller declines such an ask (delivery.StepDeclined) so its asker puts it again for another node or a later pull, instead of ending it refused. A version mismatch stays a refusal.
55 lines
2.3 KiB
Go
55 lines
2.3 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Guard is a build module's check that the toolchain installed on its node is the one its recipe builds with
|
|
// (novox/hq ADR 0469 §1; issue 1473). The same guard serves ADR 0470 §4's postgres major for the integration
|
|
// level, with its own Probe and Want.
|
|
type Guard struct {
|
|
// Language is the toolchain's key part: go or typescript (delivery.ToolchainKey).
|
|
Language string
|
|
// Want is the toolchain version the recipe builds with, and Recipe the recipe's own version. The indexer
|
|
// compares both texts as they are written here.
|
|
Want, Recipe string
|
|
// Probe reads the installed version (go version, node --version).
|
|
Probe func(ctx context.Context) (installed string, err error)
|
|
Node string
|
|
// Put is the toolchains state's Put (stdio.State(delivery.ToolchainsState).Put).
|
|
Put Put
|
|
}
|
|
|
|
// Check probes the installed version, writes it to <node>.<language> of the toolchains state every time it runs,
|
|
// and answers nil when it is the recipe's. Otherwise it answers a *Refusal naming both versions; the installed
|
|
// version is written even then, so the indexer sees that builders disagree. A probe that fails is a *Refusal
|
|
// naming its failure with Again set, and nothing is written: a toolchain that could not be read is not a toolchain
|
|
// known to be wrong, so the ask goes to another node or a later pull instead of ending refused (novox/hq issue 734).
|
|
// A write that fails is an error that is not a Refusal.
|
|
func (g Guard) Check(ctx context.Context) error {
|
|
installed, err := g.Probe(ctx)
|
|
if err != nil {
|
|
return &Refusal{Text: fmt.Sprintf("the installed %s version could not be read: %v", g.Language, err), Want: g.Want, Again: true}
|
|
}
|
|
key, err := delivery.ToolchainKey(g.Node, g.Language)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
raw, err := delivery.EncodeToolchain(delivery.Toolchain{Language: g.Language, Version: installed, Recipe: g.Recipe, At: now().UTC()})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := g.Put(key, json.RawMessage(raw)); err != nil {
|
|
return fmt.Errorf("buildstep: writing the toolchain %s: %w", key, err)
|
|
}
|
|
if installed != g.Want {
|
|
return &Refusal{Text: fmt.Sprintf("%s %s installed, the recipe builds with %s %s", g.Language, installed, g.Language, g.Want),
|
|
Want: g.Want, Have: installed}
|
|
}
|
|
return nil
|
|
}
|