Refuse a declaration for the other mode, or older than the mesh's last, and say what an apply would change first

An operator ran `mesh-host reconcile` on an adopted control-node with twelve
modules assigned. It applied the bundle the host carries — the genesis
declaration, foundation only, converged: recreated the store, failed on the
broker's held port, wrote the converged base filter and started its service,
and stopped at the first failing action. The filter closed the machine for
forty-five minutes. The host reported the node adopted in every report, the
declaration said converged, and nothing compared the two; nothing was printed
before acting (hq issue 104).

The host now records the node's mode — from every declaration the mesh sends,
and at genesis from what the operator said — and refuses, at the point of
application, a declaration that says the other mode, naming both and the act
that changes it. Only a declaration the link delivers, signed, changes the
mode: that is how `converge` and `adopt` arrive, so the flip still works and
nothing else can do it. Genesis marks the bundle consumed, with the digest of
what it applied, so `reconcile` holds a node the mesh has spoken to against
what the mesh last said and never the bundle, and refuses the carried bytes
when they are not what genesis applied. A file is refused when it is not what
the mesh last said: a declaration carries no sequence and no issued-at, so the
host cannot tell older from newer, and says so. Both commands print what they
would change — a hold, a removal, an action named as one — before touching
anything, and --dry-run is that list and nothing more.
This commit is contained in:
2026-09-23 23:15:28 +02:00
parent 9176aea6c4
commit 27c4b765b2
14 changed files with 914 additions and 34 deletions
+19 -1
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"path/filepath"
"strings"
"time"
"github.com/novox/mesh-host/internal/apply"
"github.com/novox/mesh-host/internal/declaration"
@@ -33,8 +34,15 @@ type Runner = apply.Runner
// What it does not do is the host's own lifecycle bookkeeping — recording a known-good version,
// clearing the launcher's start counter. Those are facts about a running `mesh-host`, and this is
// not one.
//
// What it does record is that the bundle was consumed, and in which mode the operator raised
// the machine. `raw` is the exact bytes of what is applied — the bundle as rewritten for this
// machine, not as carried — and its digest is what `mesh-host reconcile` later holds the carried
// bundle against: what genesis applied had its ports, root credentials and adoption rewritten,
// so the carried bytes are never it, and applying them on a raised node recreated the store and
// loaded the converged filter on an adopted one (novox/hq issue 104).
func ApplyBundle(ctx context.Context, o Options, sys system.System, d *declaration.Declaration,
run Runner, say func(string)) (apply.Report, error) {
raw []byte, run Runner, say func(string)) (apply.Report, error) {
// Refuse a shape this host cannot apply before anything is applied, exactly as `mesh-host`
// does: finding out half way through is the half-configured machine tier 0 exists to prevent.
@@ -55,6 +63,16 @@ func ApplyBundle(ctx context.Context, o Options, sys system.System, d *declarati
func(line string) { say(" " + strings.TrimPrefix(line, " ")) }, refuseSealed,
apply.KeepIn(filepath.Dir(o.State)))
// The bundle is consumed, whichever way the apply went: what is on the machine came from these
// bytes, and the carried ones must not be applied over it. The mode is the operator's word at
// genesis; the controller records the same and says it in every declaration from then on
// (novox/hq ADR 0100).
updated.Genesis = &store.Genesis{Digest: apply.DigestOf(raw), At: time.Now().UTC(), Rewritten: true}
updated.Mode = store.ModeConverged
if o.Adopted {
updated.Mode = store.ModeAdopted
}
// Saved whichever way it went, for the reason `mesh-host` gives: what was applied before a
// failure is on the machine either way, and a host that did not record it would believe it
// owns less than it does and leave that behind for ever.