Files
mesh-controller/cmd/mesh-controller/recorded_kept.go
T
jochen 5efe999733
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
Name the hq issue by its number: 294 was taken on an open branch, this is 295
2026-10-07 19:28:54 +02:00

130 lines
4.9 KiB
Go

package main
import (
"context"
"fmt"
"sort"
"github.com/novox/mesh-controller/internal/catalogue"
"github.com/novox/mesh-controller/internal/inventory"
)
// A recorded build reaches a machine only by a person's push (novox/hq issue 295, ADR 0242).
//
// **A send carries the machine's whole declaration** (ADR 0221), composed from the build the mesh holds
// of every module on it. A module whose upgrade policy records — postgres, mongodb, keycloak, the
// network path — has its new build registered at its merge and sent nowhere, "until a person pushes".
// But every other send to its machine composed it too: on 2026-10-07 a catalogue merge adopting the
// images' health checks rebuilt postgres and mongodb with the rest, and the plan's gated send to the
// control node for mail — and to the anchor for the spreadsheet app — carried both, recreating the
// providers every consumer on those machines drops with. No gate judged them (the gate judges only what
// rolls out), and nobody had pushed.
//
// So **every send but a person's push composes a recorded module at the build its machine was last
// sent**: the manifest of that build, from the build records, in place of the one the mesh holds. The
// machine runs what it ran; the send records that it still carries that build; `status` keeps saying
// the machine is behind, and `push <node>` — a person's word — sends the new one. The bus step is a
// person's too, and carries the bus; any other recorded module waiting on the bus's machine stays.
//
// A recorded module the machine was never sent (a new assignment) is composed as the mesh holds it —
// there is nothing running to keep. One whose kept build is no longer in the records refuses the send,
// said: composing the new build would be the very move this exists to stop.
type keepRecordedKey struct{}
// sendKeeps is the context a send that is not a person's push composes under: every recorded module
// kept at the build its machine runs — except, on the bus step, the bus.
func sendKeeps(ctx context.Context, inv *inventory.Inventory) (context.Context, error) {
if !busStepSending(ctx) {
return keepingRecorded(ctx), nil
}
bus, err := pendingBus(ctx, inv)
if err != nil {
return nil, err
}
return keepingRecorded(ctx, bus.module), nil
}
// keepingRecorded is a context whose sends compose every recorded module at the build its machine runs,
// except the modules named (the bus, on the bus step).
func keepingRecorded(ctx context.Context, except ...string) context.Context {
skip := map[string]bool{}
for _, m := range except {
skip[m] = true
}
return context.WithValue(ctx, keepRecordedKey{}, skip)
}
// keptExcept is whether this context keeps recorded modules, and the modules it lets move.
func keptExcept(ctx context.Context) (map[string]bool, bool) {
skip, on := ctx.Value(keepRecordedKey{}).(map[string]bool)
return skip, on
}
// recordedKept is, for a send under keepingRecorded, every recorded module the machine was last sent a
// build of that the mesh's build is not identical to: module → the commit it keeps. Nil when the context
// keeps nothing, or when what the machine was last sent is not known (it is then held whole elsewhere —
// ADR 0221).
func recordedKept(ctx context.Context, open *stores, node string) (map[string]string, error) {
skip, on := keptExcept(ctx)
if !on {
return nil, nil
}
inv := open.inventory
sent, known, err := inv.SentBuilds(ctx, node)
if err != nil || !known {
return nil, err
}
current, err := inv.CurrentBuilds(ctx)
if err != nil {
return nil, err
}
var f *moveFacts
out := map[string]string{}
for m, was := range sent {
now, held := current[m]
if !held || now.RollOut || skip[m] || was == "" || sameCommit(was, now.Commit) {
continue
}
if f == nil {
read, err := readMoveFacts(ctx, inv)
if err != nil {
return nil, err
}
f = &read
}
if f.identical(m, was, now.Commit) {
continue
}
out[m] = was
}
return out, nil
}
// keepRecorded puts, in a shelf about to be resolved for a machine, the build each recorded module there
// runs in place of the one the mesh holds; it answers what it kept, module → commit.
func keepRecorded(ctx context.Context, open *stores, node string, shelf map[string]catalogue.Manifest) (map[string]string, error) {
kept, err := recordedKept(ctx, open, node)
if err != nil || len(kept) == 0 {
return nil, err
}
names := make([]string, 0, len(kept))
for m := range kept {
names = append(names, m)
}
sort.Strings(names)
for _, m := range names {
ran, found, err := open.inventory.ManifestAt(ctx, m, kept[m])
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("%s records rather than rolls out, and %s runs its build %s, which the build "+
"records no longer hold: this send cannot keep it and does not move it — `push %s` sends the new "+
"one on a person's word (novox/hq ADR 0242)", m, node, short(kept[m]), node)
}
shelf[m] = ran
}
return kept, nil
}