Keep the builds of the grants step's machine alone, not of every machine its composition resolves (repo-check of #230)
mesh/merge-gate pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without …
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheGrantsStepSendsTheUserListAndNothingElse (2.14s)
mesh/delivery superseded: a newer head of the same pull request

Composing the bus's machine resolves the others on the same context, and
a machine never recorded as sent refused the whole composition.
This commit is contained in:
2026-10-11 19:30:10 +02:00
parent 0f853e93fa
commit 325575b292
5 changed files with 35 additions and 18 deletions
@@ -105,7 +105,10 @@ func TestTheGrantsStepSendsTheUserListAndNothingElse(t *testing.T) {
t.Fatal(err)
}
}
kept := keepingEveryBuild(keepingRecorded(ctx))
// Kept for anchor alone. laptop is never recorded as sent, on purpose: composing anchor resolves laptop
// too (who is on the private network), and a step that kept every machine's builds refused anchor's
// composition for want of laptop's last send (repo-check of #230 at 0f853e93).
kept := keepingEveryBuild(keepingRecorded(ctx), "anchor")
// What anchor was last sent: everything at the builds of before the merge, as an ordinary send sends it.
_, before := compose(keepingRecorded(ctx))
+10 -1
View File
@@ -43,7 +43,7 @@ func sendsRecorded(t *testing.T, holder string, behind bool, refuse error) *[]aS
return holder, behind, nil
}
sendRollout = func(ctx context.Context, _ *stores, names []string) ([]string, error) {
s := aSend{names: append([]string(nil), names...), keepsAll: everyBuildKept(ctx)}
s := aSend{names: append([]string(nil), names...), keepsAll: len(names) == 1 && everyBuildKept(ctx, names[0])}
for n := range scopeOf(ctx).judged {
s.judged = append(s.judged, n)
}
@@ -168,6 +168,15 @@ func TestOnlyTheUserListsContentIsExempt(t *testing.T) {
}
}
// The step keeps the builds of the machine it sends alone: the others its composition resolves are composed
// as any send composes them.
func TestTheGrantsStepKeepsOneMachinesBuilds(t *testing.T) {
ctx := keepingEveryBuild(t.Context(), "novox")
if !everyBuildKept(ctx, "novox") || everyBuildKept(ctx, "ace") || everyBuildKept(t.Context(), "novox") {
t.Error("the grants step keeps the builds of a machine it does not send")
}
}
// The delivery plan says the step before the merge.
func TestAChangePlanSaysTheGrantsStep(t *testing.T) {
const catalogue_ = "http://forge.internal:20000/novox/mesh-catalog.git"
+1 -1
View File
@@ -1238,7 +1238,7 @@ func sendToEach(ctx context.Context, open *stores, names []string) ([]string, er
numbered.stamp(&declared)
// **The grants step sends the user list and nothing else** (novox/hq issue 490): judged on this very
// declaration, the one sent, so nothing composed between a check and the send can slip through.
if everyBuildKept(ctx) {
if everyBuildKept(ctx, name) {
other, err := grantsOnlyIn(ctx, open, name, plan, declared)
if err != nil {
return nil, err
+18 -13
View File
@@ -63,19 +63,24 @@ func keptExcept(ctx context.Context) (map[string]bool, bool) {
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)
// keepingEveryBuild is a context whose sends compose every module of one machine — recorded or rolling out —
// at the build that 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.
//
// **That machine alone.** Composing one machine resolves the others too — who is on the private network,
// who answers a requirement — on the same context, and those are no part of the step's send: they are
// composed as any send composes them. Kept for every machine, a machine whose last send is not known
// refused the bus's machine's composition.
func keepingEveryBuild(ctx context.Context, node string) context.Context {
return context.WithValue(ctx, keepEveryBuildKey{}, node)
}
// 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
// everyBuildKept is whether this context keeps every module of this machine at the build it runs.
func everyBuildKept(ctx context.Context, node string) bool {
on, _ := ctx.Value(keepEveryBuildKey{}).(string)
return on != "" && on == node
}
// recordedKept is, for a send under keepingRecorded, every recorded module the machine was last sent a
@@ -87,7 +92,7 @@ func everyBuildKept(ctx context.Context) bool {
// 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)
every := everyBuildKept(ctx, node)
skip, on := keptExcept(ctx)
if !on && !every {
return nil, nil
@@ -150,7 +155,7 @@ func keepRecorded(ctx context.Context, open *stores, node string, shelf map[stri
if err != nil {
return nil, err
}
if !found && everyBuildKept(ctx) {
if !found && everyBuildKept(ctx, node) {
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]))
}
+2 -2
View File
@@ -257,7 +257,7 @@ func ungatedIn(ctx context.Context, open *stores, names []string, addedHolder st
// The grants step composes every module at the build its machine was last sent (novox/hq issue 490):
// a move it keeps is not made. Read, not assumed, so a move it could not keep is still refused here.
var keeps map[string]string
if everyBuildKept(ctx) {
if everyBuildKept(ctx, n) {
if keeps, err = recordedKept(ctx, open, n); err != nil {
return nil, err
}
@@ -968,7 +968,7 @@ func grantsStep(ctx context.Context, open *stores, node string) *inventory.Grant
// list is the one already sent.
return nil
}
if _, err := sendRollout(keepingEveryBuild(withScope(ctx, sendScope{})), open, []string{holder}); err != nil {
if _, err := sendRollout(keepingEveryBuild(withScope(ctx, sendScope{}), holder), 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)
return &inventory.GrantsStep{Node: holder, Failed: err.Error()}