From f47b6e1c31bd0da7875a987c66773aa6ee75bacd Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 18 Sep 2026 02:05:55 +0200 Subject: [PATCH] One push leaves the mesh consistent (issue 057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A provision is minted while composing the consumer's node, and the provider's grant list is a pure read of secrets already issued — so pushing the consumer left the provider blind until somebody pushed it again, with no signal to. A named push now captures what every machine should be before composing, recomputes after, and sends the machines whose declaration changed because of this push — by name, never silently, converging over bounded rounds. --- cmd/mesh-controller/push.go | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cmd/mesh-controller/push.go b/cmd/mesh-controller/push.go index 656d683..ba1fd80 100644 --- a/cmd/mesh-controller/push.go +++ b/cmd/mesh-controller/push.go @@ -9,6 +9,7 @@ import ( "flag" "fmt" "os" + "sort" "strings" "time" @@ -249,6 +250,18 @@ func pushCommand(ctx context.Context, args []string) error { return err } + // What every machine should be BEFORE this push composes anything. Composing a named node + // mints the provisions its consumers need, and a minted provision changes what the PROVIDER + // machine should be — its grant list is a pure read of secrets already issued (novox/hq + // issue 057). Captured now so that, after the send, "what changed because of this push" is + // a comparison rather than a guess. + var before map[string]string + if len(args) == 1 { + if before, err = wouldSend(ctx, open, nodes); err != nil { + return err + } + } + server, err := link.Connect(nil, nil) if err != nil { return err @@ -321,6 +334,41 @@ func pushCommand(ctx context.Context, args []string) error { } fmt.Printf("\n%d node(s) told\n", len(sending)) + // **A push leaves the mesh consistent, not just the machine it named** (novox/hq issue 057). + // The machines whose declaration changed because of THIS push — providers a provision was + // just minted from — are sent theirs too, by name, never silently: the alternative was a + // consumer that retries forever while nothing says the provider was left blind, and a rule + // ("push the provider node too") enforced by nothing. Bounded: a cascade send may itself + // mint, so this converges over a few rounds, each naming what changed and why. + if len(args) == 1 && before != nil { + delivered := map[string]bool{args[0]: true} + for round := 0; round < 3; round++ { + after, err := wouldSend(ctx, open, nodes) + if err != nil { + return err + } + var also []string + for name, digest := range after { + if !delivered[name] && before[name] != "" && before[name] != digest { + also = append(also, name) + } + } + if len(also) == 0 { + break + } + sort.Strings(also) + fmt.Printf("\nthis push changed what %s should be — a provision granted from "+ + "there; sending it too\n", strings.Join(also, ", ")) + if err := sendTo(ctx, open, also); err != nil { + return err + } + for _, name := range also { + delivered[name] = true + } + before = after + } + } + // A named node is a request to make THAT node current now, so it waits for the node to say it // applied exactly this. A whole-mesh or --behind push does not wait: it is a sweep, and blocking // on the slowest machine would hold back the report on all the others.