Judge the grants step on the declaration it sends, so nothing composed between the check and the send slips through (review of #230)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheGrantsStepSendsTheUserListAndNothingElse (2.55s)

The guard composed its own declaration ahead of the send; now sendToEach
judges the one it composed and stamped, and refuses it unsent.
This commit is contained in:
2026-10-11 19:12:33 +02:00
parent 538c4d5115
commit 78205d7405
4 changed files with 96 additions and 80 deletions
+28 -33
View File
@@ -968,19 +968,6 @@ func grantsStep(ctx context.Context, open *stores, node string) *inventory.Grant
// list is the one already sent.
return nil
}
// **The user list and nothing else**: checked before it is sent, against what the machine was last sent.
// Kept builds are not the whole of a declaration — a new assignment, a settings change, an assignment
// taken away, a provision's logins are composed as the mesh holds them now — and a step that carried any
// of it would be the unjudged change this step exists to avoid.
if other, err := grantsOnlyOf(ctx, open, holder); err != nil || other != "" {
why := other
if err != nil {
why = err.Error()
}
fmt.Printf("grants step: %s is not sent its user list ahead of %s, which is sent without it: %s\n",
holder, node, why)
return &inventory.GrantsStep{Node: holder, Failed: why}
}
if _, err := sendRollout(keepingEveryBuild(withScope(ctx, sendScope{})), open, []string{holder}); err != nil {
fmt.Printf("grants step: the bus's user list could not be sent to %s ahead of %s, which is sent without "+
"it — the bus may refuse what the send newly grants: %v\n", holder, node, err)
@@ -992,21 +979,19 @@ func grantsStep(ctx context.Context, open *stores, node string) *inventory.Grant
return &inventory.GrantsStep{Node: holder, At: &now}
}
// grantsOnlyOf is grantsOnly: a variable so a test of the walk needs no store.
var grantsOnlyOf = grantsOnly
// errNotGrantsOnly is a grants step refused unsent: its declaration would change more than the user list.
var errNotGrantsOnly = errors.New("the grants step would change more than the bus's user list, and is not sent")
// grantsOnly is what the grants step's send to the machine holding the bus would change there besides its
// user list, against what it was last sent (novox/hq issue 490); empty when nothing else. Composed as the
// step's send composes it, without allocating anything (Foreseeing), and compared resource by resource
// with the summary the last send kept (ADR 0217): exact, rather than a list of the cases that can differ.
// A module there that was never sent is a new assignment, and refused too, whatever its resources.
func grantsOnly(ctx context.Context, open *stores, holder string) (string, error) {
// grantsOnlyIn is what a grants step's declaration, composed for sending, would change on the machine holding
// the bus besides its user list, against what it was last sent (novox/hq issue 490); empty when nothing else.
// Judged by sendToEach on the very declaration it then sends — one composition, judged, then sent or refused
// unsent. Kept builds are not the whole of a declaration: a new assignment, a settings change, an assignment
// taken away, a provision's logins are composed as the mesh holds them now, and a step that carried any of
// it would be the unjudged change this step exists to avoid. Compared resource by resource with the summary
// the last send kept (ADR 0217): exact, rather than a list of the cases that can differ. A module there that
// was never sent is a new assignment, refused whatever its resources.
func grantsOnlyIn(ctx context.Context, open *stores, holder string, plan catalogue.Resolution, declared sendable) (string, error) {
inv := open.inventory
kept := keepingEveryBuild(keepingRecorded(ctx))
plan, settings, err := planFor(kept, open, holder)
if err != nil {
return "", err
}
sent, known, err := inv.SentBuilds(ctx, holder)
if err != nil {
return "", err
@@ -1024,13 +1009,22 @@ func grantsOnly(ctx context.Context, open *stores, holder string) (string, error
listID = m.Module + "." + catalogue.BusUsersID()
}
}
gens, err := generators(kept, open)
if err != nil {
return "", err
}
declared, err := declarationWith(kept, open, holder, plan, settings, gens, Foreseeing)
if err != nil {
return "", err
var facts *moveFacts
for m, was := range sent {
now, carried := declared.Builds[m]
if !carried || sameCommit(now, was) {
continue
}
if facts == nil {
f, err := readMoveFacts(ctx, inv)
if err != nil {
return "", err
}
facts = &f
}
if !facts.identical(m, was, now) {
other = append(other, fmt.Sprintf("%s %s → %s", m, short(was), short(now)))
}
}
for _, f := range declared.foreseen {
other = append(other, f+" would be minted")
@@ -1067,6 +1061,7 @@ func grantsOnly(ctx context.Context, open *stores, holder string) (string, error
}
other = append(other, fmt.Sprintf("~%s (%s)", c.ID, strings.Join(c.Fields, ", ")))
}
sort.Strings(other)
if len(other) == 0 {
return "", nil
}