Answer every placement candidate in shadow in the node's own key, applying nothing, so a placement change is checked by the nodes it places before it merges (issue 1126, ADR 0559 §4)

This commit is contained in:
2026-10-27 02:26:07 +01:00
parent 0474a2a3cb
commit fb0cdabe25
7 changed files with 591 additions and 3 deletions
+21 -2
View File
@@ -535,6 +535,19 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, sess
}
},
}
// And its answer to every placement candidate, placed or not, applying nothing (novox/hq ADR 0559 §4): written into
// its own key, `<node>.node`, under candidates.
shadowHooks := hooks
shadowHooks.refused = nil
shadow := selfcompose.NewShadow(composerEnv(session, node, say, shadowHooks),
func(ctx context.Context, answers map[string]delivery.CandidateAnswer) {
session.NoteCandidates(ctx, answers)
})
go func() {
if err := shadow.Run(linked); err != nil && linked.Err() == nil {
say("this node answers no placement candidate on this link: " + err.Error())
}
}()
if composer := selfComposer(linked, session, node, say, hooks); composer != nil {
env.Composed, env.SelfComposed, env.Trusted, env.Policy = composer, true, nil, composer.Policy
} else {
@@ -850,7 +863,13 @@ func selfComposer(ctx context.Context, session *link.Session, node string, say f
return nil
}
say("this node composes its own modules: the catalogue places it (" + key + ")")
return selfcompose.New(selfcompose.Env{Node: node,
return selfcompose.New(composerEnv(session, node, say, hooks))
}
// composerEnv is what the node's composers read of the bus, the vault and the agent: its own composer and its answer
// to placement candidates (selfcompose.Shadow) alike.
func composerEnv(session *link.Session, node string, say func(string), hooks composerHooks) selfcompose.Env {
return selfcompose.Env{Node: node,
Watch: func(ctx context.Context, bucket, pattern string) (<-chan agent.Update, error) {
return bucketWatch{session, bucket}.Watch(ctx, pattern)
},
@@ -869,7 +888,7 @@ func selfComposer(ctx context.Context, session *link.Session, node string, say f
},
// And the vault's word that a secret changed, on which it composes again (ADR 0547 §2).
Hear: session.Hear,
Running: hooks.running, Unseal: hooks.unseal, Refused: hooks.refused, Held: hooks.held})
Running: hooks.running, Unseal: hooks.unseal, Refused: hooks.refused, Held: hooks.held}
}
// endWhenPlaced calls stop once this node's placement key appears in the indexer's placement (a value after the watch