Load the guard before removing the derived filter when a node returns to adopted, and defer the adoption's orphans only on the flip (hq ADR 0103)

This commit is contained in:
2026-09-22 18:30:13 +02:00
parent b531c47486
commit 491e04fb8f
2 changed files with 124 additions and 11 deletions
+58 -11
View File
@@ -185,21 +185,53 @@ func ApplyKeeping(
return nil
}
// **What protects an adopted node goes last** (novox/hq ADR 0103). The openings and the guard
// are what keep the mesh reachable through the found firewall and the store unreachable from
// outside. When a node is converged they leave the declaration, and removing them first would
// leave the store open from the moment the guard stops until the derived filter loads — and
// for ever, if the filter then fails. So they are removed only once everything else applied
// and the found firewall is retired; if anything failed, they stay, recorded, for the next try.
var protecting []store.Applied
// **What protects an adopted node goes last on the flip, and first on the way back** (novox/hq
// ADR 0103). The openings and the guard are what keep the mesh reachable through the found
// firewall and the store unreachable from outside.
//
// When a node is converged they leave the declaration, and removing them first would leave the
// store open from the moment the guard stops until the derived filter loads — and for ever, if
// the filter then fails. So on a converged declaration they are removed only once everything
// else applied and the found firewall is retired; if anything failed, they stay, recorded, for
// the next try.
//
// Returned to adopted, it is the mirror image: removing the derived filter first would leave
// the store open until the guard loads. So the guard's own resources are applied before any
// orphan is removed, and if a removal then fails the guard is already up. A stale opening on an
// adopted node is removed as any orphan is.
var protecting, orphans []store.Applied
for _, orphan := range known.Orphans(declared, origin) {
if strings.HasPrefix(orphan.ID, declaration.AdoptionPrefix) {
if d.Adoption == nil && strings.HasPrefix(orphan.ID, declaration.AdoptionPrefix) {
protecting = append(protecting, orphan)
continue
}
if err := removeOrphan(orphan); err != nil {
return report, known, err
orphans = append(orphans, orphan)
}
ordered := d.Resources
guardFirst := 0
if d.Adoption != nil {
ordered = nil
for _, r := range d.Resources {
if strings.HasPrefix(r.Identity(), guardPrefix) {
ordered = append(ordered, r)
}
}
guardFirst = len(ordered)
for _, r := range d.Resources {
if !strings.HasPrefix(r.Identity(), guardPrefix) {
ordered = append(ordered, r)
}
}
}
orphansRemoved := false
removeOrphans := func() error {
orphansRemoved = true
for _, orphan := range orphans {
if err := removeOrphan(orphan); err != nil {
return err
}
}
return nil
}
// **A hold whose resource is no longer declared is let go, and nothing on disk is touched.**
@@ -256,7 +288,12 @@ func ApplyKeeping(
// is a different thing — one is "this machine could not do it", the other is "this was never
// a declaration", and they are fixed in different places.
var failures []*Error
for _, resource := range d.Resources {
for i, resource := range ordered {
if !orphansRemoved && i == guardFirst {
if err := removeOrphans(); err != nil {
return report, known, err
}
}
// **On an adopted node, what is found is kept until its module is taken** (novox/hq ADR
// 0100, ADR 0103). Before anything is applied: whatever of a module not yet taken is
// present with no record of this host making it — or would reach what is — is held as it
@@ -345,6 +382,12 @@ func ApplyKeeping(
}
}
if !orphansRemoved {
if err := removeOrphans(); err != nil {
return report, known, err
}
}
// A converged node whose found firewall was in force retires it only now, once everything —
// the mesh's derived filter among it — applied cleanly (novox/hq ADR 0100).
if len(failures) == 0 {
@@ -370,6 +413,10 @@ func ApplyKeeping(
return report, known, nil
}
// guardPrefix is the ids of the mesh's guard on an adopted node: its package, table, unit and
// service (novox/hq ADR 0100).
const guardPrefix = declaration.AdoptionPrefix + "guard"
// Unseal opens a value the mesh sealed to this node. Nil when the node has no sealing key, which
// makes every sealed file an error rather than a silently skipped one.
type Unseal func(sealed string) ([]byte, error)