Merge main into fix/1901-failure-contract: pin mesh-sdk a2759ec (the group's sdk head), the failure kit check's marker lists what it reads (issue 1936), and the registry test's password can no longer match a random workspace name (issue 1901)
mesh/delivery merged: source.main moved to novox/mesh-catalog@64530a8dcb08
mesh/delivery-group group fix/1901-failure-contract merged: every member is merged
mesh/check checking e8a953818c71 on 64530a8dcb08 | novox/mesh-catalog#516 in group fix/1901-failure-contract

This commit is contained in:
2026-10-27 00:39:35 +01:00
130 changed files with 2872 additions and 245 deletions
@@ -84,9 +84,17 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
// The capacity's own change wakes the puller (issue 1887): the runtime's watch of the config value as mesh-config
// resolved it; the settings file is read only when that watch cannot start. A test's Capacity is its own.
var watch func(string, func(stdio.ProvisionChange) error) error
// And the CPU bounds it (issue 1806's follow-up): the configured capacity is an upper bound, the node's build slice
// gives floor(its quota's cores / the module's cores-per-step) at once, read live; a test judges by its own Capacity.
// And the node's build hours (the operator, 2026-10-26: g14 builds only from 18:00 to 06:00): outside them it takes
// nothing new.
var cores func() float64
var hours func() (*buildstep.BuildWindow, string)
if capacity == nil {
hours = buildstep.NodeBuildHours(m.Settings, m.Node, m.Say)
capacity = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, m.Say)
watch = buildstep.WatchConfigServed
cores = buildstep.NodeCoresPerStep(m.Settings, buildstep.DefaultCoresPerStep[delivery.AskGo], m.Say)
}
ask := m.askSteps(steps)
var load func(string) ([]byte, error)
@@ -97,7 +105,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
}
}
return &buildstep.Puller{Source: source, Language: delivery.AskGo, Node: m.Node, Put: m.Steps, PausePut: m.Pauses, Load: load,
Capacity: capacity, WatchConfig: watch, Logf: m.Say, Has: m.Has,
Capacity: capacity, WatchConfig: watch, CoresPerStep: cores, BuildHours: hours, Logf: m.Say, Has: m.Has,
Run: func(ctx context.Context, a delivery.StepAsk) (any, error) {
if err := m.Check(ctx); err != nil {
return nil, err
@@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"fmt"
"os"
"slices"
"strings"
@@ -125,12 +126,28 @@ func TestManifestClaimsTheGoBuildSeatAndReadsItsCapacity(t *testing.T) {
raw, _ := os.ReadFile(moduleDir + "/module.json")
var doc struct {
Config map[string]struct {
Default string `json:"default"`
Default json.RawMessage `json:"default"`
} `json:"config"`
}
_ = json.Unmarshal(raw, &doc)
if doc.Config[buildstep.CapacitySetting].Default != buildstep.DefaultCapacities {
t.Errorf("the capacity's default = %q, want %q", doc.Config[buildstep.CapacitySetting].Default, buildstep.DefaultCapacities)
// Issue 1806's follow-up: the cores a step needs is a setting of its own, in the settings file, with the SDK's default.
if cores := string(doc.Config[buildstep.CoresPerStepSetting].Default); cores != fmt.Sprint(buildstep.DefaultCoresPerStep[delivery.AskGo]) {
t.Errorf("the cores-per-step default = %s, want %g", cores, buildstep.DefaultCoresPerStep[delivery.AskGo])
}
if got := buildstep.NodeCoresPerStep(moduleDir+"/files/settings.json", 0, nil)(); got != buildstep.DefaultCoresPerStep[delivery.AskGo] {
t.Errorf("the settings template holds cores-per-step %g", got)
}
// And the build hours (the operator, 2026-10-26): a setting of their own, empty by default (any hour), in the template.
if hours := string(doc.Config[buildstep.BuildHoursSetting].Default); hours != `""` {
t.Errorf("the build-hours default = %s, want empty", hours)
}
if w, fault := buildstep.NodeBuildHours(moduleDir+"/files/settings.json", "g14", t.Errorf)(); w != nil || fault != "" {
t.Errorf("the settings template holds build hours %v (%s)", w, fault)
}
var capacity string
_ = json.Unmarshal(doc.Config[buildstep.CapacitySetting].Default, &capacity)
if capacity != buildstep.DefaultCapacities {
t.Errorf("the capacity's default = %q, want %q", capacity, buildstep.DefaultCapacities)
}
settings := resource(t, m, "settings")
if settings["path"] != "${dir:state}/settings.json" {
@@ -116,7 +116,7 @@ func TestTheIntegrationLevelDigestsUnderBothTags(t *testing.T) {
t.Errorf("the integration digest listed %q", s.command)
}
}
if DigestVersion != "5" {
if DigestVersion != "6" {
t.Errorf("the digest's form changed in kind and is still version %s", DigestVersion)
}
}
@@ -15,6 +15,8 @@ import (
func (m *Module) cpuLimits() buildcpu.Limits {
slots := 1
switch {
case m.Held != nil:
slots = m.Held.CapacityNow() // the capacity the puller judges by, bounded by the node's build slice
case m.Capacity != nil:
slots = m.Capacity()
case m.Settings != "":
@@ -32,5 +34,9 @@ func (m *Module) threadEnv() []string {
// cpuStatus is what the status tool says of it: the limits a step is given, and what the kernel holds this runtime
// to (its unit and its slice), read from its cgroup rather than assumed.
func (m *Module) cpuStatus() map[string]any {
return map[string]any{"limits": m.cpuLimits(), "in-force": buildcpu.ReadInForce()}
out := map[string]any{"limits": m.cpuLimits(), "in-force": buildcpu.ReadInForce()}
if m.Held != nil {
out["capacity"] = m.Held.CapacityDetail() // the capacity judged by and why (issue 1806's follow-up)
}
return out
}
+138 -14
View File
@@ -16,6 +16,7 @@ import (
"io"
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strconv"
@@ -29,17 +30,25 @@ import (
// commits (the lead's decision L5) and the integration level's tags integration,isolation (the checker's agreement).
// 4: the siblings' commits enter only the digest of a package whose tests reach a file naming MESH_CHECK_BESIDE (ADR 0536 §1).
// 5: a catalogue module's non-Go files enter the digest of its packages (issue 1935).
const DigestVersion = "5"
// 6: a reads-outside marker that lists what its tests read enters the digest with those files' contents (issue 1936).
const DigestVersion = "6"
// besideVariable is the name a file bears when it reads where the siblings lie; a package whose tests reach such a file
// depends on the siblings' commits.
const besideVariable = "MESH_CHECK_BESIDE"
// ReadsOutsideMarker is the name of a file in a package's directory that says its tests read files outside it (a
// repository's documents, a sibling directory): such a package is never kept (issue 1935). The digest hashes only what
// the Go toolchain lists, so it cannot name what a test opens elsewhere; a marker file, beside the Go files where a
// reader of the package sees it, is the plain way to say so until the checker keeps the files a test opened (the Go
// test log) with each verdict.
// repository's documents, a sibling directory), which the Go toolchain does not list (issue 1935). It lists, one per
// line, what those tests read, as paths from the repository's root; a path may be a glob (filepath.Match), and a
// directory it names is read whole (issue 1936). Blank lines and lines starting with # are not read. A line starting
// with ? names a path whose absence is what the tests read (a file that must not be there): it may match nothing.
//
// The digest then hashes, besides what the toolchain lists, every line of the marker, every file it names (under its
// path in the tree, by content) and every file of the package's own directory, Go files included (a file its build tags
// exclude is one go list does not hash). A marker that lists nothing, a line that is not a clean relative path, or a
// line without ? that matches nothing makes the package one that is never kept (its digest a fresh random hex): a list
// nobody can trust keeps nothing. The exact fix, the files a test opened kept
// from the Go test log with each verdict, replaces the lists (issue 1936).
const ReadsOutsideMarker = "reads-outside"
// NeedsMarker is the name of a file in a package's directory that names, one per line, what its integration tests need
@@ -128,9 +137,13 @@ type DigestResult struct {
// only when they are the siblings it gave the fetch.
Beside []string `json:"beside,omitempty"`
WithoutTests []string `json:"without_tests,omitempty"`
// ReadsOutside names the packages that carry the marker: each has a digest that no kept verdict can have (a fresh
// random one), so the check runs it every time.
// ReadsOutside names the packages whose marker lists nothing it can trust (ReadsOutsideMarker): each has a digest
// that no kept verdict can have (a fresh random one), so the check runs it every time.
ReadsOutside []string `json:"reads_outside,omitempty"`
// neverKept is, per package of ReadsOutside, why its marker cannot be trusted; hashedFiles caches the content lines
// of the files markers name, which several packages share (the catalogue's manifests).
neverKept map[string]string
hashedFiles map[string]string
// Needs are the packages whose tests at this level need something of the node, as their NeedsMarker names it.
Needs []delivery.PackageNeeds `json:"needs,omitempty"`
// JudgedBy is the program that ran the digest (ADR 0535 §5): "installed", or "candidate <merge commit>" under the
@@ -247,7 +260,7 @@ func (m *Module) digestHere(ctx context.Context, workspace, level string) (Diges
sort.Slice(res.Needs, func(i, j int) bool { return res.Needs[i].Package < res.Needs[j].Package })
for _, p := range res.ReadsOutside {
if m.Say != nil {
m.Say("the %s level of %s always runs: its directory carries %s, so its tests read files outside it that no digest names", level, p, ReadsOutsideMarker)
m.Say("the %s level of %s always runs: its %s %s, so its tests read files outside it that no digest names", level, p, ReadsOutsideMarker, res.neverKept[p])
}
}
return res, nil
@@ -316,18 +329,27 @@ func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, he
res.Needs = append(res.Needs, delivery.PackageNeeds{Package: path, Needs: n})
}
}
if _, err := os.Stat(filepath.Join(root.Dir, ReadsOutsideMarker)); err == nil {
// The digest of a package that reads outside itself is one nothing can have kept: the key is a fresh random
// hex, so the ask finds no kept verdict and runs it, and what the checker writes under it is never read again.
declared, why, err := declaredReads(tree, root, res.hashed())
if err != nil {
return fmt.Errorf("%s: its %s: %w", path, ReadsOutsideMarker, err)
}
if why != "" {
// The digest of a package whose reads outside itself are not listed is one nothing can have kept: the key is a
// fresh random hex, so the ask finds no kept verdict and runs it, and what the checker writes under it is never
// read again.
var nonce [32]byte
if _, err := rand.Read(nonce[:]); err != nil {
return err
}
res.Packages[path] = hex.EncodeToString(nonce[:])
res.ReadsOutside = append(res.ReadsOutside, path)
if res.neverKept == nil {
res.neverKept = map[string]string{}
}
res.neverKept[path] = why
continue
}
d, err := packageDigest(root, testMain, pkgs, tree, header, beside)
d, err := packageDigest(root, testMain, pkgs, tree, header, beside, declared)
if err != nil {
return fmt.Errorf("%s: %w", path, err)
}
@@ -340,14 +362,15 @@ func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, he
// tests reach in the module's own tree (by their content, under their path in the tree), the version and checksum of
// every module outside it, and the package's own test data. The beside lines (the siblings' commits) join the header
// only when a file the tests reach names MESH_CHECK_BESIDE. A package of the standard library is the Go version's.
func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree string, header, beside []string) (string, error) {
// The lines a package's ReadsOutsideMarker declares join them (declaredReads).
func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree string, header, beside, declared []string) (string, error) {
reached := map[string]*goPackage{root.ImportPath: root}
for _, dep := range testMain.Deps {
if p := pkgs[dep]; p != nil {
reached[dep] = p
}
}
lines := []string{}
lines := append([]string{}, declared...)
readsBeside := false
for _, p := range reached {
if p.Standard || p.Module == nil {
@@ -469,3 +492,104 @@ func contentLine(tree string, p *goPackage, path string) (string, error) {
sum := sha256.Sum256(raw)
return "file " + label + " " + hex.EncodeToString(sum[:]), nil
}
// hashed is the cache of content lines a digest step shares across its packages.
func (r *DigestResult) hashed() map[string]string {
if r.hashedFiles == nil {
r.hashedFiles = map[string]string{}
}
return r.hashedFiles
}
// declaredReads are the digest lines a package's ReadsOutsideMarker adds: each of its lines, the content line of every
// file it names, and those of every file of the package's own directory. Without a marker it answers nothing. why is
// set, and the lines nil, when the marker cannot be trusted: it lists nothing, a line is not a clean relative path, or
// a line without ? matches nothing.
func declaredReads(tree string, root *goPackage, cache map[string]string) (lines []string, why string, err error) {
raw, err := os.ReadFile(filepath.Join(root.Dir, ReadsOutsideMarker))
if errors.Is(err, fs.ErrNotExist) {
return nil, "", nil
}
if err != nil {
return nil, "", err
}
var patterns []string
for _, l := range strings.Split(string(raw), "\n") {
if l = strings.TrimSpace(l); l != "" && !strings.HasPrefix(l, "#") {
patterns = append(patterns, l)
}
}
if len(patterns) == 0 {
return nil, "lists nothing", nil
}
seen := map[string]bool{}
add := func(path string) error {
if seen[path] {
return nil
}
seen[path] = true
l, ok := cache[path]
if !ok {
var err error
if l, err = contentLine(tree, root, path); err != nil {
return err
}
cache[path] = l
}
lines = append(lines, "read "+l)
return nil
}
for _, l := range patterns {
pattern, absent := strings.CutPrefix(l, "?")
if !cleanTreePattern(pattern) {
return nil, fmt.Sprintf("names %q, which is not a clean path from the repository's root", l), nil
}
matches, err := filepath.Glob(filepath.Join(tree, filepath.FromSlash(pattern)))
if err != nil {
return nil, fmt.Sprintf("names %q, which is not a glob", l), nil
}
if len(matches) == 0 && !absent {
return nil, fmt.Sprintf("names %q, which matches nothing (a path whose absence is read starts with ?)", l), nil
}
lines = append(lines, "reads "+l)
for _, match := range matches {
if err := walkFiles(match, add); err != nil {
return nil, "", err
}
}
}
// Every file of the package's own directory: its lists, its marker, and its Go files too, since go list leaves a
// file its build tags exclude (an integration test a unit test globs) out of what the digest hashes. A file the
// toolchain lists is hashed twice, which changes nothing.
err = walkFiles(root.Dir, add)
return lines, "", err
}
// cleanTreePattern says a marker's line is a path from the repository's root that stays inside it.
func cleanTreePattern(p string) bool {
if p == "" || strings.ContainsAny(p, " \t\\") || strings.HasPrefix(p, "/") || path.Clean(p) != p {
return false
}
for _, e := range strings.Split(p, "/") {
if e == ".." || e == "." {
return false
}
}
return true
}
// walkFiles calls each with every file at or under path, skipping .git and node_modules, in lexical order.
func walkFiles(path string, each func(string) error) error {
return filepath.WalkDir(path, func(p string, e fs.DirEntry, err error) error {
if err != nil {
return err
}
if e.IsDir() {
if n := e.Name(); p != path && (n == ".git" || n == "node_modules") {
return filepath.SkipDir
}
return nil
}
return each(p)
})
}
@@ -173,7 +173,7 @@ func TestAModuleOutsideTheTreeCountsByVersionAndChecksum(t *testing.T) {
testMain := &goPackage{ImportPath: "x/r.test", Deps: []string{ext.ImportPath}}
pkgs[root.ImportPath] = root
digest := func() string {
d, err := packageDigest(root, testMain, pkgs, dir, []string{"digest 1"}, nil)
d, err := packageDigest(root, testMain, pkgs, dir, []string{"digest 1"}, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -190,28 +190,74 @@ func TestAModuleOutsideTheTreeCountsByVersionAndChecksum(t *testing.T) {
}
}
// Issue 1935: a package whose directory carries the reads-outside marker is never kept: its digest is one no kept verdict
// can have (it differs on every call, though nothing changed), and the answer names it. A package without the marker
// keeps its digest.
func TestAPackageWithTheReadsOutsideMarkerIsNeverKept(t *testing.T) {
// Issue 1935, 1936: a package whose reads-outside marker lists nothing it can trust is never kept: its digest is one no
// kept verdict can have (it differs on every call, though nothing changed), and the answer names it. A marker that lists
// what the tests read is kept: a change to a file it names changes the package's digest, a change elsewhere does not.
func TestAPackageWithTheReadsOutsideMarkerIsKeptOnlyByWhatItLists(t *testing.T) {
t.Parallel()
m, tree := digestFixture(t)
plain := digests(t, m, "unit")
if len(plain.ReadsOutside) != 0 {
t.Fatalf("a package carries the marker in the fixture: %v", plain.ReadsOutside)
}
edit(t, tree, "c/"+ReadsOutsideMarker, "the tests read ../docs\n")
first, second := digests(t, m, "unit"), digests(t, m, "unit")
c := "fixture/digest/c"
if len(first.Packages[c]) != 64 || first.Packages[c] == second.Packages[c] || first.Packages[c] == plain.Packages[c] {
t.Errorf("c carries the marker and its digest is stable: %s, %s, before %s", first.Packages[c], second.Packages[c], plain.Packages[c])
c, a := "fixture/digest/c", "fixture/digest/a"
if err := os.MkdirAll(filepath.Join(tree, "docs"), 0o755); err != nil {
t.Fatal(err)
}
if strings.Join(first.ReadsOutside, ",") != c {
t.Errorf("the answer does not name the package that reads outside: %v", first.ReadsOutside)
edit(t, tree, "docs/one.md", "one\n")
edit(t, tree, "other.md", "other\n")
for name, marker := range map[string]string{
"an empty marker": "# nothing listed\n\n",
"a sentence": "the tests read ../docs\n",
"a path climbing out": "../docs\n",
"a path matching nothing": "docs/none.md\n",
"an absolute path": "/etc/passwd\n",
"a path that is not clean": "docs/./one.md\n",
"a good line and a bad line": "docs\nnope/*.md\n",
} {
edit(t, tree, "c/"+ReadsOutsideMarker, marker)
first, second := digests(t, m, "unit"), digests(t, m, "unit")
if len(first.Packages[c]) != 64 || first.Packages[c] == second.Packages[c] || first.Packages[c] == plain.Packages[c] {
t.Errorf("%s: c's digest is stable: %s, %s, before %s", name, first.Packages[c], second.Packages[c], plain.Packages[c])
}
if strings.Join(first.ReadsOutside, ",") != c {
t.Errorf("%s: the answer does not name the package that reads outside: %v", name, first.ReadsOutside)
}
if first.Packages[a] != plain.Packages[a] || first.Packages[a] != second.Packages[a] {
t.Errorf("%s: a has no marker and its digest moved: %s, %s, before %s", name, first.Packages[a], second.Packages[a], plain.Packages[a])
}
}
a := "fixture/digest/a"
if first.Packages[a] != plain.Packages[a] || first.Packages[a] != second.Packages[a] {
t.Errorf("a has no marker and its digest moved: %s, %s, before %s", first.Packages[a], second.Packages[a], plain.Packages[a])
// A list: a glob, a directory read whole, and a path whose absence is read.
edit(t, tree, "c/"+ReadsOutsideMarker, "# what c's tests read\ndocs/*.md\ndocs\n?gone\n")
before := digests(t, m, "unit")
if len(before.ReadsOutside) != 0 || len(before.Packages[c]) != 64 || before.Packages[c] == plain.Packages[c] {
t.Fatalf("a listed marker is not kept: %v, %s (no marker %s)", before.ReadsOutside, before.Packages[c], plain.Packages[c])
}
if again := digests(t, m, "unit"); again.Packages[c] != before.Packages[c] {
t.Errorf("nothing changed and c's digest moved")
}
for _, step := range []struct {
file, text string
moves bool
}{
{"other.md", "changed\n", false}, // outside the list
{"d/d.go", "package d\n\n// D.\n", false}, // a package c's tests do not reach
{"docs/one.md", "changed\n", true}, // a listed file
{"docs/two.md", "new\n", true}, // a new file the glob matches
{"gone", "here\n", true}, // a path whose absence was read
{"c/note.txt", "changed\n", true}, // the package's own file that is not Go
{"c/x_integration_test.go", "//go:build integration\n\npackage c\n", true}, // a Go file its build tags leave out (go list's IgnoredGoFiles)
} {
edit(t, tree, step.file, step.text)
after := digests(t, m, "unit")
if moved := after.Packages[c] != before.Packages[c]; moved != step.moves {
t.Errorf("a change to %s: c's digest moved %v, want %v", step.file, moved, step.moves)
}
if after.Packages[a] != plain.Packages[a] {
t.Errorf("a change to %s moved a's digest", step.file)
}
before = after
}
// Issue 890/AC3: a package whose directory carries the needs marker is named with its needs at the integration level,
@@ -42,11 +42,13 @@ type FetchResult struct {
// head onto it, the paths the merge changes, and the tree of each module directory (a directory holding a
// module.json) in the merge. A head that does not merge cleanly is an answer: Merge is empty, Conflicts names the
// files, and the workspace is left at the trunk commit.
Base string `json:"base,omitempty"`
Merge string `json:"merge,omitempty"`
Conflicts []string `json:"conflicts,omitempty"`
Changed []string `json:"changed,omitempty"`
Trees map[string]buildstep.ModuleTree `json:"trees,omitempty"`
Base string `json:"base,omitempty"`
Merge string `json:"merge,omitempty"`
Conflicts []string `json:"conflicts,omitempty"`
Changed []string `json:"changed,omitempty"`
// Programs are the changed files that are compiled programs (issue 1577); a merge answers it, a pointer to an empty list when there are none, absent for a fetch that made no merge.
Programs *[]buildstep.Program `json:"programs,omitempty"`
Trees map[string]buildstep.ModuleTree `json:"trees,omitempty"`
// Beside are the sibling repositories placed as plain directories under the workspace's beside directory, which
// build_go_test hands to the tests as MESH_CHECK_BESIDE (ADR 0484 clause 4).
Beside []Sibling `json:"beside,omitempty"`
@@ -132,6 +132,9 @@ func (m *Module) lint(ctx context.Context, run forgekey.Runner, tree, d string,
// lintAll lints each directory, up to m.parallel() at once like the level's tests; the failures join in directory order
// and the error is the first directory's, so the answer does not depend on which ended first (issue 1930).
func (m *Module) lintAll(ctx context.Context, run forgekey.Runner, tree string, dirs []string, env func(d string) []string) ([]Failure, error) {
// The ask's phases say it (issue 1890): lint of the directories, then their tests.
buildstep.Phase(ctx, fmt.Sprintf("unit: lint of %d directories", len(dirs)))
defer buildstep.Phase(ctx, fmt.Sprintf("unit: tests of %d directories", len(dirs)))
type answer struct {
failed []Failure
err error
@@ -41,6 +41,9 @@ func (m *Module) merge(ctx context.Context, res *FetchResult, tree, remote, base
}
res.Base = mr.Base
res.Conflicts, res.Merge, res.Changed = mr.Conflicts, mr.Merge, mr.Changed
if mr.Merge != "" {
res.Programs = &mr.Programs
}
if res.Merge == "" {
return nil
}
@@ -77,7 +77,7 @@ func TestFetchWithABaseMergesAndAnswersThePreview(t *testing.T) {
t.Fatal(err)
}
if res.Base != baseSHA || res.Merge != mergeSHA || res.Commit != commitSHA || len(res.Conflicts) != 0 ||
strings.Join(res.Changed, ",") != "a.go,modules/m/main.go" {
strings.Join(res.Changed, ",") != "a.go,modules/m/main.go" || res.Programs == nil || len(*res.Programs) != 0 { // 1577: a merge says it found no program
t.Errorf("result = %s", st.Result)
}
mt, ok := res.Trees["modules/m"]
@@ -1 +1,7 @@
These tests read other catalogue modules' files, outside this module (issue 1935): never kept.
# What these tests read outside their package, as paths from the repository's root (a glob, or a directory read whole;
# ? before a path whose absence is read). mesh-build-go hashes them into the package's digest, so its verdict is kept
# until one of them changes (issue 1936). An empty list, or a line matching nothing, keeps nothing.
# the manifests through mesh-sdk's validator (the predefined keys), gitea's and mesh-build-node's.
predefined.json
modules/gitea
modules/mesh-build-node
+3 -1
View File
@@ -1,3 +1,5 @@
{
"capacity": "shanks=3,ace=2,g14=0,novox=0"
"capacity": "shanks=3,ace=2,g14=0,novox=0",
"cores-per-step": 6,
"build-hours": ""
}
+1 -1
View File
@@ -3,7 +3,7 @@ module meshbuildgo
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026191742-47a66e3c0dc8
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026233910-a2759ec92ec9
github.com/distribution/distribution/v3 v3.1.2
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026191742-47a66e3c0dc8 h1:6OaUWIjcpXOtnu70zeqH0VEaF4oGykrxGpFkaFOzCmY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026191742-47a66e3c0dc8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026233910-a2759ec92ec9 h1:MS7i4NVNQHMAMqRRFMdzbto31BHZY+BuEZY1ql3p4Rs=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026233910-a2759ec92ec9/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
+13 -1
View File
@@ -29,6 +29,18 @@
"description": "How many asks of the Go build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.",
"pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*",
"default": "shanks=3,ace=2,g14=0,novox=0"
},
"build-hours": {
"type": "string",
"description": "When each node may take this module's build asks, as node=HH:MM-HH:MM pairs in the node's local time, separated by commas, such as g14=18:00-06:00 (an end before its start crosses midnight). Outside its window a node takes no new step and finishes the ones it runs; a node not named, or an empty value, builds at any hour. The operator, 2026-10-26: g14 builds only outside office hours.",
"pattern": "|[a-z0-9_-]+=([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9](,[a-z0-9_-]+=([01][0-9]|2[0-3]):[0-5][0-9]-([01][0-9]|2[0-3]):[0-5][0-9])*",
"default": ""
},
"cores-per-step": {
"type": "number",
"description": "How many processors one Go step of this module needs (a full check of a large repository takes what it is given: three at about 4 cores each ran past the 30-minute step bound on 2026-10-26). The capacity above is an upper bound: on each node the module runs at most floor(the cores of its build slice's CPU quota / this) steps at once, the quota read live from its cgroup (issue 1806), so a lowered build CPU share never starves its steps. Above 0.",
"minimum": 0.1,
"default": 6
}
},
"tools": [
@@ -107,7 +119,7 @@
"path": "${dir:state}/settings.json",
"mode": "0600",
"owner": "mesh-build-go",
"takes-config": ["capacity"],
"takes-config": ["capacity", "cores-per-step", "build-hours"],
"merge": "json",
"template": "files/settings.json"
},