Unify trunk on main: initialization → main #3

Merged
jschoubben merged 58 commits from initialization into main 2026-09-05 01:13:33 +00:00
25 changed files with 70 additions and 70 deletions
Showing only changes of commit ee2648188d - Show all commits
+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
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.
+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
// 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.
//
+7 -7
View File
@@ -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
+6 -6
View File
@@ -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:
+4 -4
View File
@@ -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]))
}
+8 -8
View File
@@ -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 == "" {
+3 -3
View File
@@ -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",
+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).
// 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
+3 -3
View File
@@ -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 == "" {
+1 -1
View File
@@ -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{}
+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
// 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.
+1 -1
View File
@@ -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.
//
+2 -2
View File
@@ -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) {
+4 -4
View File
@@ -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-- {
+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
// 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.
+3 -3
View File
@@ -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,
+1 -1
View File
@@ -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"},
+2 -2
View File
@@ -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 {
+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
// 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")
+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
# 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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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 -1
View File
@@ -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"
+1 -1
View File
@@ -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]
+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" \
"$(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" \