diff --git a/internal/selfcompose/selfcompose.go b/internal/selfcompose/selfcompose.go index a8f81a0..45a07f6 100644 --- a/internal/selfcompose/selfcompose.go +++ b/internal/selfcompose/selfcompose.go @@ -915,6 +915,13 @@ func (c *Composer) compose(ctx context.Context) []agent.Update { } c.paid() pass.Held = held + // The provisioner client's account the node runs with, dropped by this pass, is said: every filled write is refused + // under a declaration that names none (filling.JudgeAccount), and no module is handed to it (issue 774, ace, + // 2026-11-04 01:29). A placed client keeps its account while it is held (compose.Node.RunningProvisioner). + if was, now := ownProvisioner(running), ownProvisioner(mapValues(compositions)); was != "" && now == "" && nodeWanted { + c.sayOnceEach(fmt.Sprintf("the node's own composition names no provisioner client's account, where what runs names %s: "+ + "once applied, every filled write is refused and no module is handed to the provisioner", was)) + } // A module refused alone is held: it keeps what it runs, its have says why, and every other module is applied (issue // 1126: one module's refusal froze the whole node). done := c.waiting("the agent marking each held module's have") @@ -1699,3 +1706,21 @@ func (c *Composer) ComposedToday(ctx context.Context, module, fp string) (string } return "", false } + +// ownProvisioner is the provisioner client's account the node's own composition among cs names, or "". +func ownProvisioner(cs []delivery.Composed) string { + for _, c := range cs { + if c.Module == "" { + return c.Own.Provisioner + } + } + return "" +} + +func mapValues(m map[string]delivery.Composed) []delivery.Composed { + out := make([]delivery.Composed, 0, len(m)) + for _, k := range slices.Sorted(maps.Keys(m)) { + out = append(out, m[k]) + } + return out +}