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
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:
+48
-1
@@ -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
|
||||
|
||||
@@ -125,12 +125,15 @@ func TestARecordWithoutAnOwnerBelongsToTheModuleItsIdNames(t *testing.T) {
|
||||
// want's removal lets it go.
|
||||
func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
id string // the resource the node's own composition carried, and no longer does
|
||||
moved bool // its module's own composition now declares it (Others)
|
||||
placed bool // a want still places its module here
|
||||
refused bool // the node's own apply is refused whole
|
||||
stays bool // the resource is still on the machine and recorded
|
||||
name string
|
||||
id string // the resource the node's own composition carried, and no longer does
|
||||
moved bool // its module's own composition now declares it (Others)
|
||||
// dropped: its module has a composition of its own here that no longer declares it (Others holds another of its
|
||||
// resources); the node's own apply leaves it, and the module's own apply removes it.
|
||||
dropped bool
|
||||
placed bool // a want still places its module here
|
||||
refused bool // the node's own apply is refused whole
|
||||
stays bool // the resource is still on the machine and recorded
|
||||
// wants names the modules whose want says removed (Scope.RemovedByWant); nil leaves the guard to Placed alone.
|
||||
wants []string
|
||||
}{
|
||||
@@ -141,6 +144,11 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
||||
// it; in the node's own apply only a module a want removed loses its resources.
|
||||
{name: "a folded module with no want", id: "nats.server-conf", wants: []string{}, refused: true, stays: true},
|
||||
{name: "a folded module a want removed", id: "nats.server-conf", wants: []string{"nats", "old"}},
|
||||
// 2026-10-30 19:42 on the bus node: restic's manifest replaced its process measure by awake. Its record was the
|
||||
// node's, carried when restic was folded, so restic's own apply never saw it and the node's own was refused
|
||||
// whole every two minutes, applying nothing of the node's own.
|
||||
{name: "its module's own composition dropped it", id: "restic.measure", dropped: true, placed: true,
|
||||
wants: []string{"old"}, stays: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
@@ -159,6 +167,11 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
||||
moved := declare(t, `{"id":"`+tc.id+`","type":"file","path":"`+fact+`","content":"mesh\n","module":"`+module+`"}`)
|
||||
scope.Others = moved.Resources
|
||||
}
|
||||
if tc.dropped {
|
||||
later := declare(t, `{"id":"`+module+`.awake","type":"file","path":"`+filepath.Join(dir, "awake")+
|
||||
`","content":"a\n","module":"`+module+`"}`)
|
||||
scope.Others = later.Resources
|
||||
}
|
||||
if tc.placed {
|
||||
scope.Placed = map[string]bool{module: true}
|
||||
}
|
||||
@@ -188,6 +201,22 @@ func TestAResourceThatMovedToItsModulesCompositionIsLeftByTheNodesOwnApply(t *te
|
||||
if exists(gone) == !tc.refused {
|
||||
t.Fatalf("a resource no composition holds any more exists %v, with the apply refused %v", exists(gone), tc.refused)
|
||||
}
|
||||
if tc.dropped {
|
||||
if o := outcomeOf(report, tc.id); o.Action != "kept" {
|
||||
t.Fatalf("the node's own apply did not say it left it: %+v", o)
|
||||
}
|
||||
later := declare(t, `{"id":"`+module+`.awake","type":"file","path":"`+filepath.Join(dir, "awake")+
|
||||
`","content":"a\n","module":"`+module+`"}`)
|
||||
_, last, err := ApplyModule(context.Background(), archHost(t), later, Scope{Module: module}, after,
|
||||
store.OriginDeclared, noServices, nil, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, still := last.Find(tc.id); still || exists(fact) {
|
||||
t.Fatalf("the module's own apply left what it no longer declares: recorded %v, on the machine %v",
|
||||
still, exists(fact))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user