Retry only a failed apply that is not held, answer a repeated compose from the last pass, and say the try the refusal named (issue 774, review round 1)
mesh/delivery merged: source.main moved to novox/mesh-host@3d1465606bed
mesh/delivery-group group fix/compose-retries-transient merged: every member is merged
mesh/check being carried to b0d6684d3731 on 91fb323ce0cf | from mesh-sdk dec3bfe7a8cb | novox/mesh-host#185 in group fix/compose-retries-transient

The verb compose has no warrant (ADR 0563 §2): Agent.Retry released a module held for a failed backup and re-applied
one already running its want. RetryFailed tries again only a failed apply not held, and refuses in words otherwise.
A hand ask no longer restarts the growing bound, and a pass less than askedWithin old is the answer, so a caller in a
loop drives no passes against the vault. A composer whose loop ended refuses at once. The retry line said try N+1.
The test's first shadow now ends before slowAfter is set, a race the race detector found under the new timing.
This commit is contained in:
2026-10-31 01:08:57 +01:00
parent 56eb0b792c
commit b0d6684d37
5 changed files with 131 additions and 23 deletions
+11 -8
View File
@@ -306,11 +306,18 @@ func composeFrom(current *agentNow, composer *atomic.Pointer[selfcompose.Compose
return nil, errors.New("this node composes none of its own modules now (it applies the controller's " +
"compositions, or its node agent is starting), so there is no pass to make")
}
ag := current.get()
out := map[string]any{}
if module != "" {
if ag == nil || !ag.Placed("")[module] {
return nil, fmt.Errorf("%s has no want on this machine, so there is no apply of it to try again", module)
// Only a failed apply that is not held is tried again; what runs is never taken away (review round 1: the
// verb has no warrant, ADR 0563 §2, and a hold for a failed backup is the operator's to release, ADR 0568 §4).
ag := current.get()
if ag == nil {
return nil, errors.New("this engine's node agent has not started, so no apply is tried again")
}
if err := ag.RetryFailed(module); err != nil {
return nil, err
}
out["retried"] = module
}
ctx, cancel := context.WithTimeout(context.Background(), composeReadFor)
defer cancel()
@@ -318,11 +325,7 @@ func composeFrom(current *agentNow, composer *atomic.Pointer[selfcompose.Compose
if err != nil {
return nil, err
}
out := map[string]any{"pass": pass}
if module != "" {
ag.Retry(module)
out["retried"] = module
}
out["pass"] = pass
return out, nil
}
}