Keep a recorded module at the build its machine runs on every send but a person's push, and say what a send recreates (hq issue 294, ADR 0242)
A plan's gated send for mail carried postgres's and mongodb's new builds (policy record) to the control node and the anchor on 2026-10-07: a send composes the machine's whole declaration from the builds the mesh holds, and the gate only ever looked at modules that roll out. Every send but a person's push now composes a recorded module from the manifest of the build the machine was last sent and records that it still carries it; the bus step moves the bus alone. And each move a gated send carries says how many of the module's containers it recreates, and whether with a new image or only their declaration.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
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 294, 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
|
||||
}
|
||||
Reference in New Issue
Block a user