A repeat assignment says nothing changed (ADR 0115)

One assignment of a module per node is now the rule, not a limitation —
the operator dropped the multi-assignment requirement, and the schema's
(node, module) key has been the decision since migration 0005. What
changed: Assign reports whether the assignment was new, and the command
says 'already runs — one node runs one of each (ADR 0115); nothing
changed' instead of printing 'is assigned' for a no-op, which read as
an action that happened. Idempotence stays: a repeat is exit 0, because
a script stating what is already true is not wrong.
This commit is contained in:
2026-09-26 19:02:35 +02:00
parent 95426e25cf
commit 50734095b8
9 changed files with 51 additions and 25 deletions
+16 -7
View File
@@ -430,27 +430,36 @@ func (i *Inventory) discard(ctx context.Context, name string) error {
return nil
}
// Assign puts a module on a node.
// Assign puts a module on a node, and says whether that is new.
//
// Records the intention and checks nothing. Whether the set of assignments can actually become a
// declaration is resolution's question, asked over the whole set at once — and asking it here,
// one module at a time, would let an assignment look accepted and then refuse when a second
// arrives.
func (i *Inventory) Assign(ctx context.Context, nodeName, module string) error {
//
// **One assignment of a module per node is the rule, not a race lost** (novox/hq ADR 0115). The
// module's name is the assignment's identity — its database user, its broker account, its
// containers and its placed directory are all named by it — so the schema's (node, module) key
// is the decision, and a repeat is absorbed rather than refused. Absorbed audibly: the caller is
// told nothing changed, because "is assigned" printed for a no-op reads as an action.
func (i *Inventory) Assign(ctx context.Context, nodeName, module string) (bool, error) {
node, err := i.NodeByName(ctx, nodeName)
if err != nil {
return err
return false, err
}
if err := i.runsSomewhere(ctx, module); err != nil {
return err
return false, err
}
_, err = i.store.Pool().Exec(ctx,
tag, err := i.store.Pool().Exec(ctx,
`insert into assignment (node, module) values ($1, $2) on conflict do nothing`,
node.ID, module)
if err != nil && strings.Contains(err.Error(), "assignment_module_fkey") {
return fmt.Errorf("%w: %s", ErrNoSuchModule, module)
return false, fmt.Errorf("%w: %s", ErrNoSuchModule, module)
}
return err
if err != nil {
return false, err
}
return tag.RowsAffected() > 0, nil
}
// Unassign takes a module off a node.