Review round 1 of the bus want: a removal keeps the bus server, the have says a server made anew runs the want's fingerprint, the old build is read before the first write, and an unanswered inspect fails the apply rather than replacing the server (ADR 0594 §2, §4)
mesh/delivery-group group feat/774-bus-want rejected: composed together: the checker's verdict at the members' heads (a0ab6ce2e316): novox
mesh/delivery closed: the pull request closed unmerged
mesh/check failed: novox: not composed on novox: nats has not been built for this mesh: server is its "server" artifact… | 2c5f818d1522 on 798d73eb11e9

This commit is contained in:
2026-11-03 09:18:31 +01:00
parent a096abac92
commit 2c5f818d15
7 changed files with 279 additions and 55 deletions
+54 -6
View File
@@ -120,6 +120,9 @@ type Report struct {
MovedOut []MovedOut `json:"moved-out,omitempty"`
// Left is every socket, pipe or device a move-out directory holds and did not move (ADR 0422 §4).
Left []LeftInPlace `json:"left,omitempty"`
// Kept is every resource the scope keeps (Scope.Keep) that this apply left as it runs, by id (novox/hq ADR 0594
// §2, §4): what says whether the bus server still runs what it ran, or was made by this apply.
Kept []string `json:"kept,omitempty"`
}
// Waiting says whether a container of this apply waits to be replaced because it is busy: what makes the
@@ -416,6 +419,11 @@ func applyScoped(
if err := refuseRootIdentities(d); err != nil {
return report, known, &Error{Resource: "the declaration's identities", Err: err, Done: report}
}
// What of the bus server is kept as it runs, asked of the runtime before anything moves (ADR 0594 §2).
keepNow, err := keptServer(ctx, d, scope, run)
if err != nil {
return report, known, &Error{Resource: "the bus server", Err: err, Done: report}
}
declared := map[string]bool{}
for _, r := range d.Resources {
@@ -544,6 +552,17 @@ func applyScoped(
removeOrphan := func(orphan store.Applied) error {
var action, detail string
var err error
if keepNow[orphan.ID] {
// A removed want of the bus module removes the rest of it and never its server (ADR 0594 §2): only a
// warranted want replaces or removes the server. Its record stays, the module's.
detail := "no longer declared; the bus server is left as it runs: only a want carrying the operator's " +
"warrant removes it (novox/hq ADR 0594 §2)"
report.Outcomes = append(report.Outcomes, Outcome{ID: orphan.ID, Type: orphan.Type, Target: orphan.Target,
Action: keptAsItRuns, Detail: detail})
report.Kept = append(report.Kept, orphan.ID)
log(fmt.Sprintf(" %s %s (%s): %s", keptAsItRuns, orphan.ID, orphan.Target, detail))
return nil
}
if module, held := heldByOthers[orphan.ID]; held && !declared[orphan.ID] {
if module == "" {
module = "another module"
@@ -1071,8 +1090,9 @@ func applyScoped(
if from, handed := handedFiles[resource.Identity()]; handed && was.ID == "" {
previous.Wrote = from.Wrote
}
if o, kept := keepAsItRuns(ctx, scope, resource, run, previous); kept {
if o, kept := keepAsItRuns(keepNow, resource, previous); kept {
outcome = o
report.Kept = append(report.Kept, o.ID)
} else {
outcome, err = applyOne(ctx, sys, resource, run, changed, in, previous, unseal, keepFound)
}
@@ -3041,8 +3061,8 @@ const keptAsItRuns = "kept-as-it-runs"
// unpacked at its path before. The outcome carries what the resource was last recorded reading and holding, so its
// record is the module's from here on (taken over from the node's own composition at the bus module's first want) and
// a later warranted apply still sees what changed under it.
func keepAsItRuns(ctx context.Context, scope *Scope, r declaration.Resource, run Runner, previous store.Applied) (Outcome, bool) {
if scope == nil || !scope.Keep[r.Identity()] {
func keepAsItRuns(keep map[string]bool, r declaration.Resource, previous store.Applied) (Outcome, bool) {
if !keep[r.Identity()] {
return Outcome{}, false
}
out := begin(r)
@@ -3053,9 +3073,7 @@ func keepAsItRuns(ctx context.Context, scope *Scope, r declaration.Resource, run
if res.Schedule != "" || res.RunOnce {
return Outcome{}, false
}
if _, err := containerState(ctx, res.Name, run); err != nil {
return Outcome{}, false
}
// Found running by keptServer before the loop: it is left whatever its declaration says now.
out.reads = previous.Reads
out.busySince = previous.BusySince
out.Detail = why
@@ -3079,6 +3097,36 @@ func keepAsItRuns(ctx context.Context, scope *Scope, r declaration.Resource, run
return Outcome{}, false
}
// keptServer is what of the scope's Keep this apply keeps (novox/hq ADR 0594 §2), decided once before anything is
// applied, as the archives that restart the server come before it: every kept container the declaration names is
// asked of the runtime once. All found: the Keep stands. One the runtime says is not there: nothing is kept, so the
// server is made at what the declaration says now and its archives are applied with it. A runtime that does not
// answer, or answers anything else, is an error, never taken as an absent server: replacing a running bus server
// because one inspect failed is the harm the Keep exists to prevent.
func keptServer(ctx context.Context, d *declaration.Declaration, scope *Scope, run Runner) (map[string]bool, error) {
if scope == nil || len(scope.Keep) == 0 {
return nil, nil
}
for _, r := range d.Resources {
c, ok := r.(*declaration.Container)
if !ok || !scope.Keep[r.Identity()] || c.Schedule != "" || c.RunOnce {
continue
}
cri, err := containerRuntime(ctx, run)
if err != nil {
return nil, fmt.Errorf("%w, so whether the bus server %s runs cannot be told; nothing was applied", err, c.Name)
}
if _, err := run(ctx, cri, "container", "inspect", "--format", "{{.State.Running}}", c.Name); err != nil {
if absent(err) {
return nil, nil // made, at what the declaration says now
}
return nil, fmt.Errorf("the container runtime did not say whether the bus server %s runs (%v); nothing was "+
"applied, so it is not replaced on an unanswered look", c.Name, err)
}
}
return scope.Keep, nil
}
// heldStill is the action of a container left alone because a maintenance window holds it stopped
// (novox/hq issue 224). Its own word rather than "unchanged": a person reading a machine that looks
// half-stopped needs to see why, and "unchanged" beside a stopped server says the opposite.