A walk left the machine holding the bus out of a gated send whenever a build waited there for that same gate, so a module's new health tool was refused by the bus on its first machine and the gate could not pass. The grants step sends that machine its declaration with every build kept, before the first machine's send; plans and the change plan say it.
166 lines
6.5 KiB
Go
166 lines
6.5 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 0245).
|
|
//
|
|
// **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
|
|
}
|
|
|
|
type keepEveryBuildKey struct{}
|
|
|
|
// keepingEveryBuild is a context whose sends compose every module — recorded or rolling out — at the build
|
|
// its machine was last sent (novox/hq issue 490): the grants step, which sends the machine holding the bus
|
|
// its new user list and nothing of any module's new code. That code waits there for a gate like any other
|
|
// move; the list must not, or the first machine's gate is judged against a bus that refuses what the change
|
|
// newly grants.
|
|
func keepingEveryBuild(ctx context.Context) context.Context {
|
|
return context.WithValue(ctx, keepEveryBuildKey{}, true)
|
|
}
|
|
|
|
// everyBuildKept is whether this context keeps every module at the build its machine runs.
|
|
func everyBuildKept(ctx context.Context) bool {
|
|
on, _ := ctx.Value(keepEveryBuildKey{}).(bool)
|
|
return 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).
|
|
//
|
|
// Under keepingEveryBuild, every module the machine was sent is kept, whatever its policy (novox/hq issue
|
|
// 490), and a machine whose last send is not known refuses the send: there is nothing to keep it at, and
|
|
// composing the mesh's builds would be the very move the grants step must not make.
|
|
func recordedKept(ctx context.Context, open *stores, node string) (map[string]string, error) {
|
|
every := everyBuildKept(ctx)
|
|
skip, on := keptExcept(ctx)
|
|
if !on && !every {
|
|
return nil, nil
|
|
}
|
|
if every {
|
|
skip = nil
|
|
}
|
|
inv := open.inventory
|
|
sent, known, err := inv.SentBuilds(ctx, node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !known {
|
|
if every {
|
|
return nil, fmt.Errorf("what %s was last sent is not known, so the bus's user list cannot be sent "+
|
|
"there alone with every build kept (novox/hq issue 490)", node)
|
|
}
|
|
return nil, nil
|
|
}
|
|
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 && !every) || 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 && everyBuildKept(ctx) {
|
|
return nil, fmt.Errorf("%s runs %s's build %s, which the build records no longer hold: the bus's user "+
|
|
"list cannot be sent there alone with it kept (novox/hq issue 490)", node, m, short(kept[m]))
|
|
}
|
|
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 0245)", m, node, short(kept[m]), node)
|
|
}
|
|
shelf[m] = ran
|
|
}
|
|
return kept, nil
|
|
}
|