Node agent: answer a proposal's candidate keys and follow the seat's placement, not the indexer's groups (issue 1126)

The answer names its proposal and the revision of the node's own candidate key; a node with no key in a proposal is not asked. The watch for its own placement key is tried again until the seat's state exists, so the node switches when its key is written.
This commit is contained in:
2026-10-28 18:43:49 +01:00
parent 58b4f8b6bc
commit 2fe9d678fc
8 changed files with 125 additions and 117 deletions
+21 -6
View File
@@ -937,7 +937,7 @@ func composerEnv(session *link.Session, node string, say func(string), hooks com
Running: hooks.running, Unseal: hooks.unseal, Refused: hooks.refused, Held: hooks.held}
}
// endWhenUnplaced calls stop once this node's placement key is deleted from the indexer's placement (after the watch
// endWhenUnplaced calls stop once this node's placement key is deleted from the seat's placement (after the watch
// caught up, or gone already), so the agent begins again and applies the controller's compositions, as a node the
// catalogue does not place does from its start: the way back (issue 1126, the seed's revert). A watch that cannot be
// made is said; the node then returns at its next start.
@@ -969,7 +969,7 @@ func endWhenUnplaced(ctx context.Context, session *link.Session, node string, st
}
}
// endWhenPlaced calls stop once this node's placement key appears in the indexer's placement (a value after the watch
// endWhenPlaced calls stop once this node's placement key appears in the seat's placement (a value after the watch
// caught up, or one already there), so the agent begins again and composes its own. A watch that cannot be made is said;
// the node then switches at its next start.
func endWhenPlaced(ctx context.Context, session *link.Session, node string, stop func(), say link.Announce) {
@@ -977,10 +977,25 @@ func endWhenPlaced(ctx context.Context, session *link.Session, node string, stop
if err != nil {
return
}
updates, err := bucketWatch{session, delivery.PlacementBucket}.Watch(ctx, key)
if err != nil {
say("whether the catalogue places this node is not followed (" + err.Error() + "): it composes its own from its next start")
return
// The seat's placement state appears when its holder first writes it (ADR 0567): until then the watch is refused, as a
// read of it finds no bucket (Placed), and the node stays on the controller's compositions. The watch is tried again,
// so the node switches when its key is written, not only at its next start.
var updates <-chan agent.Update
said := ""
for try := 1; ; try++ {
updates, err = bucketWatch{session, delivery.PlacementBucket}.Watch(ctx, key)
if err == nil {
break
}
if why := err.Error(); why != said {
say("whether the catalogue places this node is not followed yet (" + why + "): the node stays on the controller's compositions and the watch is tried again")
said = why
}
select {
case <-ctx.Done():
return
case <-time.After(link.Backoff(try)):
}
}
for u := range updates {
if !u.Caught && !u.Deleted && len(u.Value) > 0 {