Assert the bus's objects on every send, not only at start (hq issue 208)
A module or seat holder assigned after the controller started was sent
its declaration and found nothing to bind: messenger on novox
("consumer novox_messenger not found", 2026-10-06) and every first
build-agent holder (2026-10-03). assertBusObjects ran only in the start
raise; a push ensured a module's consumer only when a bus credential was
minted, which a module carried by the runtime never is.
The send's grant now runs the same derivation (assertOnSend) before the
memberships, on every push, cascade, plan send and rotation. A failure
is said in the send's output and raised as bus.objects.unasserted, which
the next send that asserts everything clears; the send itself goes on,
because the objects are the mesh's and holding every machine back for
one would turn one fault into all. Module consumers are each tried and
every failure named. The start raise stays as it was.
This commit is contained in:
@@ -2,9 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/broker"
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
@@ -51,14 +53,70 @@ func assertBusObjects(ctx context.Context, inv *inventory.Inventory, r broker.Ra
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Every one tried, and every failure named: one module's consumer the bus refuses is no reason
|
||||
// the modules after it in the list hear nothing (novox/hq issue 208, where this runs on each send).
|
||||
var failed []error
|
||||
for _, c := range consumers {
|
||||
if err := r.EnsureConsumer(c.Consumer); err != nil {
|
||||
return nil, fmt.Errorf("how %s on %s hears what it consumes: %w", c.Module, c.Node, err)
|
||||
failed = append(failed, fmt.Errorf("how %s on %s hears what it consumes: %w", c.Module, c.Node, err))
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
return nil, errors.Join(failed...)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// What raises the condition a send says when the objects it implies could not be asserted, and its kind.
|
||||
const (
|
||||
sourceBusObjects = "bus-objects"
|
||||
kindBusObjectsUnasserted = "bus-objects-unasserted"
|
||||
)
|
||||
|
||||
// assertOnSend asserts, on the bus a send is about to use, every object assertBusObjects derives —
|
||||
// **whenever a declaration is sent, not only when the controller starts** (novox/hq issue 208).
|
||||
//
|
||||
// A module assigned after the controller started was sent its declaration and found no consumer to
|
||||
// bind (`consumer not found`, messenger on 2026-10-06), and a seat holder assigned after it found no
|
||||
// worker: the objects a declaration implies were asserted at start and nowhere else, so they existed
|
||||
// only for what was assigned before the last restart. The same derivation, not a second list of what
|
||||
// a send needs: what start asserts, the self-check expects and a send asserts are one answer. Every
|
||||
// part is idempotent, so asserting the whole of it again is the no-op a restart already relies on.
|
||||
//
|
||||
// **A failure is said and raised, and the send goes on.** The objects are the mesh's, not the
|
||||
// machines' being sent: holding every machine back for one consumer that none of them may use would
|
||||
// turn one fault into all of them, and the declarations are not what is wrong. It is never silent —
|
||||
// said in the send's own output and raised as a condition, which the next send that asserts them
|
||||
// clears — and the start-time raise still refuses to serve without them.
|
||||
func assertOnSend(ctx context.Context, inv *inventory.Inventory, r broker.Raiser, indent string) error {
|
||||
_, err := assertBusObjects(ctx, inv, r)
|
||||
var observed []conditions.Observation
|
||||
if err != nil {
|
||||
fmt.Printf("%sTHE BUS DOES NOT HOLD WHAT THIS SEND IMPLIES: %v\n", indent, err)
|
||||
fmt.Printf("%s a module may find no consumer to bind, or a holder no worker; sent anyway, raised as "+
|
||||
"condition %s, and asserted again by the next send\n", indent, unassertedObservation(err).Key())
|
||||
observed = append(observed, unassertedObservation(err))
|
||||
}
|
||||
// Observed when it failed, cleared when it did not: a send that asserted everything is the
|
||||
// observation that the bus holds what it should.
|
||||
if kerr := withKeeper(ctx, func(k *conditions.Keeper) error {
|
||||
return k.Reconcile(ctx, sourceBusObjects, observed)
|
||||
}); kerr != nil {
|
||||
fmt.Printf("%sand whether the bus holds what this send implies could not be kept as a condition: %v\n",
|
||||
indent, kerr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// unassertedObservation is a send's failure to assert the bus's objects, as a condition.
|
||||
func unassertedObservation(err error) conditions.Observation {
|
||||
return conditions.Observation{Scope: conditions.ScopeBus, ID: "objects", Token: "unasserted",
|
||||
Kind: kindBusObjectsUnasserted, Severity: conditions.Warning, Source: sourceBusObjects,
|
||||
Summary: "the bus's streams and consumers could not be asserted when a declaration was sent: " +
|
||||
"a module may find no consumer to bind, or a seat's holder no worker",
|
||||
Said: err.Error()}
|
||||
}
|
||||
|
||||
// moduleConsumers is every module's durable consumer, from the records the user list is composed from.
|
||||
func moduleConsumers(ctx context.Context, inv *inventory.Inventory) ([]broker.ModuleConsumer, error) {
|
||||
records, err := inv.BusRecords(ctx)
|
||||
|
||||
Reference in New Issue
Block a user