Two binaries, one law
The smallest honest fleet: an intake binary and a worker binary that share one topic over a unix socket, plus the plan that names both instances, routes the topic between them, and states a claim across the pair. Each binary's build emits a topology artifact; `hale fleet check` composes the artifacts and verifies the plan against what the binaries actually are — not what a diagram says they are.
The first binary: an intake that queues three tasks onto `Work` and connects the topic to a unix socket. Its whole contract with the outside world is the topic declaration and the binding.
type Task { kind: Int; body: String; }
topic Work { payload: Task; }
locus Intake {
params { queued: Int = 0; }
bus { publish Work; }
run() {
let mut i = 0;
while i < 3 {
Work <- Task { kind: i % 2, body: "job" };
self.queued = self.queued + 1;
i = i + 1;
}
println("intake queued ", self.queued);
}
}
main locus IntakeApp {
params { in_: Intake = Intake { }; }
bindings { Work: unix("/tmp/hale-fleet-demo.sock", role: connect); }
run() { }
}
fn main() { IntakeApp { }; }The second binary: a supervised executor listening on the same socket. The two programs share nothing but the `Work` payload shape and the wire — separate builds, separate processes, separate failures.
type Task { kind: Int; body: String; }
topic Work { payload: Task; }
@supervised locus Executor {
params { done: Int = 0; }
bus { subscribe Work as perform; }
fn perform(t: Task) {
self.done = self.done + 1;
println("executor did ", t.body, " kind ", t.kind);
}
}
main locus WorkerApp {
params { ex: Executor = Executor { }; }
bindings { Work: unix("/tmp/hale-fleet-demo.sock", role: listen); }
on_failure(e: Executor, err: ClosureViolation) { restart (e); }
run() { }
}
fn main() { WorkerApp { }; }The fleet plan is deployment describing itself: exact instances, each pointing at the topology artifact its build produced, the route that connects them, and a fleet-level claim — every task must have a subscribed executor. `hale fleet check` composes the real artifacts and holds the plan to them:
{
"schema": "1.1",
"name": "demo",
"instances": [
{
"id": "intake-0",
"artifact": "intake.topology.json"
},
{
"id": "worker-0",
"artifact": "worker.topology.json"
}
],
"routes": [
{
"id": "work",
"publishers": [
{
"instance": "intake-0",
"topic": "Work"
}
],
"subscribers": [
{
"instance": "worker-0",
"topic": "Work"
}
],
"transport": "unix:///tmp/hale-fleet-demo.sock"
}
],
"groups": {
"executors": {
"instances": [
"worker-0"
]
}
},
"claims": [
{
"name": "every_task_has_a_worker",
"require_subscribes": {
"group": "executors",
"subject": "Work"
}
}
]
}$ hale check --dump-topology … && hale fleet check plan.json
ok: fleet `demo` composed — 2 instance(s), 1 route(s), fleet_shape_hash 8839828fa54935a3Now the reason this layer exists. Ship a worker build whose executor stopped subscribing — the binary still typechecks, both artifacts are individually valid, and only the composition can see the hole:
A worker build that quietly stopped listening. (worker.hl)
- bus { subscribe Work as perform; }
+ bus { }$ hale fleet check plan.json
route `work`: instance `worker-0` is named as a subscriber of `Work` (subject `Work`), but nothing in that component subscribes it — declaring a topic is not using it