Apply only the node's own compositions, never the controller's
mesh/delivery merged: source.main moved to novox/mesh-host@84737f4cb26d
mesh/check being carried to 38d666f22390 on 84737f4cb26d | from 14a12cc7b6f9
mesh/delivery merged: source.main moved to novox/mesh-host@84737f4cb26d
mesh/check being carried to 38d666f22390 on 84737f4cb26d | from 14a12cc7b6f9
The controller ended by its unassignment, so a node that falls back to the compositions in its bucket follows what nobody writes (issue 1126, A7). The node agent now always runs its own composer: without a placement key it composes nothing and keeps what runs, a placement it cannot watch stops the agent and is said, and a candidate taking its placement away is refused instead of answered as a return to the controller. The start-time placement read, the end-when-placed watchers and the returning answer go, with their tests and the three integration tests of the controller-composed path. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
+18
-154
@@ -552,8 +552,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
}
|
||||
return out, nil
|
||||
},
|
||||
Trusted: trustedKeys(opts, noControllerKey),
|
||||
Backup: backupNode(session),
|
||||
Backup: backupNode(session),
|
||||
// What the filling refused before this apply may be named now: the provisioner's client is told to fill
|
||||
// again (ace, 2026-10-25 21:17).
|
||||
Settled: func(module string) {
|
||||
@@ -604,8 +603,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
return res
|
||||
},
|
||||
}
|
||||
// The node composes its own modules once the catalogue places it (novox/hq ADR 0547 §2, ADR 0549, issue 1126): its
|
||||
// placement key is read at the agent's start; until then it applies the controller's compositions as before.
|
||||
// The node composes its own modules (novox/hq ADR 0547 §2, ADR 0549, issue 1126).
|
||||
run, stopRun := context.WithCancel(linked)
|
||||
// What the node runs and how it opens a sealed value, for the composer to keep the boxes it runs; and the agent
|
||||
// told when the node cannot compose, so each wanted module's have says it.
|
||||
@@ -650,8 +648,8 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
shadowHooks := hooks
|
||||
shadowHooks.refused, shadowHooks.certify = nil, nil
|
||||
shadowEnv := composerEnv(session, node, say, shadowHooks)
|
||||
// And, after each answer, what runs of an own secret the answer says the vault holds another value of, handed to
|
||||
// the vault while the node applies the controller's compositions (issue 1126, cause C4; ownresync).
|
||||
// And, after each answer, what runs of an own secret the answer says the vault holds another value of: handed to
|
||||
// the vault only while a node applied the controller's compositions (issue 1126, cause C4; ownresync), so nothing now.
|
||||
composes := &composesNow{}
|
||||
resync := ownResync(session, node, say, shadowEnv, composes)
|
||||
shadow := selfcompose.NewShadow(shadowEnv,
|
||||
@@ -660,33 +658,16 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
go resync.take(ctx, answers)
|
||||
})
|
||||
go shadow.Keep(run, link.Backoff) // this run's alone: a switch begins a run, and its shadow, again
|
||||
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 {
|
||||
machinesNow.Store(composer) // the machines a failing network part points at, by the placements (issue 774)
|
||||
composerNow.Store(composer) // what the verb compose asks a pass of, this run's alone
|
||||
composes.set(delivery.ComposesOwn)
|
||||
env.Composed, env.SelfComposed, env.Trusted, env.Policy = composer, true, nil, composer.Policy
|
||||
env.HoldsData = composer.HoldsData // what runs from the kept declaration says Data as its manifest does
|
||||
session.NoteComposes(linked, delivery.ComposesOwn, why)
|
||||
// And once the catalogue no longer places it, the agent begins again on the controller's compositions: the
|
||||
// placement key deleted ends this run, the way back (issue 1126, the seed's revert).
|
||||
go endWhenUnplaced(run, session, node, stopRun, say)
|
||||
} else {
|
||||
composes.set(delivery.ComposesController)
|
||||
go resync.again(run)
|
||||
session.NoteComposes(linked, delivery.ComposesController, why)
|
||||
// And once the catalogue places it, the agent begins again composing its own: the placement key appearing ends
|
||||
// this run, so a node does not wait for a restart to follow the seed of its placement (ADR 0549 §4).
|
||||
go endWhenPlaced(run, session, node, stopRun, say, func(reason string) {
|
||||
session.NoteComposes(linked, delivery.ComposesController, why+"; "+reason)
|
||||
})
|
||||
}
|
||||
// Its own composer, always (issue 1126, A7): a composition made elsewhere (the controller's, in the node's
|
||||
// composition bucket) is never applied. While the seat's placement holds no key for this node it composes
|
||||
// nothing and keeps running what it runs; a placement it cannot watch stops the agent, said, and it begins again.
|
||||
composer := selfComposer(session, node, hooks, say)
|
||||
machinesNow.Store(composer) // the machines a failing network part points at, by the placements (issue 774)
|
||||
composerNow.Store(composer) // what the verb compose asks a pass of, this run's alone
|
||||
composes.set(delivery.ComposesOwn)
|
||||
env.Composed, env.SelfComposed, env.Policy = composer, true, composer.Policy
|
||||
env.HoldsData = composer.HoldsData // what runs from the kept declaration says Data as its manifest does
|
||||
session.NoteComposes(linked, delivery.ComposesOwn, "it composes its own modules, and applies no composition made elsewhere")
|
||||
ag = agent.New(env)
|
||||
own.set(ag)
|
||||
current.set(ag)
|
||||
@@ -1002,8 +983,6 @@ func applyContext(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.WithoutCancel(ctx), applyBound)
|
||||
}
|
||||
|
||||
// selfComposer is the node's own composer when the catalogue places the node (its key `placement.<node>` in the
|
||||
// indexer's state), and nil, said once, while it does not or the key cannot be read.
|
||||
// composerHooks are what the composer reads of the agent and the machine (selfcompose.Env Running, Unseal, Refused).
|
||||
type composerHooks struct {
|
||||
running func() []delivery.Composed
|
||||
@@ -1015,12 +994,9 @@ type composerHooks struct {
|
||||
certify func(ctx context.Context) error
|
||||
}
|
||||
|
||||
func selfComposer(ctx context.Context, session *link.Session, node string, say func(string), hooks composerHooks) (*selfcompose.Composer, string) {
|
||||
key, err := delivery.PlacementNodeKey(node)
|
||||
if err != nil {
|
||||
say("this node applies the controller's compositions: " + err.Error())
|
||||
return nil, err.Error()
|
||||
}
|
||||
// selfComposer is the node's own composer: it composes what the seat's placement places on the node, and nothing while
|
||||
// the placement holds no key for it (issue 1126).
|
||||
func selfComposer(session *link.Session, node string, hooks composerHooks, say func(string)) *selfcompose.Composer {
|
||||
env := composerEnv(session, node, say, hooks)
|
||||
// And it keeps each placed module's data items in its own state data, which the node's disk-load watches (novox/hq
|
||||
// ADR 0577 clause 1); the shadow composes for candidates and keeps none.
|
||||
@@ -1028,27 +1004,7 @@ func selfComposer(ctx context.Context, session *link.Session, node string, say f
|
||||
// And the packages its compositions declare in its own key of the state packages, which pacman reads (novox/hq ADR
|
||||
// 0565 §6).
|
||||
env.Packages = link.EnginePackages{Session: session}
|
||||
unreadable := ""
|
||||
placed, err := selfcompose.Placed(ctx, env.Get, node, link.Backoff, func(m string) {
|
||||
if strings.Contains(m, "cannot be read") {
|
||||
unreadable = m
|
||||
}
|
||||
say(m)
|
||||
}, selfcompose.ComposedItsOwn(ctx, env.Get, node))
|
||||
if err != nil {
|
||||
return nil, "its placement could not be read before the link ended: " + err.Error()
|
||||
}
|
||||
if !placed {
|
||||
why := "the seat's placement state holds no key for it"
|
||||
if unreadable != "" {
|
||||
why = "its placement cannot be read, and it never composed its own: " + unreadable
|
||||
}
|
||||
say("this node applies the controller's compositions: " + why)
|
||||
return nil, why
|
||||
}
|
||||
why := "the seat's placement state holds its key (" + key + ")"
|
||||
say("this node composes its own modules: " + why)
|
||||
return selfcompose.New(env), why
|
||||
return selfcompose.New(env)
|
||||
}
|
||||
|
||||
// composerEnv is what the node's composers read of the bus, the vault and the agent: its own composer and its answer
|
||||
@@ -1085,95 +1041,3 @@ func composerEnv(session *link.Session, node string, say func(string), hooks com
|
||||
Running: hooks.running, Unseal: hooks.unseal, Refused: hooks.refused, Held: hooks.held,
|
||||
VaultAuthority: hooks.vaultAuthority, Certify: hooks.certify}
|
||||
}
|
||||
|
||||
// placementKeysOf is a node's node key and the pattern of the keys that say it is placed: its node key and its legacy
|
||||
// key `placement.<node>`, read while the holder may not have converted it (research 072 row 37). Its module keys say
|
||||
// nothing of whether it is placed.
|
||||
func placementKeysOf(node string) (key, keys string, err error) {
|
||||
key, err = delivery.PlacementNodeKey(node)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
legacy, _ := delivery.LegacyPlacementKey(node)
|
||||
return key, link.WatchPatterns(key, legacy), nil
|
||||
}
|
||||
|
||||
// anyHeld says some key holds a value.
|
||||
func anyHeld(held map[string]bool) bool {
|
||||
for _, h := range held {
|
||||
if h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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.
|
||||
func endWhenUnplaced(ctx context.Context, session *link.Session, node string, stop func(), say link.Announce) {
|
||||
key, keys, err := placementKeysOf(node)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
updates, err := bucketWatch{session, delivery.PlacementBucket}.Watch(ctx, keys)
|
||||
if err != nil {
|
||||
say("whether this node's key in the seat's placement state still exists is not followed (" + err.Error() + "): it returns to the controller's compositions at its next start")
|
||||
return
|
||||
}
|
||||
// Its node key or, before the holder converted it, its legacy key: placed while either holds a value (research
|
||||
// 072 row 37); the holder deletes the legacy key before the node key, so the node key's delete is the last.
|
||||
held, caught := map[string]bool{}, false
|
||||
for u := range updates {
|
||||
if u.Caught {
|
||||
caught = true
|
||||
} else {
|
||||
held[u.Key] = !u.Deleted && len(u.Value) > 0
|
||||
}
|
||||
if caught && !anyHeld(held) {
|
||||
say("this node's key in the seat's placement state is gone (" + key + "): the node agent begins again, applying the controller's compositions")
|
||||
stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
if ctx.Err() == nil {
|
||||
say("the watch of " + key + " ended while the link stays: whether this node's key in the seat's placement state still exists is followed again at the node agent's next start")
|
||||
}
|
||||
}
|
||||
|
||||
// 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, note func(string)) {
|
||||
key, keys, err := placementKeysOf(node)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// The seat's placement state is made when its holder starts; the delivery brings the grants part and the holder first,
|
||||
// so it is normally there. A node-engine that starts before it waits for the bus's word that the bucket was created
|
||||
// (no timer), says so once in the journal and in the node's own have, where the watchman reads it, and stays on the
|
||||
// controller's compositions until its key is written. A watch the account may not make is said the same way, and the
|
||||
// node stays until its next start.
|
||||
updates, err := bucketWatch{session, delivery.PlacementBucket}.WatchAwait(ctx, keys, func(err error) {
|
||||
why := "this node's key in the seat's placement state is waited for, the state is not on the bus yet (" + err.Error() + "): it stays on the controller's compositions and is watched when the bus says the state was created"
|
||||
say(why)
|
||||
note(why)
|
||||
})
|
||||
if err != nil {
|
||||
why := "this node's key in the seat's placement state cannot be followed (" + err.Error() + "): it stays on the controller's compositions until its next start"
|
||||
say(why)
|
||||
note(why)
|
||||
return
|
||||
}
|
||||
for u := range updates {
|
||||
if !u.Caught && !u.Deleted && len(u.Value) > 0 {
|
||||
say("this node's key in the seat's placement state is written now (" + u.Key + "): the node agent begins again, composing its own modules")
|
||||
stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
if ctx.Err() == nil {
|
||||
say("the watch of " + key + " ended while the link stays: whether this node's key in the seat's placement state is written is followed again at the node agent's next start")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user