The one-push cascade compares against what was last sent (issue 057)

The first cut compared a before/after snapshot of the named push — but
the provision is minted at assign or module-issue, before push runs, so
by push time the provider is already behind with no delta to detect.
Fixed to flush machines whose declaration differs from what they were
last SENT (the same Waiting path --behind uses), which is the honest
meaning of 'one push leaves the mesh consistent' (ADR 0083). Verified
live on a kept two-node mesh: pushing the consumer populates the
provider's grant and the vhost is minted.
This commit is contained in:
2026-09-18 02:37:26 +02:00
parent 25e42b3ad6
commit 97c076ea48
+26 -29
View File
@@ -250,18 +250,6 @@ 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
@@ -334,38 +322,47 @@ 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)
// **A named push leaves the mesh consistent, not just the machine it named** (novox/hq
// issue 057, ADR 0083). Assigning a cross-node consumer mints a provision, and the PROVIDER's
// grant list is a pure read of secrets already issued — so after the named node is current,
// other machines can be behind *as a consequence*: their declaration now differs from what
// they were last sent. Those are flushed too, by name, in the push's own output.
//
// Compared against what each machine was last SENT, not against a before/after of this push:
// the mint usually happened at `assign` or `module issue`, before this command ran, so the
// only durable signal is "what it should be" versus "what it last received". A machine behind
// for an unrelated reason is caught here too, which is not a cost — a named push that knew a
// machine was behind and left it so would be the very silence this removes. Bounded: a
// flushed send may itself mint, so this converges over a few rounds.
if len(args) == 1 {
flushed := map[string]bool{args[0]: true}
for round := 0; round < 4; round++ {
would, err := wouldSend(ctx, open, nodes)
if err != nil {
return err
}
behind, err := inv.Waiting(ctx, would)
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)
for _, m := range behind {
if !flushed[m.Node] {
also = append(also, m.Node)
}
}
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, ", "))
fmt.Printf("\nthis push left %s behind — a provision granted from there, or a "+
"declaration since changed; sending it too\n", strings.Join(also, ", "))
if err := sendTo(ctx, open, also); err != nil {
return err
}
for _, name := range also {
delivered[name] = true
flushed[name] = true
}
before = after
}
}