Leave a record the node's own carried for a module that composes its own, and let that module's apply remove it, so the node's own is not refused for ever (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-host@62b211bb6040
mesh/check checking 1d8d7267584e on 62b211bb6040

On the bus node restic's manifest replaced its process measure by awake (mesh-catalog 35ff67bd). The record of restic.measure was the node's, made while restic was folded, so restic's own apply never saw it, and the guard against removing a placed module's resources refused the node's own apply whole every two minutes from 2026-10-30 19:42: nothing of the node's own applied.

Quick fix considered: forget the one record by hand (a hand act on the node; refused). Thorough fix (this): the node's own apply keeps such a record and says so, never refusing or removing it; a module's own apply owns a node record whose id names it, so it alone removes it once neither composition declares it.
This commit is contained in:
2026-10-31 01:13:11 +01:00
parent 91fb323ce0
commit 1d8d726758
2 changed files with 83 additions and 7 deletions
+48 -1
View File
@@ -347,8 +347,19 @@ func (sc *Scope) placedOwner(a store.Applied) string {
}
// owns reports whether a record belongs to the scope's module.
//
// In a module's own apply, a record the node's own composition made for the module while it was folded (recorded as the
// node's, its id naming the module) is the module's too: it is the only apply that may remove it once neither
// composition declares it (issue 774). One the node's own still declares stays, as another's held resource does.
func (sc *Scope) owns(a store.Applied) bool {
return store.OwnerOf(a, declaration.ModuleGuess) == sc.Module
if store.OwnerOf(a, declaration.ModuleGuess) == sc.Module {
return true
}
if sc.Module == store.NodeModule || a.Module != store.NodeModule {
return false
}
m, ok := declaration.ModuleGuess(a.ID)
return ok && m == sc.Module
}
// isNode is whether the scope is the node's own composition, the one that carries identities, groups, libraries and
@@ -473,6 +484,29 @@ func applyScoped(
}
}
// **A resource the node's own carried for a module that now has a composition of its own is that module's to remove**
// (issue 774, 2026-10-30 on the bus node): recorded as the node's while the module was folded, it is no longer in the
// node's own composition and no longer in the module's own either (restic's measure, replaced by awake). The node's own
// apply leaves it, record and all, and says so; the module's own apply sees it as its own (Scope.owns) and removes it.
// Refused instead, the node's own applied nothing at all, every two minutes, for as long as the record stayed.
takenByItsModule := map[string]string{}
if scope != nil && scope.Module == store.NodeModule {
composes := map[string]bool{}
for _, r := range scope.Others {
if m := r.DeclaredModule(); m != "" && m != store.NodeModule {
composes[m] = true
}
}
for _, orphan := range known.Orphans(declared, origin) {
if orphan.Module != store.NodeModule {
continue
}
if m, ok := declaration.ModuleGuess(orphan.ID); ok && composes[m] {
takenByItsModule[orphan.ID] = m
}
}
}
// **A module a want still places here is never removed by another's apply** (issue 774): its resources go only with
// its own apply or its want's removal. One that would is refused whole, before anything is changed, naming them: a
// composition that leaves them out lost what runs, and removing what runs is the worst a node can do with that.
@@ -485,6 +519,9 @@ func applyScoped(
if _, kept := heldByOthers[orphan.ID]; kept {
continue
}
if _, kept := takenByItsModule[orphan.ID]; kept {
continue
}
if m := scope.placedOwner(orphan); m != "" {
refused = append(refused, orphan.ID+" of "+m)
}
@@ -513,6 +550,16 @@ func applyScoped(
log(fmt.Sprintf(" kept %s (%s): %s", orphan.ID, orphan.Target, detail))
return nil
}
if module, taken := takenByItsModule[orphan.ID]; taken {
detail := "no longer declared here; " + module + " has a composition of its own here, whose apply removes it, " +
"so it was left as it is"
report.Outcomes = append(report.Outcomes, Outcome{
ID: orphan.ID, Type: orphan.Type, Target: orphan.Target,
Action: "kept", Detail: detail,
})
log(fmt.Sprintf(" kept %s (%s): %s", orphan.ID, orphan.Target, detail))
return nil
}
if by, held := heldFiles[fileKey(orphan)]; held && by != orphan.ID {
if scope != nil && !declared[by] {
// Another module declares the path: it is that module's, so this record is let go and the file is