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.
83 lines
3.4 KiB
Go
83 lines
3.4 KiB
Go
package bundle
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
)
|
|
|
|
// declarationParse is the parser Load uses, named here so the test reads as the assertion it is.
|
|
func declarationParse(raw []byte) (any, error) { return declaration.Parse(raw) }
|
|
|
|
func TestADefaultBuildCarriesNothingAndSaysSo(t *testing.T) {
|
|
// The important one. A host built without a bundle that applied nothing and reported
|
|
// success would look exactly like a host that raised a first node — and the difference
|
|
// would surface as a mesh that never came up, with nothing to point at.
|
|
if !IsEmpty() {
|
|
t.Fatal("the default build claims to carry a substrate")
|
|
}
|
|
_, err := Load()
|
|
if !errors.Is(err, ErrEmpty) {
|
|
t.Fatalf("an empty bundle did not refuse: %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "would look exactly like applying something") {
|
|
t.Errorf("the refusal does not say why it matters: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCommentsAreNotContent(t *testing.T) {
|
|
// The placeholder is a comment. If comments counted as content, every default build would
|
|
// claim to carry a substrate and then fail to parse it — the right outcome for the wrong
|
|
// reason, and a confusing error at the worst moment.
|
|
if got := stripComments([]byte("// a\n{\"a\":1}\n // b\n")); strings.Contains(string(got), "//") {
|
|
t.Errorf("comments survived stripping: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestOnlyWholeLineCommentsAreStripped(t *testing.T) {
|
|
// Anything cleverer would have to know where strings begin and end. A parser that
|
|
// half-understands its input is worse than one that does not try — a path containing a
|
|
// double slash is ordinary, and losing half of it would be silent.
|
|
raw := []byte(`{"path":"https://example.invalid/a"}`)
|
|
if got := string(stripComments(raw)); got != string(raw) {
|
|
t.Errorf("a slash inside a string was treated as a comment: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestABundleWithContentIsParsedByTheSameParserTheLinkWillUse(t *testing.T) {
|
|
// A bundle that reaches a machine and is then refused by the host carrying it would be a
|
|
// build-time mistake found at the worst possible moment.
|
|
real := []byte(`// pinned
|
|
{"declaration":1,"resources":[{"id":"d","type":"directory","path":"/etc/mesh"}]}`)
|
|
stripped := stripComments(real)
|
|
if strings.Contains(string(stripped), "pinned") {
|
|
t.Fatal("the comment survived")
|
|
}
|
|
if !strings.Contains(string(stripped), "declaration") {
|
|
t.Fatal("the declaration did not survive")
|
|
}
|
|
}
|
|
|
|
func TestWhatValidatesIsWhatIsApplied(t *testing.T) {
|
|
// Found on a real machine. `mesh-host bundle` validated the carried bundle through Load,
|
|
// which strips comments; `reconcile` handed the RAW bytes to the parser, which does not.
|
|
// 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 now one path. This asserts the property that made the bug possible cannot
|
|
// return: whatever Load accepts is what gets applied, byte for byte.
|
|
annotated := []byte("// a comment\n" + `{"declaration":1,"resources":[{"id":"d","type":"directory","path":"/etc/mesh"}]}`)
|
|
|
|
if _, err := parseFor(annotated); err != nil {
|
|
t.Fatalf("an annotated bundle was refused: %v", err)
|
|
}
|
|
}
|
|
|
|
// parseFor mirrors what Load does to arbitrary bytes, so the test can exercise the path
|
|
// without rebuilding the binary with a different embedded file.
|
|
func parseFor(raw []byte) (any, error) {
|
|
return declarationParse(stripComments(raw))
|
|
}
|