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 ` — 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 0245)", m, node, short(kept[m]), node) } shelf[m] = ran } return kept, nil }