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
+45 -13
View File
@@ -77,8 +77,9 @@ type Haves interface {
Read(ctx context.Context, module string) (Have, bool)
// Write writes a module's have where it differs from the bucket's.
Write(ctx context.Context, h Have)
// Removed writes the module's have with the phase removed, once, if a key of it exists.
Removed(ctx context.Context, module string)
// Removed writes the module's have with the phase removed, once, if a key of it exists; words, when given, are its
// detail.
Removed(ctx context.Context, module string, words ...string)
}
// Result is how one apply or removal came out.
@@ -88,6 +89,8 @@ type Result struct {
// JetStream API error while the bus reloads), not the module's: the apply is tried again without a new revision.
Transient bool
Detail string
// Kept is the resources of the keep set the apply left as they ran, by id (apply.Report.Kept, novox/hq ADR 0594 §4).
Kept []string
}
// Backup is the answer of the backup asked before a module moves.
@@ -117,8 +120,9 @@ type Env struct {
// resources (catalogue.BusServerOf, novox/hq ADR 0594 §7), empty for any other module; known says whether the
// module's manifest could be read. Nil names no bus module.
BusServer func(ctx context.Context, module string) (ids []string, known bool)
// Remove removes the module's own resources and nothing of another's.
Remove func(ctx context.Context, module string, others []delivery.Composed) Result
// Remove removes the module's own resources and nothing of another's, and leaves those of keep as they run (the bus
// server's, ADR 0594 §2).
Remove func(ctx context.Context, module string, others []delivery.Composed, keep map[string]bool) Result
// Backup backs a module up before a new composition of it is applied; nil backs up nothing.
Backup func(ctx context.Context, module string) Backup
// Kept is the resources the node last applied, by module, from the declaration it kept before the switch (ADR 0539
@@ -612,6 +616,10 @@ func (a *Agent) writeHave(ctx context.Context, h Have) {
// serverOf is what the module's have says the bus server runs (Have.Server): kept since this agent read or wrote it,
// else read from the have once. Empty for every module but the bus.
//
// A have written before this node agent kept the server names none: the server then runs what that have says the module
// runs, its fingerprint, else its old-form build (the take-over of a server the node's own composition made). Read so on
// the first read, before this agent writes the have, so every have it writes carries it.
func (a *Agent) serverOf(ctx context.Context, module string) string {
a.mu.Lock()
s, known := a.server[module]
@@ -619,8 +627,17 @@ func (a *Agent) serverOf(ctx context.Context, module string) string {
if known {
return s
}
if server, _ := a.busKeep(ctx, module); len(server) == 0 {
return "" // not the bus, or not known to be yet: nothing kept, so a later read asks again
}
if h, ok := a.env.Haves.Read(ctx, module); ok {
s = h.Server
if s == "" {
s = h.FP
}
if s == "" {
s = h.Build
}
}
a.mu.Lock()
if _, written := a.server[module]; !written {
@@ -1496,14 +1513,6 @@ func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNo
ranServer, madeServer := "", ""
if len(keep) > 0 {
ranServer = a.serverOf(ctx, module)
if ranServer == "" {
if h, ok := a.env.Haves.Read(ctx, module); ok {
ranServer = h.FP
if ranServer == "" {
ranServer = h.Build
}
}
}
if ranServer == "" {
ranServer = fp // a bus module this node never ran: its server is made at this fingerprint
}
@@ -1606,6 +1615,10 @@ func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNo
if a.env.Settled != nil {
a.env.Settled(module)
}
if len(keep) > 0 && len(res.Kept) == 0 {
// Nothing of the server was there to keep: the apply made it at this fingerprint (ADR 0594 §4).
madeServer = fp
}
a.writeHave(ctx, Have{Module: module, FP: fp, Composed: dig, Phase: string(delivery.PhaseRunning), Detail: res.Detail,
Server: madeServer})
}
@@ -1624,7 +1637,16 @@ func (a *Agent) remove(ctx context.Context, module string, w wantNow) {
}
others := a.heldOut(module)
a.mu.Unlock()
res := a.env.Remove(ctx, module, others)
// A removed want of the bus module never removes its server (novox/hq ADR 0594 §2): a removal carries no warrant, and
// removing the server is not built. One whose manifest cannot be read keeps what would be the server's, said.
keep, known := a.busKeep(ctx, module)
if !known {
keep = map[string]bool{}
for _, id := range catalogue.BusServer {
keep[module+"."+id] = true
}
}
res := a.env.Remove(ctx, module, others, keep)
a.mu.Lock()
if res.Failed {
a.attempt[module] = attempt{FP: "removed", WantRev: w.Rev}
@@ -1644,6 +1666,16 @@ func (a *Agent) remove(ctx context.Context, module string, w wantNow) {
if a.env.Settled != nil {
a.env.Settled(module)
}
if len(res.Kept) > 0 {
words := fmt.Sprintf("removed, but for %s: the bus server stays as it runs until a want carrying the operator's "+
"warrant removes it (novox/hq ADR 0594 §2)", strings.Join(res.Kept, ", "))
if !known {
words += "; its manifest could not be read, so what would be its server was left"
}
a.env.Say(module + " is " + words)
a.env.Haves.Removed(ctx, module, words)
return
}
a.env.Haves.Removed(ctx, module)
}