Unify trunk on main: initialization → main #3
@@ -9,7 +9,7 @@ mesh-host profile
|
||||
```
|
||||
|
||||
That is the whole installation. One statically linked binary, nothing else present, no runtime
|
||||
to install first ([`novox/hq` ADR 0041](https://git.novox.be/novox/hq)).
|
||||
to install first ([`novox/hq` ADR 0005](https://git.novox.be/novox/hq)).
|
||||
|
||||
## What it is for
|
||||
|
||||
@@ -56,7 +56,7 @@ cannot be asked to: [firewall privileged]
|
||||
|
||||
A declaration is JSON, versioned, and an **ordered list** of resources — the order is stated
|
||||
rather than derived, because deriving it would be the host deciding
|
||||
([`novox/hq` ADR 0043](https://git.novox.be/novox/hq)). The vocabulary is `directory`, `file`
|
||||
([`novox/hq` ADR 0005](https://git.novox.be/novox/hq)). The vocabulary is `directory`, `file`
|
||||
and `service`, and **anything outside it refuses the whole declaration**: a host that skipped
|
||||
what it did not understand would apply most of a declaration and report success.
|
||||
|
||||
@@ -153,7 +153,7 @@ CGO_ENABLED=0 go build -ldflags="-s -w" -o mesh-host ./cmd/mesh-host
|
||||
Roughly 3 MB, static, no dynamic dependencies. Cross-compiles with `GOOS`/`GOARCH`; a host is
|
||||
built once per architecture and copied, never built on the machine it runs on.
|
||||
|
||||
**Mocking the boundary is forbidden** ([`novox/hq` ADR 0034](https://git.novox.be/novox/hq)).
|
||||
**Mocking the boundary is forbidden** ([`novox/hq` ADR 0017](https://git.novox.be/novox/hq)).
|
||||
Every detector is exercised against a fake runner for its logic *and* against this machine for
|
||||
its behaviour. The tests do not assert which capabilities a machine has — that varies, and is
|
||||
the point of detecting — they assert that detection tells the truth about whatever is there.
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
// version is stamped at build time. Unset in a development build, and said so rather than
|
||||
// defaulted to something that looks like a release.
|
||||
// builtFor names the operating system this host was built for, set at link time
|
||||
// (novox/hq ADR 0060). A host built without one refuses to do anything that touches the
|
||||
// (novox/hq ADR 0005). A host built without one refuses to do anything that touches the
|
||||
// machine, rather than guessing and calling a package manager that is not there.
|
||||
var builtFor = ""
|
||||
|
||||
@@ -161,7 +161,7 @@ func run(ctx context.Context, command string, opts options) error {
|
||||
return fmt.Errorf("reading the declaration: %w", err)
|
||||
}
|
||||
// ParseTrusted: a file handed to the host by someone already running it as root is
|
||||
// not the link. novox/hq ADR 0047 bounds what a REMOTE party may push; someone who
|
||||
// not the link. novox/hq ADR 0005 bounds what a REMOTE party may push; someone who
|
||||
// can write this file and run this binary can do anything the binary can, so refusing
|
||||
// them an action would buy nothing and would make an action untestable except by
|
||||
// rebuilding the bundle.
|
||||
@@ -172,7 +172,7 @@ func run(ctx context.Context, command string, opts options) error {
|
||||
return runApply(ctx, opts, d, opts.file)
|
||||
|
||||
case "reconcile":
|
||||
// The first node's path. novox/hq ADR 0038: no mesh reachable means the declaration
|
||||
// The first node's path. novox/hq ADR 0004: no mesh reachable means the declaration
|
||||
// comes from the bundle the host carries. There is no link yet, so this is currently
|
||||
// the only source — which is a stage, not a design, and saying so beats implying the
|
||||
// other source exists.
|
||||
@@ -275,7 +275,7 @@ func writeInventory(inv inventory.Inventory) {
|
||||
writeProfile(inv.Profile)
|
||||
|
||||
// Printed last and never hidden. An inventory that quietly omits what it could not read
|
||||
// is the same fault as a report assembled from intent (novox/hq ADR 0035).
|
||||
// is the same fault as a report assembled from intent (novox/hq ADR 0018).
|
||||
if len(inv.Unreadable) > 0 {
|
||||
fmt.Println("\ncould not read:")
|
||||
for _, u := range inv.Unreadable {
|
||||
@@ -307,7 +307,7 @@ func runApply(ctx context.Context, opts options, d *declaration.Declaration, sou
|
||||
}
|
||||
// Refuse a declaration naming a shape this host cannot apply, before anything is applied.
|
||||
// An android host has no `package` applier, and finding that out half way through is the
|
||||
// half-configured machine this host exists to prevent (novox/hq ADR 0060).
|
||||
// half-configured machine this host exists to prevent (novox/hq ADR 0005).
|
||||
if err := system.Check(sys, d); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -336,7 +336,7 @@ func runApply(ctx context.Context, opts options, d *declaration.Declaration, sou
|
||||
}
|
||||
|
||||
// Only now, and only after a clean apply: this version got as far as a completed
|
||||
// reconcile, which is the whole of what "known good" claims (novox/hq ADR 0059). Not
|
||||
// reconcile, which is the whole of what "known good" claims (novox/hq ADR 0005). Not
|
||||
// health — a disconnected node is ordinary, and a resource that fails is the machine's
|
||||
// problem rather than the binary's.
|
||||
//
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
// Three properties, each following a recorded decision, and each of them the difference
|
||||
// between this and a script that writes files:
|
||||
//
|
||||
// - A failed step fails the apply (novox/hq ADR 0008). Not "logs and continues": a partial
|
||||
// - A failed step fails the apply (novox/hq ADR 0010). Not "logs and continues": a partial
|
||||
// apply that reports success is the mesh's most expensive shape.
|
||||
// - Every applier READS BACK. Setting a value is not evidence the value took.
|
||||
// - What was applied is recorded after it works, never before (ADR 0035). A failed apply
|
||||
// - What was applied is recorded after it works, never before (ADR 0018). A failed apply
|
||||
// leaves the machine in whatever state it reached, and nothing must claim otherwise.
|
||||
package apply
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
// Runner executes a command. The real one is used everywhere outside unit tests; behaviour
|
||||
// against a real system is tested alongside rather than mocked (novox/hq ADR 0034).
|
||||
// against a real system is tested alongside rather than mocked (novox/hq ADR 0017).
|
||||
type Runner = system.Runner
|
||||
|
||||
// Outcome is what happened to one resource.
|
||||
@@ -379,7 +379,7 @@ func applyService(ctx context.Context, sys system.System, r *declaration.Service
|
||||
// what it actually did.
|
||||
//
|
||||
// Only ever called for something in the store, which is what bounds it: the host is
|
||||
// authoritative over its own footprint and inert everywhere else (novox/hq ADR 0043).
|
||||
// authoritative over its own footprint and inert everywhere else (novox/hq ADR 0005).
|
||||
//
|
||||
// It returns the action rather than assuming "removed", because for half the vocabulary the
|
||||
// honest word is "forgotten". A host that reported a package removed when it left the package
|
||||
@@ -469,7 +469,7 @@ func ExecRunner(ctx context.Context, name string, args ...string) (string, error
|
||||
//
|
||||
// It never upgrades and never removes. "Present" is the whole of what a package resource
|
||||
// asserts, because version is the package manager's business and the mesh does not have a
|
||||
// second opinion about it (novox/hq ADR 0041 — the host depends on nothing, and that includes
|
||||
// second opinion about it (novox/hq ADR 0005 — the host depends on nothing, and that includes
|
||||
// not becoming a second package manager).
|
||||
func applyPackage(ctx context.Context, sys system.System, r *declaration.Package, run Runner) (Outcome, error) {
|
||||
out := begin(r)
|
||||
@@ -641,7 +641,7 @@ func sortedKeys(m map[string]string) []string {
|
||||
// idempotency check and the read-back. Running it first is how the host knows whether there is
|
||||
// anything to do — it does not know what a database is, so "is the database there" is a
|
||||
// question only the declaration can ask. Running it again afterwards is how the host knows the
|
||||
// command had the effect it claimed (novox/hq ADR 0047).
|
||||
// command had the effect it claimed (novox/hq ADR 0005).
|
||||
func applyAction(ctx context.Context, r *declaration.Action, run Runner) (Outcome, error) {
|
||||
out := begin(r)
|
||||
|
||||
@@ -682,7 +682,7 @@ func runAction(ctx context.Context, r *declaration.Action, argv []string, run Ru
|
||||
//
|
||||
// Two, because two exist on machines the mesh runs on. The list is short on purpose: each entry
|
||||
// is a claim that its probe and its CLI have been checked, not that a binary of that name might
|
||||
// work (novox/hq ADR 0060).
|
||||
// work (novox/hq ADR 0005).
|
||||
//
|
||||
// The probe differs and the rest does not, which is what makes this a lookup rather than an
|
||||
// interface. `docker info --format {{.ServerVersion}}` fails on podman — the field does not
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/novox/mesh-host/internal/system"
|
||||
)
|
||||
|
||||
// Each test names the decision it defends (novox/hq ADR 0034).
|
||||
// Each test names the decision it defends (novox/hq ADR 0017).
|
||||
|
||||
func parse(t *testing.T, raw string) *declaration.Declaration {
|
||||
t.Helper()
|
||||
@@ -87,7 +87,7 @@ func TestADriftedMachineIsReturned(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestADroppedResourceIsRemoved(t *testing.T) {
|
||||
// novox/hq ADR 0043: the host removes what it previously applied and is no longer
|
||||
// novox/hq ADR 0005: the host removes what it previously applied and is no longer
|
||||
// declared. Removing a line from a declaration is an act with an effect.
|
||||
dir := t.TempDir()
|
||||
keep := filepath.Join(dir, "keep.conf")
|
||||
@@ -178,7 +178,7 @@ func TestARenameToTheSamePathDoesNotDeleteTheNewFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAFailedStepFailsTheApply(t *testing.T) {
|
||||
// novox/hq ADR 0008. And the error carries what HAD been done, because the machine is in
|
||||
// novox/hq ADR 0010. And the error carries what HAD been done, because the machine is in
|
||||
// whatever state the apply reached and the only honest thing to hand back is that list.
|
||||
dir := t.TempDir()
|
||||
blocker := filepath.Join(dir, "blocker")
|
||||
@@ -214,7 +214,7 @@ func TestAFailedStepFailsTheApply(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNothingIsRecordedUntilItWorked(t *testing.T) {
|
||||
// novox/hq ADR 0035. A record written before the fact restates the request in a new place
|
||||
// novox/hq ADR 0018. A record written before the fact restates the request in a new place
|
||||
// and inherits none of the authority of having happened.
|
||||
dir := t.TempDir()
|
||||
blocker := filepath.Join(dir, "blocker")
|
||||
@@ -416,7 +416,7 @@ func TestForgettingAUnitThatIsGoneDoesNotStrandTheNode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- package, container and action (novox/hq 07-the-substrate.md, ADR 0046, ADR 0047) ---
|
||||
// --- package, container and action (novox/hq 07-the-substrate.md, ADR 0006, ADR 0005) ---
|
||||
|
||||
func parseTrusted(t *testing.T, raw string) *declaration.Declaration {
|
||||
t.Helper()
|
||||
@@ -831,7 +831,7 @@ func TestAnUnknownBootStateIsRefusedNotGuessed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- more than one container runtime (novox/hq ADR 0060) ---
|
||||
// --- more than one container runtime (novox/hq ADR 0005) ---
|
||||
|
||||
func TestTheRuntimeProbeIsPerRuntime(t *testing.T) {
|
||||
// Verified against a real podman 6.1.0 before this was written:
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Package bundle is the declaration the host carries.
|
||||
//
|
||||
// novox/hq ADR 0038: the host has one behaviour and two sources of declaration — the control
|
||||
// novox/hq ADR 0004: 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
|
||||
// is the whole installation" (ADR 0005) stops being true the moment a second file has to
|
||||
// arrive with it.
|
||||
package bundle
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
// One bundle per operating system, because its CONTENTS are per system even though its
|
||||
// mechanism is not: package names, unit names and service names all differ
|
||||
// (novox/hq ADR 0060). All three are embedded and the host applies the one it was built for —
|
||||
// (novox/hq ADR 0005). All three are embedded and the host applies the one it was built for —
|
||||
// an arch host never reads the alpine bundle.
|
||||
//
|
||||
// A host whose bundle is only comments carries nothing, and says so rather than applying
|
||||
@@ -76,7 +76,7 @@ func Load(system string) (*declaration.Declaration, error) {
|
||||
return nil, ErrEmpty
|
||||
}
|
||||
// ParseTrusted: the bundle arrives with the binary, so it may carry actions the link may
|
||||
// not (novox/hq ADR 0047). The bootstrap needs them — creating the control plane's database
|
||||
// not (novox/hq ADR 0005). The bootstrap needs them — creating the control plane's database
|
||||
// happens before there is any mesh to ask for one.
|
||||
return declaration.ParseTrusted(stripComments(locks[system]))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Data, never instructions. The vocabulary is finite, versioned, and anything outside it
|
||||
// refuses the whole declaration rather than being skipped — a host that applied most of what
|
||||
// it was sent and reported success is a node that looks configured and is not
|
||||
// (novox/hq ADR 0043).
|
||||
// (novox/hq ADR 0005).
|
||||
package declaration
|
||||
|
||||
import (
|
||||
@@ -162,7 +162,7 @@ type Container struct {
|
||||
ID string `json:"id"`
|
||||
Type Type `json:"type"`
|
||||
Name string `json:"name"`
|
||||
// Image is pinned by digest (novox/hq ADR 0046) — a tag moves and a digest does not.
|
||||
// Image is pinned by digest (novox/hq ADR 0006) — a tag moves and a digest does not.
|
||||
Image string `json:"image"`
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
Ports []string `json:"ports,omitempty"`
|
||||
@@ -189,7 +189,7 @@ type Action struct {
|
||||
Command []string `json:"command"`
|
||||
// Verify is not optional and is not a courtesy. It is the read-back AND the idempotency
|
||||
// check: the host does not know what a database is, so "is it already there" is a question
|
||||
// only the declaration can ask (novox/hq ADR 0047).
|
||||
// only the declaration can ask (novox/hq ADR 0005).
|
||||
Verify []string `json:"verify"`
|
||||
// In names a container to run inside. Empty means the machine itself.
|
||||
In string `json:"in,omitempty"`
|
||||
@@ -207,7 +207,7 @@ func (a *Action) Target() string {
|
||||
}
|
||||
|
||||
func (a *Action) validate(where string, allowActions bool) []string {
|
||||
// The bound the whole security argument rests on (novox/hq ADR 0047).
|
||||
// The bound the whole security argument rests on (novox/hq ADR 0005).
|
||||
if !allowActions {
|
||||
return []string{where +
|
||||
": an action arrived over the link, and the link may not carry one. The host " +
|
||||
@@ -264,7 +264,7 @@ type Declaration struct {
|
||||
// nothing to check against.
|
||||
For string
|
||||
// Resources, in the order they are applied. The host does not sort them: ordering is a
|
||||
// decision, and deciding is not what the host does (novox/hq ADR 0037).
|
||||
// decision, and deciding is not what the host does (novox/hq ADR 0005).
|
||||
Resources []Resource
|
||||
}
|
||||
|
||||
@@ -286,14 +286,14 @@ func (e *RefusalError) Error() string {
|
||||
}
|
||||
|
||||
// Parse reads a declaration that arrived over the link, and refuses anything it does not fully
|
||||
// understand — including any action, which the link may not carry (novox/hq ADR 0047).
|
||||
// understand — including any action, which the link may not carry (novox/hq ADR 0005).
|
||||
func Parse(raw []byte) (*Declaration, error) { return parse(raw, false) }
|
||||
|
||||
// ParseTrusted reads a declaration from a source already as privileged as the host itself: the
|
||||
// bundle it carries, or a file handed to it by someone who is running it as root.
|
||||
//
|
||||
// Actions are permitted here and nowhere else. The asymmetry is deliberate and is the entire
|
||||
// content of ADR 0047: refusing actions from the bundle buys nothing, because whoever built the
|
||||
// content of ADR 0005: refusing actions from the bundle buys nothing, because whoever built the
|
||||
// bundle built the binary; refusing them from the link buys the bound on what a compromised
|
||||
// control plane can express.
|
||||
func ParseTrusted(raw []byte) (*Declaration, error) { return parse(raw, true) }
|
||||
@@ -450,7 +450,7 @@ func checkMode(where, mode string) []string {
|
||||
// checkImage insists on a digest.
|
||||
//
|
||||
// A tag moves and a digest does not. The bundle's whole claim is that what it names is exact
|
||||
// (novox/hq ADR 0046), and a bundle pinning `postgres:17` pins nothing — it names whatever
|
||||
// (novox/hq ADR 0006), and a bundle pinning `postgres:17` pins nothing — it names whatever
|
||||
// that tag points at on the day the host happens to run.
|
||||
func checkImage(where, image string) []string {
|
||||
if image == "" {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Each test names the decision it defends (novox/hq ADR 0034). The decision here is ADR 0043,
|
||||
// Each test names the decision it defends (novox/hq ADR 0017). The decision here is ADR 0005,
|
||||
// and the property it turns on is that unknown is REFUSED, never skipped.
|
||||
|
||||
func valid() string {
|
||||
@@ -160,7 +160,7 @@ func TestAnEmptyDeclarationIsAMistake(t *testing.T) {
|
||||
// --- the vocabulary the substrate bootstrap needs (novox/hq 07-the-substrate.md) ---
|
||||
|
||||
func TestAnActionOverTheLinkIsRefused(t *testing.T) {
|
||||
// novox/hq ADR 0047. The link may push declarations of known shape and never a command to
|
||||
// novox/hq ADR 0005. The link may push declarations of known shape and never a command to
|
||||
// run. This is the boundary the whole security argument rests on, so it is asserted
|
||||
// directly rather than inferred from the type list.
|
||||
raw := []byte(`{"declaration":1,"resources":[
|
||||
@@ -195,7 +195,7 @@ func TestAnActionWithoutVerifyIsRefused(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAnImageMustBePinnedByDigest(t *testing.T) {
|
||||
// novox/hq ADR 0046: reproducibility comes from pinning the identity of a thing. A bundle
|
||||
// novox/hq ADR 0006: reproducibility comes from pinning the identity of a thing. A bundle
|
||||
// naming a tag pins nothing — it names whatever that tag points at on the day it runs.
|
||||
for _, image := range []string{
|
||||
"postgres:17",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Reported upward and never asked downward (novox/hq 03-DESIGN/01-to-be/05-the-node-host.md).
|
||||
// Everything here is read from the machine at the moment of asking — nothing is remembered,
|
||||
// nothing is derived from a file that says what the machine ought to be
|
||||
// (novox/hq ADR 0035).
|
||||
// (novox/hq ADR 0018).
|
||||
package inventory
|
||||
|
||||
import (
|
||||
@@ -39,7 +39,7 @@ type Inventory struct {
|
||||
|
||||
// ObservedAt is when this was read. An inventory with no timestamp cannot be told from a
|
||||
// stale one, and a node that has been unreachable for a week is an ordinary situation
|
||||
// (novox/hq ADR 0036) rather than an error — so the age of the observation is part of it.
|
||||
// (novox/hq ADR 0004) rather than an error — so the age of the observation is part of it.
|
||||
ObservedAt time.Time `json:"observed_at"`
|
||||
|
||||
// Unreadable lists what could not be determined, and why. An absent field and a field that
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestMemoryIsReadOrReportedMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
|
||||
// A node unreachable for a week is an ordinary situation (novox/hq ADR 0036), so an
|
||||
// A node unreachable for a week is an ordinary situation (novox/hq ADR 0004), so an
|
||||
// inventory that cannot be told from a stale one is missing the fact that matters.
|
||||
before := time.Now().UTC()
|
||||
inv := Collect(context.Background(), nil, nil, time.Second)
|
||||
@@ -79,7 +79,7 @@ func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
|
||||
|
||||
func TestTheHostDoesNotNameTheNode(t *testing.T) {
|
||||
// A node's name is assigned by the mesh. A host that named itself would be deciding
|
||||
// something, which is precisely what novox/hq ADR 0037 forbids it to do.
|
||||
// something, which is precisely what novox/hq ADR 0005 forbids it to do.
|
||||
inv := Collect(context.Background(), nil, nil, time.Second)
|
||||
hostname, _ := os.Hostname()
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestTheHostDoesNotNameTheNode(t *testing.T) {
|
||||
// --- against this machine ---------------------------------------------------------------
|
||||
|
||||
func TestAgainstThisMachine_inventoryIsTrue(t *testing.T) {
|
||||
// novox/hq ADR 0034: behaviour against a real system is tested alongside, not mocked.
|
||||
// novox/hq ADR 0017: behaviour against a real system is tested alongside, not mocked.
|
||||
inv := Collect(context.Background(), nil, profile.Default(nil), 10*time.Second)
|
||||
|
||||
if inv.Machine == "" {
|
||||
|
||||
@@ -111,7 +111,7 @@ func firstLine(s string) string {
|
||||
// privileged reports whether the host can change this machine at all.
|
||||
//
|
||||
// Reported as a capability rather than checked at startup on purpose: a host that cannot act
|
||||
// is still a host that can report, and novox/hq ADR 0036 says what varies between nodes lives
|
||||
// is still a host that can report, and novox/hq ADR 0004 says what varies between nodes lives
|
||||
// here rather than in the definition of a node.
|
||||
type privileged struct{}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ type Detector interface {
|
||||
// Runner executes a command. Replaceable in tests for the pure-logic layer ONLY — every
|
||||
// detector in this package is exercised against the real machine as well, because a test that
|
||||
// fakes the system under detection asserts that the fake behaves as expected
|
||||
// (novox/hq ADR 0034).
|
||||
// (novox/hq ADR 0017).
|
||||
type Runner func(ctx context.Context, name string, args ...string) (stdout string, err error)
|
||||
|
||||
// ExecRunner runs a real command, with output captured and stdin closed.
|
||||
@@ -97,7 +97,7 @@ func (p Profile) Missing() []string {
|
||||
// Detect runs every detector and collects the verdicts.
|
||||
//
|
||||
// A detector that fails does not fail the profile. This is deliberately NOT the rule in
|
||||
// novox/hq ADR 0008: that rule governs applying state, where a failed step means the machine
|
||||
// novox/hq ADR 0010: that rule governs applying state, where a failed step means the machine
|
||||
// is not what was asked for. Detection is the opposite — a failed probe is a finding, and the
|
||||
// finding is "absent, because the probe failed", which is exactly what a caller needs to know.
|
||||
// Aborting would replace one legible absence with total ignorance.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Against the real machine. novox/hq ADR 0034: structure and logic are tested first, behaviour
|
||||
// Against the real machine. novox/hq ADR 0017: structure and logic are tested first, behaviour
|
||||
// against a real system alongside, and mocking the boundary is forbidden — a test that fakes
|
||||
// the system under detection asserts that the fake behaves as expected.
|
||||
//
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// The decision each test defends is named in the test, per novox/hq ADR 0034. These cover
|
||||
// The decision each test defends is named in the test, per novox/hq ADR 0017. These cover
|
||||
// structure and logic; profile_system_test.go covers the same detectors against the real
|
||||
// machine, because a test that fakes the system under detection asserts only that the fake
|
||||
// behaves as expected.
|
||||
@@ -57,7 +57,7 @@ func TestEveryVerdictSaysHowItKnows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDetectionSurvivesAFailingProbe(t *testing.T) {
|
||||
// Deliberately NOT ADR 0008. That rule governs APPLYING state, where a failed step means
|
||||
// Deliberately NOT ADR 0010. That rule governs APPLYING state, where a failed step means
|
||||
// the machine is not what was asked for. A failed probe is a finding, and aborting would
|
||||
// replace one legible absence with total ignorance of the rest.
|
||||
only := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Package store is what this node knows about itself, and it is authoritative while
|
||||
// disconnected.
|
||||
//
|
||||
// Not a cache of the control plane. novox/hq ADR 0036 makes disconnection an ordinary
|
||||
// Not a cache of the control plane. novox/hq ADR 0004 makes disconnection an ordinary
|
||||
// situation rather than an exception, and this is what makes it ordinary: a machine shut for a
|
||||
// week comes back and reconciles, it does not come back and ask what it is.
|
||||
//
|
||||
// Its first job arrives with the first apply rather than with the link (ADR 0043): the host
|
||||
// Its first job arrives with the first apply rather than with the link (ADR 0005): the host
|
||||
// removes what it previously applied and is no longer declared, and it can only know that
|
||||
// because it wrote it down.
|
||||
package store
|
||||
@@ -27,7 +27,7 @@ const DefaultPath = "/var/lib/mesh-host/state.json"
|
||||
|
||||
// Applied is one resource the host put on this machine, and what it did.
|
||||
//
|
||||
// Recorded AFTER the resource was applied and read back, never before (novox/hq ADR 0035).
|
||||
// Recorded AFTER the resource was applied and read back, never before (novox/hq ADR 0018).
|
||||
// A record written up front restates the request in a new place and inherits none of the
|
||||
// authority of having happened.
|
||||
type Applied struct {
|
||||
@@ -164,7 +164,7 @@ func (s *State) Forget(id string) {
|
||||
//
|
||||
// Reverse order because undoing in the order things were made undoes a directory before the
|
||||
// file inside it. Reversing is the only ordering the host can derive without deciding
|
||||
// anything, which is the line novox/hq ADR 0037 draws.
|
||||
// anything, which is the line novox/hq ADR 0005 draws.
|
||||
func (s State) Orphans(declared map[string]bool) []Applied {
|
||||
var out []Applied
|
||||
for i := len(s.Resources) - 1; i >= 0; i-- {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// way to run something — and refuses the other three. That is not a broken host: a declaration
|
||||
// naming a shape this host does not implement is refused whole, the same treatment an unknown
|
||||
// type gets, and the profile tells the control plane which shapes exist so it never sends one
|
||||
// it cannot do (novox/hq ADR 0060).
|
||||
// it cannot do (novox/hq ADR 0005).
|
||||
//
|
||||
// What it cannot do, and why:
|
||||
//
|
||||
@@ -24,13 +24,13 @@ import (
|
||||
// and an unlocked bootloader. On a normal device nothing can register with it.
|
||||
// - **container** — no container runtime, and no kernel access to give one.
|
||||
//
|
||||
// **This host is EPISODIC** (novox/hq ADR 0062). Everywhere else an init runs the launcher at
|
||||
// **This host is EPISODIC** (novox/hq ADR 0005). Everywhere else an init runs the launcher at
|
||||
// boot and the launcher supervises the host. Android grants neither: nothing to register with
|
||||
// without root, and nothing worth supervising, because a supervisor would be killed alongside
|
||||
// what it supervises.
|
||||
//
|
||||
// So it runs when the platform allows and is killed when the platform wants the memory — and
|
||||
// that is **disconnection**, which ADR 0036 already made an ordinary situation rather than an
|
||||
// that is **disconnection**, which ADR 0004 already made an ordinary situation rather than an
|
||||
// exception. It needs no keep-alive and no new mechanism: the store is already authoritative
|
||||
// while disconnected, reconcile already happens on start, and the mesh already reports *last
|
||||
// heard from* rather than alarming on silence.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package system is the part of the host that differs between operating systems.
|
||||
//
|
||||
// novox/hq ADR 0060. A machine has apk because it is Alpine; the package manager, the service
|
||||
// novox/hq ADR 0005. A machine has apk because it is Alpine; the package manager, the service
|
||||
// manager and the packaging format arrive together as one decision somebody made at install
|
||||
// time. So they are not independent knobs — they are one implementation, named after the system
|
||||
// it belongs to.
|
||||
@@ -75,7 +75,7 @@ func Supports(s System, t declaration.Type) bool {
|
||||
// Check refuses a declaration naming a shape this host cannot apply.
|
||||
//
|
||||
// Refused whole and before anything is applied, which is the same treatment an unknown type
|
||||
// gets (novox/hq ADR 0043) — a host that applied the parts it understood would leave a machine
|
||||
// gets (novox/hq ADR 0005) — a host that applied the parts it understood would leave a machine
|
||||
// that looks configured and is not. The reason differs and the outcome does not.
|
||||
func Check(s System, d *declaration.Declaration) error {
|
||||
var problems []string
|
||||
@@ -116,7 +116,7 @@ func everyShape() []declaration.Type {
|
||||
// portableShapes need only a filesystem and a way to run something.
|
||||
//
|
||||
// The floor. A host that can do nothing else can still do these, which is what makes a partial
|
||||
// host a real thing rather than a broken one (novox/hq ADR 0060).
|
||||
// host a real thing rather than a broken one (novox/hq ADR 0005).
|
||||
func portableShapes() []declaration.Type {
|
||||
return []declaration.Type{
|
||||
declaration.TypeDirectory, declaration.TypeFile, declaration.TypeAction,
|
||||
|
||||
@@ -238,7 +238,7 @@ func TestOpenRCBootStateComesFromTheRunlevel(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEachSystemUsesItsOwnCommands(t *testing.T) {
|
||||
// The whole point of ADR 0060: the alpine host must never reach for systemctl, and the arch
|
||||
// The whole point of ADR 0005: the alpine host must never reach for systemctl, and the arch
|
||||
// host must never reach for rc-service.
|
||||
for _, tc := range []struct{ name, forbidden string }{
|
||||
{"arch", "rc-service"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package upgrade is how the host survives replacing itself.
|
||||
//
|
||||
// novox/hq ADR 0057 and ADR 0059. Two facts, and neither is the host judging its own health:
|
||||
// novox/hq ADR 0005 and ADR 0005. Two facts, and neither is the host judging its own health:
|
||||
//
|
||||
// - whether the executable this process started from has been replaced on disk, which is how
|
||||
// it knows to stand aside for a new one;
|
||||
@@ -42,7 +42,7 @@ type Self struct {
|
||||
//
|
||||
// path is what os.Executable() returned; a test passes one it can manipulate, because the
|
||||
// boundary being tested is the filesystem and a fake would assert that the fake behaves as
|
||||
// expected (novox/hq ADR 0034).
|
||||
// expected (novox/hq ADR 0017).
|
||||
func Current(path string) (Self, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
//
|
||||
// Against the real filesystem rather than a fake one. What is being tested is how the operating
|
||||
// system behaves when a file is replaced under a running process, and a fake would assert that
|
||||
// the fake behaves as expected (novox/hq ADR 0034).
|
||||
// the fake behaves as expected (novox/hq ADR 0017).
|
||||
func started(t *testing.T) (Self, string) {
|
||||
t.Helper()
|
||||
binary := filepath.Join(t.TempDir(), "mesh-host")
|
||||
|
||||
@@ -141,7 +141,7 @@ done
|
||||
# These need the launcher to actually run as a supervisor rather than one iteration, so they do
|
||||
# not set MESH_HOST_RUN_ONCE.
|
||||
|
||||
# A host that exits 0 has upgraded itself and stood aside (novox/hq ADR 0057). The launcher must
|
||||
# A host that exits 0 has upgraded itself and stood aside (novox/hq ADR 0005). The launcher must
|
||||
# start it again — and must NOT count it, because it did not fail.
|
||||
setup
|
||||
unset MESH_HOST_RUN_ONCE
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Supervise the host: start it, watch it, and decide what to do when it stops.
|
||||
#
|
||||
# novox/hq ADR 0061. The init is asked for ONE thing — run this at boot — and everything else
|
||||
# novox/hq ADR 0005. The init is asked for ONE thing — run this at boot — and everything else
|
||||
# lives here, in a script that can be tested. Whether to restart, how long to wait, when to give
|
||||
# up, when to roll back: all of it is policy, and policy in a unit file can only be read and
|
||||
# hoped for.
|
||||
@@ -109,7 +109,7 @@ while :; do
|
||||
case "$status" in
|
||||
0)
|
||||
# Exited cleanly. That is how the host stands aside for a new binary after an
|
||||
# upgrade (novox/hq ADR 0057) — so loop and run whatever is now on disk.
|
||||
# upgrade (novox/hq ADR 0005) — so loop and run whatever is now on disk.
|
||||
#
|
||||
# Deliberately NOT counted, and this is the whole reason the counter is
|
||||
# incremented here rather than before the start: counting attempts meant a host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Put the host back on the last version that worked.
|
||||
#
|
||||
# novox/hq ADR 0059. This runs when nox-mesh-host will not start, so it shares no code with it
|
||||
# novox/hq ADR 0005. This runs when nox-mesh-host will not start, so it shares no code with it
|
||||
# and calls none of it: a binary that cannot start cannot be its own recovery. POSIX sh, no
|
||||
# bashisms, nothing that has to be installed.
|
||||
#
|
||||
@@ -60,6 +60,6 @@ if ! pacman -U --noconfirm "$PKG"; then
|
||||
fi
|
||||
|
||||
# Deliberately does NOT start anything. The launcher called this and will exec the host next,
|
||||
# so starting it here would run two. novox/hq ADR 0061 moved that responsibility; this script
|
||||
# so starting it here would run two. novox/hq ADR 0005 moved that responsibility; this script
|
||||
# installs a version and says so, and nothing else.
|
||||
say "rolled back to $VERSION. the launcher will start it."
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/sbin/openrc-run
|
||||
# The Alpine equivalent of the systemd unit beside this. Four lines of the same two facts:
|
||||
# run the launcher, and bring it back if it dies. Everything else is in the launcher, which is
|
||||
# what makes a second init transcription rather than a port (novox/hq ADR 0061).
|
||||
# what makes a second init transcription rather than a port (novox/hq ADR 0005).
|
||||
name="nox-mesh-host"
|
||||
command="/usr/lib/nox-mesh-host/launch"
|
||||
supervisor="supervise-daemon"
|
||||
|
||||
@@ -3,7 +3,7 @@ Description=Novox Mesh node host
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
# One line of policy: run the launcher at boot (novox/hq ADR 0061). Restarting the host,
|
||||
# One line of policy: run the launcher at boot (novox/hq ADR 0005). Restarting the host,
|
||||
# backing off, giving up and rolling back are all the launcher's, where they can be tested.
|
||||
# Restart= here is a backstop for the launcher itself being killed, not the mechanism.
|
||||
[Service]
|
||||
|
||||
@@ -47,7 +47,7 @@ touch "$MESH_HOST_PKG_CACHE/nox-mesh-host-1.4.2-1-x86_64.pkg.tar.zst"
|
||||
check "installs the known-good version" "pacman is asked to install the cached package" \
|
||||
"$(grep -c 'nox-mesh-host-1.4.2' "$MESH_HOST_STATE_DIR/pacman.calls" 2>/dev/null || echo 0)" "1"
|
||||
# It installs and stops. The launcher execs the host next, and starting it here would run two
|
||||
# (novox/hq ADR 0061).
|
||||
# (novox/hq ADR 0005).
|
||||
check "does not start anything itself" "the launcher owns starting" \
|
||||
"$([ -f "$MESH_HOST_STATE_DIR/systemctl.calls" ] && echo started || echo not-started)" "not-started"
|
||||
check "records that it rolled back" "the attempted marker holds the version" \
|
||||
|
||||
Reference in New Issue
Block a user