Stage 2 — the bundle a host carries
novox/hq ADR 0038: one behaviour, two sources of declaration. This is the source that does not need a mesh — the first node's path. The bundle is embedded in the binary rather than shipped beside it, because "copy it onto a machine and run it is the whole installation" stops being true the moment a second file has to arrive with it. `make host BUNDLE=...` builds a host carrying one; `mesh-host reconcile` applies it; `mesh-host bundle` shows it. A default build carries nothing and REFUSES to reconcile, saying why. A host that applied nothing and reported success would look exactly like one that raised a first node, and the difference would surface later as a mesh that never came up with nothing to point at. Proved on a sealed machine: no route out, no name resolution, one binary copied on, and it configured itself from what it carried. Idempotent on the second run. One bug found by running rather than reasoning, and it is a shape worth naming: `mesh-host bundle` validated the carried bundle through a path that strips comments, while `reconcile` handed the raw bytes to the parser. So the command whose whole job is to check the bundle said yes, and the command that uses it said no. Two paths to one artefact, disagreeing. There is one path now, and a test asserts that what validates is what is applied. What this does NOT prove is stated in the README rather than left implied: the claim under stage 2 is that one host can raise the substrate alone, and the substrate is four container services. There is no container type, because a container needs an image and where images come from is open; what belongs in a substrate is not known, because the closure for a one-node mesh is what research 011 and 012 exist to answer; and the machine used to test this cannot install a container runtime through a sealed network. The mechanism is finished. The claim is not, and shipping a host that claimed a substrate it has never raised would be the fault this whole project is about. 65 tests.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Package bundle is the declaration the host carries.
|
||||
//
|
||||
// novox/hq ADR 0038: the host has one behaviour and two sources of declaration — the control
|
||||
// plane when a mesh is reachable, and this when none is. The first node is not a different
|
||||
// kind of node; it is a node whose mesh is not up yet, and this is what it applies until it is.
|
||||
//
|
||||
// Carried inside the binary rather than beside it, because "copy it onto a machine and run it
|
||||
// is the whole installation" (ADR 0041) stops being true the moment a second file has to
|
||||
// arrive with it.
|
||||
package bundle
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/novox/mesh-host/internal/declaration"
|
||||
)
|
||||
|
||||
// substrate is the pinned tier-1 descriptor, appliable with no mesh present.
|
||||
//
|
||||
// A host built without one carries the placeholder below, and says so rather than applying
|
||||
// nothing and reporting success — a host that silently did nothing on a first node would look
|
||||
// exactly like one that worked.
|
||||
//
|
||||
//go:embed substrate.lock
|
||||
var substrate []byte
|
||||
|
||||
// ErrEmpty means this host was built without a bundle.
|
||||
var ErrEmpty = errors.New(
|
||||
"this host carries no bundle. A host built without one cannot raise a first node, and " +
|
||||
"applying nothing would look exactly like applying something")
|
||||
|
||||
// Raw returns the carried bytes, for inspection.
|
||||
func Raw() []byte { return substrate }
|
||||
|
||||
// IsEmpty reports whether anything was built in. A bundle of only comments and whitespace is
|
||||
// empty for this purpose: the placeholder is a comment, and treating it as content would mean
|
||||
// a default build claims to carry a substrate.
|
||||
func IsEmpty() bool {
|
||||
for _, line := range strings.Split(string(substrate), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" && !strings.HasPrefix(line, "//") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Load parses the carried bundle.
|
||||
//
|
||||
// The same parser the link will use. A bundle that reaches a machine and is then refused by the
|
||||
// host that carries it would be a build-time mistake discovered at the worst possible moment,
|
||||
// which is why `mesh-host bundle` exists to ask before it matters.
|
||||
func Load() (*declaration.Declaration, error) {
|
||||
if IsEmpty() {
|
||||
return nil, ErrEmpty
|
||||
}
|
||||
return declaration.Parse(stripComments(substrate))
|
||||
}
|
||||
|
||||
// stripComments removes whole-line `//` comments so a bundle can be annotated.
|
||||
//
|
||||
// It is JSON on the wire and a pinned, hand-authored artefact here, and a pinned thing nobody
|
||||
// can annotate is a pinned thing nobody can review. Only whole lines: anything cleverer would
|
||||
// need to know where strings begin and end, and a parser that half-understands its input is
|
||||
// worse than one that does not try.
|
||||
func stripComments(raw []byte) []byte {
|
||||
var kept []string
|
||||
for _, line := range strings.Split(string(raw), "\n") {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "//") {
|
||||
continue
|
||||
}
|
||||
kept = append(kept, line)
|
||||
}
|
||||
return []byte(strings.Join(kept, "\n"))
|
||||
}
|
||||
Reference in New Issue
Block a user