Repoint ADR references after HQ consolidated 65 records to 23

96 comments across the two repos named records that no longer exist. Each now
points at the consolidated record that holds its reasoning -- ADR 0034 (a test
defends a decision) is 0017, the eight host records are 0005, the four lab
records are 0016.

Worth noting for next time: these are references from outside HQ, so renumbering
there is not free. It cost 38 files here.
This commit is contained in:
2026-08-28 23:33:44 +02:00
parent ebba16ce4a
commit ee2648188d
25 changed files with 70 additions and 70 deletions
+3 -3
View File
@@ -9,7 +9,7 @@ mesh-host profile
``` ```
That is the whole installation. One statically linked binary, nothing else present, no runtime 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 ## 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 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 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 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. 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 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. 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 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 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. the point of detecting — they assert that detection tells the truth about whatever is there.
+6 -6
View File
@@ -31,7 +31,7 @@ import (
// version is stamped at build time. Unset in a development build, and said so rather than // version is stamped at build time. Unset in a development build, and said so rather than
// defaulted to something that looks like a release. // defaulted to something that looks like a release.
// builtFor names the operating system this host was built for, set at link time // 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. // machine, rather than guessing and calling a package manager that is not there.
var builtFor = "" var builtFor = ""
@@ -161,7 +161,7 @@ func run(ctx context.Context, command string, opts options) error {
return fmt.Errorf("reading the declaration: %w", err) return fmt.Errorf("reading the declaration: %w", err)
} }
// ParseTrusted: a file handed to the host by someone already running it as root is // 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 // 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 // them an action would buy nothing and would make an action untestable except by
// rebuilding the bundle. // rebuilding the bundle.
@@ -172,7 +172,7 @@ func run(ctx context.Context, command string, opts options) error {
return runApply(ctx, opts, d, opts.file) return runApply(ctx, opts, d, opts.file)
case "reconcile": 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 // 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 // the only source — which is a stage, not a design, and saying so beats implying the
// other source exists. // other source exists.
@@ -275,7 +275,7 @@ func writeInventory(inv inventory.Inventory) {
writeProfile(inv.Profile) writeProfile(inv.Profile)
// Printed last and never hidden. An inventory that quietly omits what it could not read // 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 { if len(inv.Unreadable) > 0 {
fmt.Println("\ncould not read:") fmt.Println("\ncould not read:")
for _, u := range inv.Unreadable { 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. // 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 // 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 { if err := system.Check(sys, d); err != nil {
return err 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 // 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 // health — a disconnected node is ordinary, and a resource that fails is the machine's
// problem rather than the binary's. // problem rather than the binary's.
// //
+7 -7
View File
@@ -3,10 +3,10 @@
// Three properties, each following a recorded decision, and each of them the difference // Three properties, each following a recorded decision, and each of them the difference
// between this and a script that writes files: // 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. // apply that reports success is the mesh's most expensive shape.
// - Every applier READS BACK. Setting a value is not evidence the value took. // - 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. // leaves the machine in whatever state it reached, and nothing must claim otherwise.
package apply package apply
@@ -29,7 +29,7 @@ import (
) )
// Runner executes a command. The real one is used everywhere outside unit tests; behaviour // 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 type Runner = system.Runner
// Outcome is what happened to one resource. // 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. // what it actually did.
// //
// Only ever called for something in the store, which is what bounds it: the host is // 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 // 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 // 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 // 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 // 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). // not becoming a second package manager).
func applyPackage(ctx context.Context, sys system.System, r *declaration.Package, run Runner) (Outcome, error) { func applyPackage(ctx context.Context, sys system.System, r *declaration.Package, run Runner) (Outcome, error) {
out := begin(r) 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 // 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 // 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 // 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) { func applyAction(ctx context.Context, r *declaration.Action, run Runner) (Outcome, error) {
out := begin(r) 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 // 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 // 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 // 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 // interface. `docker info --format {{.ServerVersion}}` fails on podman — the field does not
+6 -6
View File
@@ -13,7 +13,7 @@ import (
"github.com/novox/mesh-host/internal/system" "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 { func parse(t *testing.T, raw string) *declaration.Declaration {
t.Helper() t.Helper()
@@ -87,7 +87,7 @@ func TestADriftedMachineIsReturned(t *testing.T) {
} }
func TestADroppedResourceIsRemoved(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. // declared. Removing a line from a declaration is an act with an effect.
dir := t.TempDir() dir := t.TempDir()
keep := filepath.Join(dir, "keep.conf") keep := filepath.Join(dir, "keep.conf")
@@ -178,7 +178,7 @@ func TestARenameToTheSamePathDoesNotDeleteTheNewFile(t *testing.T) {
} }
func TestAFailedStepFailsTheApply(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. // whatever state the apply reached and the only honest thing to hand back is that list.
dir := t.TempDir() dir := t.TempDir()
blocker := filepath.Join(dir, "blocker") blocker := filepath.Join(dir, "blocker")
@@ -214,7 +214,7 @@ func TestAFailedStepFailsTheApply(t *testing.T) {
} }
func TestNothingIsRecordedUntilItWorked(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. // and inherits none of the authority of having happened.
dir := t.TempDir() dir := t.TempDir()
blocker := filepath.Join(dir, "blocker") 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 { func parseTrusted(t *testing.T, raw string) *declaration.Declaration {
t.Helper() 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) { func TestTheRuntimeProbeIsPerRuntime(t *testing.T) {
// Verified against a real podman 6.1.0 before this was written: // Verified against a real podman 6.1.0 before this was written:
+4 -4
View File
@@ -1,11 +1,11 @@
// Package bundle is the declaration the host carries. // 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 // 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. // 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 // 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. // arrive with it.
package bundle package bundle
@@ -20,7 +20,7 @@ import (
// One bundle per operating system, because its CONTENTS are per system even though its // 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 // 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. // an arch host never reads the alpine bundle.
// //
// A host whose bundle is only comments carries nothing, and says so rather than applying // 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 return nil, ErrEmpty
} }
// ParseTrusted: the bundle arrives with the binary, so it may carry actions the link may // 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. // happens before there is any mesh to ask for one.
return declaration.ParseTrusted(stripComments(locks[system])) return declaration.ParseTrusted(stripComments(locks[system]))
} }
+8 -8
View File
@@ -3,7 +3,7 @@
// Data, never instructions. The vocabulary is finite, versioned, and anything outside it // 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 // 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 // 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 package declaration
import ( import (
@@ -162,7 +162,7 @@ type Container struct {
ID string `json:"id"` ID string `json:"id"`
Type Type `json:"type"` Type Type `json:"type"`
Name string `json:"name"` 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"` Image string `json:"image"`
Env map[string]string `json:"env,omitempty"` Env map[string]string `json:"env,omitempty"`
Ports []string `json:"ports,omitempty"` Ports []string `json:"ports,omitempty"`
@@ -189,7 +189,7 @@ type Action struct {
Command []string `json:"command"` Command []string `json:"command"`
// Verify is not optional and is not a courtesy. It is the read-back AND the idempotency // 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 // 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"` Verify []string `json:"verify"`
// In names a container to run inside. Empty means the machine itself. // In names a container to run inside. Empty means the machine itself.
In string `json:"in,omitempty"` In string `json:"in,omitempty"`
@@ -207,7 +207,7 @@ func (a *Action) Target() string {
} }
func (a *Action) validate(where string, allowActions bool) []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 { if !allowActions {
return []string{where + return []string{where +
": an action arrived over the link, and the link may not carry one. The host " + ": 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. // nothing to check against.
For string For string
// Resources, in the order they are applied. The host does not sort them: ordering is a // 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 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 // 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) } 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 // 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. // 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 // 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 // bundle built the binary; refusing them from the link buys the bound on what a compromised
// control plane can express. // control plane can express.
func ParseTrusted(raw []byte) (*Declaration, error) { return parse(raw, true) } 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. // 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 // 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. // that tag points at on the day the host happens to run.
func checkImage(where, image string) []string { func checkImage(where, image string) []string {
if image == "" { if image == "" {
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"testing" "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. // and the property it turns on is that unknown is REFUSED, never skipped.
func valid() string { func valid() string {
@@ -160,7 +160,7 @@ func TestAnEmptyDeclarationIsAMistake(t *testing.T) {
// --- the vocabulary the substrate bootstrap needs (novox/hq 07-the-substrate.md) --- // --- the vocabulary the substrate bootstrap needs (novox/hq 07-the-substrate.md) ---
func TestAnActionOverTheLinkIsRefused(t *testing.T) { 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 // run. This is the boundary the whole security argument rests on, so it is asserted
// directly rather than inferred from the type list. // directly rather than inferred from the type list.
raw := []byte(`{"declaration":1,"resources":[ raw := []byte(`{"declaration":1,"resources":[
@@ -195,7 +195,7 @@ func TestAnActionWithoutVerifyIsRefused(t *testing.T) {
} }
func TestAnImageMustBePinnedByDigest(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. // naming a tag pins nothing — it names whatever that tag points at on the day it runs.
for _, image := range []string{ for _, image := range []string{
"postgres:17", "postgres:17",
+2 -2
View File
@@ -3,7 +3,7 @@
// Reported upward and never asked downward (novox/hq 03-DESIGN/01-to-be/05-the-node-host.md). // 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, // 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 // nothing is derived from a file that says what the machine ought to be
// (novox/hq ADR 0035). // (novox/hq ADR 0018).
package inventory package inventory
import ( import (
@@ -39,7 +39,7 @@ type Inventory struct {
// ObservedAt is when this was read. An inventory with no timestamp cannot be told from a // 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 // 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"` ObservedAt time.Time `json:"observed_at"`
// Unreadable lists what could not be determined, and why. An absent field and a field that // Unreadable lists what could not be determined, and why. An absent field and a field that
+3 -3
View File
@@ -64,7 +64,7 @@ func TestMemoryIsReadOrReportedMissing(t *testing.T) {
} }
func TestTheInventorySaysWhenItWasTaken(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. // inventory that cannot be told from a stale one is missing the fact that matters.
before := time.Now().UTC() before := time.Now().UTC()
inv := Collect(context.Background(), nil, nil, time.Second) inv := Collect(context.Background(), nil, nil, time.Second)
@@ -79,7 +79,7 @@ func TestTheInventorySaysWhenItWasTaken(t *testing.T) {
func TestTheHostDoesNotNameTheNode(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 // 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) inv := Collect(context.Background(), nil, nil, time.Second)
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
@@ -91,7 +91,7 @@ func TestTheHostDoesNotNameTheNode(t *testing.T) {
// --- against this machine --------------------------------------------------------------- // --- against this machine ---------------------------------------------------------------
func TestAgainstThisMachine_inventoryIsTrue(t *testing.T) { 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) inv := Collect(context.Background(), nil, profile.Default(nil), 10*time.Second)
if inv.Machine == "" { if inv.Machine == "" {
+1 -1
View File
@@ -111,7 +111,7 @@ func firstLine(s string) string {
// privileged reports whether the host can change this machine at all. // 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 // 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. // here rather than in the definition of a node.
type privileged struct{} type privileged struct{}
+2 -2
View File
@@ -44,7 +44,7 @@ type Detector interface {
// Runner executes a command. Replaceable in tests for the pure-logic layer ONLY — every // 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 // 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 // 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) 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. // 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. // Detect runs every detector and collects the verdicts.
// //
// A detector that fails does not fail the profile. This is deliberately NOT the rule in // 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 // 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. // 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. // Aborting would replace one legible absence with total ignorance.
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"time" "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 // 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. // the system under detection asserts that the fake behaves as expected.
// //
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"time" "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 // 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 // machine, because a test that fakes the system under detection asserts only that the fake
// behaves as expected. // behaves as expected.
@@ -57,7 +57,7 @@ func TestEveryVerdictSaysHowItKnows(t *testing.T) {
} }
func TestDetectionSurvivesAFailingProbe(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 // 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. // replace one legible absence with total ignorance of the rest.
only := func(ctx context.Context, name string, args ...string) (string, error) { only := func(ctx context.Context, name string, args ...string) (string, error) {
+4 -4
View File
@@ -1,11 +1,11 @@
// Package store is what this node knows about itself, and it is authoritative while // Package store is what this node knows about itself, and it is authoritative while
// disconnected. // 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 // 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. // 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 // removes what it previously applied and is no longer declared, and it can only know that
// because it wrote it down. // because it wrote it down.
package store 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. // 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 // A record written up front restates the request in a new place and inherits none of the
// authority of having happened. // authority of having happened.
type Applied struct { 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 // 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 // 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 { func (s State) Orphans(declared map[string]bool) []Applied {
var out []Applied var out []Applied
for i := len(s.Resources) - 1; i >= 0; i-- { for i := len(s.Resources) - 1; i >= 0; i-- {
+3 -3
View File
@@ -13,7 +13,7 @@ import (
// way to run something — and refuses the other three. That is not a broken host: a declaration // 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 // 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 // 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: // What it cannot do, and why:
// //
@@ -24,13 +24,13 @@ import (
// and an unlocked bootloader. On a normal device nothing can register with it. // and an unlocked bootloader. On a normal device nothing can register with it.
// - **container** — no container runtime, and no kernel access to give one. // - **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 // 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 // without root, and nothing worth supervising, because a supervisor would be killed alongside
// what it supervises. // what it supervises.
// //
// So it runs when the platform allows and is killed when the platform wants the memory — and // 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 // 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 // while disconnected, reconcile already happens on start, and the mesh already reports *last
// heard from* rather than alarming on silence. // heard from* rather than alarming on silence.
+3 -3
View File
@@ -1,6 +1,6 @@
// Package system is the part of the host that differs between operating systems. // 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 // 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 // time. So they are not independent knobs — they are one implementation, named after the system
// it belongs to. // 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. // 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 // 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. // that looks configured and is not. The reason differs and the outcome does not.
func Check(s System, d *declaration.Declaration) error { func Check(s System, d *declaration.Declaration) error {
var problems []string var problems []string
@@ -116,7 +116,7 @@ func everyShape() []declaration.Type {
// portableShapes need only a filesystem and a way to run something. // 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 // 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 { func portableShapes() []declaration.Type {
return []declaration.Type{ return []declaration.Type{
declaration.TypeDirectory, declaration.TypeFile, declaration.TypeAction, declaration.TypeDirectory, declaration.TypeFile, declaration.TypeAction,
+1 -1
View File
@@ -238,7 +238,7 @@ func TestOpenRCBootStateComesFromTheRunlevel(t *testing.T) {
} }
func TestEachSystemUsesItsOwnCommands(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. // host must never reach for rc-service.
for _, tc := range []struct{ name, forbidden string }{ for _, tc := range []struct{ name, forbidden string }{
{"arch", "rc-service"}, {"arch", "rc-service"},
+2 -2
View File
@@ -1,6 +1,6 @@
// Package upgrade is how the host survives replacing itself. // 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 // - whether the executable this process started from has been replaced on disk, which is how
// it knows to stand aside for a new one; // 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 // 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 // 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) { func Current(path string) (Self, error) {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// //
// Against the real filesystem rather than a fake one. What is being tested is how the operating // 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 // 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) { func started(t *testing.T) (Self, string) {
t.Helper() t.Helper()
binary := filepath.Join(t.TempDir(), "mesh-host") binary := filepath.Join(t.TempDir(), "mesh-host")
+1 -1
View File
@@ -141,7 +141,7 @@ done
# These need the launcher to actually run as a supervisor rather than one iteration, so they do # These need the launcher to actually run as a supervisor rather than one iteration, so they do
# not set MESH_HOST_RUN_ONCE. # 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. # start it again — and must NOT count it, because it did not fail.
setup setup
unset MESH_HOST_RUN_ONCE unset MESH_HOST_RUN_ONCE
+2 -2
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# Supervise the host: start it, watch it, and decide what to do when it stops. # 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 # 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 # up, when to roll back: all of it is policy, and policy in a unit file can only be read and
# hoped for. # hoped for.
@@ -109,7 +109,7 @@ while :; do
case "$status" in case "$status" in
0) 0)
# Exited cleanly. That is how the host stands aside for a new binary after an # 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 # Deliberately NOT counted, and this is the whole reason the counter is
# incremented here rather than before the start: counting attempts meant a host # incremented here rather than before the start: counting attempts meant a host
+2 -2
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# Put the host back on the last version that worked. # 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 # 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. # bashisms, nothing that has to be installed.
# #
@@ -60,6 +60,6 @@ if ! pacman -U --noconfirm "$PKG"; then
fi fi
# Deliberately does NOT start anything. The launcher called this and will exec the host next, # 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. # installs a version and says so, and nothing else.
say "rolled back to $VERSION. the launcher will start it." say "rolled back to $VERSION. the launcher will start it."
+1 -1
View File
@@ -1,7 +1,7 @@
#!/sbin/openrc-run #!/sbin/openrc-run
# The Alpine equivalent of the systemd unit beside this. Four lines of the same two facts: # 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 # 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" name="nox-mesh-host"
command="/usr/lib/nox-mesh-host/launch" command="/usr/lib/nox-mesh-host/launch"
supervisor="supervise-daemon" supervisor="supervise-daemon"
+1 -1
View File
@@ -3,7 +3,7 @@ Description=Novox Mesh node host
After=network-online.target After=network-online.target
Wants=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. # 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. # Restart= here is a backstop for the launcher itself being killed, not the mechanism.
[Service] [Service]
+1 -1
View File
@@ -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" \ 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" "$(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 # 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" \ check "does not start anything itself" "the launcher owns starting" \
"$([ -f "$MESH_HOST_STATE_DIR/systemctl.calls" ] && echo started || echo not-started)" "not-started" "$([ -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" \ check "records that it rolled back" "the attempted marker holds the version" \