0062: a host may be episodic; 0060's Android gap closed

0060 named the gap and did not close it: everywhere else an init runs the
launcher at boot, and Android grants neither an init to register with nor
anything worth supervising, because a supervisor would be killed alongside what
it supervises.

Closed by narrowing what is required rather than building something. A host is
resident or episodic, and both are hosts. Being killed by the platform is
disconnection, which 0036 already made ordinary -- and every mechanism an
episodic host needs already exists because it was built for laptops that close.

A partial host can join a mesh and cannot be the first node, since every
bootstrap step is a shape it refuses. Its bundle says so.

Two consequences that are easy to miss: last-heard-from means much less on an
episodic host, so a healthy phone reads as a dead server unless the reader
knows which kind it is; and a declaration may take a long time to land, which
makes 0058's outstanding-versus-failed distinction load-bearing.

Still open, and in that order: what an Android node is FOR, and only then how
it is started.
This commit is contained in:
2026-08-28 01:24:07 +02:00
parent f1b1cd9aa0
commit ba0d01788e
3 changed files with 149 additions and 0 deletions
@@ -14,6 +14,7 @@ decisions:
- 02-DECISIONS/0058-delivery-ends-in-a-declaration.md
- 02-DECISIONS/0060-the-host-is-built-per-operating-system.md
- 02-DECISIONS/0061-the-host-asks-an-init-for-start-and-restart.md
- 02-DECISIONS/0062-a-host-may-be-episodic.md
---
# The node lifecycle
@@ -189,6 +190,43 @@ used months later on node two.
---
## Two kinds of host
Everything above assumes a machine with an init that runs the host at boot. Not every machine
has one ([ADR 0062](../../02-DECISIONS/0062-a-host-may-be-episodic.md)).
| | **resident** | **episodic** |
|---|---|---|
| examples | Alpine, Arch | Android |
| started by | an init, at boot | whatever the platform allows |
| supervised by | the launcher | nothing — the platform decides when it runs |
| the link | held open | opened while it runs |
| being stopped | shutdown, or a failure | **ordinary** |
| shapes | all six | `file`, `directory`, `action` |
| can be the first node | yes | **no** |
**An episodic host being killed is disconnection, not failure.** That is
[ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md) doing the work it was written
for: reachability is state, not class. Everything the design already does for a laptop that
closes — an authoritative local store, reconcile on start, *last heard from* reported without an
alarm — is what an episodic host needs, at a shorter period.
**It cannot be the first node**, and that is not a limitation to work around. Every step of
raising a substrate is a `package`, a `container` or an `action` against one, and a partial host
refuses the first two. So `mesh-host-android bundle` returns a file that says so rather than an
empty placeholder waiting to be filled in.
**Two things this changes for anything reading the mesh.** *Last heard from* is a much weaker
signal on an episodic host — a healthy phone looks like a dead server — so a reader has to know
which kind it is looking at. And a declaration may take a long time to land, which makes
[ADR 0058](../../02-DECISIONS/0058-delivery-ends-in-a-declaration.md)'s separation of
*outstanding* from *failed* load-bearing rather than tidy.
**Still open:** how an episodic host is started in practice — an APK with a foreground service,
or Termux with its boot addon — and, first, **what an Android node is for.** A device that can
write files and run commands is not a workload host; it is a presence, or somewhere an agent
runs. Building the start mechanism before deciding that would be building it for nobody.
## Adoption: what happens to what is already there
Adoption is not a state. It is what the **first apply** does when it is told to own something a