A failing placement read chooses neither composer, a deleting head is compared with the controller's compositions at the node's wants, and a placement watch that ends is said; pin mesh-sdk e32d777 (issue 1126, review of the shadow, round 1)
mesh/delivery merged: source.main moved to novox/mesh-host@199f6b48b1df
mesh/delivery-group group fix/1126-shadow-all merged: every member is merged
mesh/check checking 463626143567 on bb15aab50eda | novox/mesh-host#154 in group fix/1126-shadow-all

selfComposer took a KVHistory error for 'not placed': at any restart a timeout
or a bus not ready flipped a self-composing node to the controller's
compositions for a whole run. selfcompose.Placed chooses only on a read that
succeeds (a missing placement state is one), and tries again otherwise, said
once. The shadow's answer to a head deleting the placement reads the
controller's composition at each want in the node's bucket and compares it with
what runs; a want with none refuses, without returning.
This commit is contained in:
2026-10-27 23:37:59 +01:00
parent 4e8fdfdf04
commit 4636261435
7 changed files with 278 additions and 20 deletions
+21 -7
View File
@@ -544,7 +544,14 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, sess
session.NoteCandidates(ctx, answers)
})
go shadow.Keep(linked, link.Backoff)
if composer, why := selfComposer(linked, session, node, say, hooks); composer != nil {
composer, why := selfComposer(linked, session, node, say, hooks)
if linked.Err() != nil {
// The link ended before the node knew whose compositions to apply: it chose neither, and waits for the next.
stopRun()
cancel()
continue
}
if composer != nil {
env.Composed, env.SelfComposed, env.Trusted, env.Policy = composer, true, nil, composer.Policy
session.NoteComposes(linked, delivery.ComposesOwn, why)
// And once the catalogue no longer places it, the agent begins again on the controller's compositions: the
@@ -854,18 +861,19 @@ func selfComposer(ctx context.Context, session *link.Session, node string, say f
say("this node applies the controller's compositions: " + err.Error())
return nil, err.Error()
}
got, err := session.KVHistory(ctx, delivery.PlacementBucket, key)
if err != nil || len(got) == 0 || got[len(got)-1].Deleted {
env := composerEnv(session, node, say, hooks)
placed, err := selfcompose.Placed(ctx, env.Get, node, link.Backoff, say)
if err != nil {
return nil, "its placement could not be read before the link ended: " + err.Error()
}
if !placed {
why := "the catalogue does not place it"
if err != nil {
why = "its placement cannot be read: " + err.Error()
}
say("this node applies the controller's compositions: " + why)
return nil, why
}
why := "the catalogue places it (" + key + ")"
say("this node composes its own modules: " + why)
return selfcompose.New(composerEnv(session, node, say, hooks)), why
return selfcompose.New(env), why
}
// composerEnv is what the node's composers read of the bus, the vault and the agent: its own composer and its answer
@@ -922,6 +930,9 @@ func endWhenUnplaced(ctx context.Context, session *link.Session, node string, st
placed = true
}
}
if ctx.Err() == nil {
say("the watch of " + key + " ended while the link stays: whether the catalogue still places this node is followed again at the node agent's next start")
}
}
// endWhenPlaced calls stop once this node's placement key appears in the indexer's placement (a value after the watch
@@ -944,4 +955,7 @@ func endWhenPlaced(ctx context.Context, session *link.Session, node string, stop
return
}
}
if ctx.Err() == nil {
say("the watch of " + key + " ended while the link stays: whether the catalogue places this node is followed again at the node agent's next start")
}
}