Implementation: Fleetd.main's boot composition now lives in FleetdAssembly (fleetd #612)
Figure 2's intro and caption said main assembles the graph; that has not been true since #620. Adds the seam's contract: FleetdAssembly/FleetdRuntime/ ResourcePorts, why there is deliberately no inert default, and the two traps a boot test hits — the late attachApp, and the one-FakeHerdr fixture that cannot tell leadAgents() from memberAgents() when memberHerdrSocket is unset.
+44
-6
@@ -306,7 +306,10 @@ loop (`rest/FleetApp.java:143-158`). **There is no `/events` route**, and the wo
|
||||
|
||||
## Bootstrap wiring
|
||||
|
||||
`Fleetd.main` assembles the object graph in dependency order, then starts both faces.
|
||||
`Fleetd.main` loads the config, reports it and runs `cfg.validateAll()`. Everything after that —
|
||||
the whole object graph, in dependency order, then starting both faces — lives in
|
||||
`FleetdAssembly.assembleAndStart(AssemblyInputs, ResourcePorts)` (fleetd #612). `main` is now three
|
||||
statements and one call.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -324,11 +327,46 @@ flowchart LR
|
||||
mcp --> app["rest.FleetApp<br/>(start Javalin)"]
|
||||
```
|
||||
|
||||
*Figure 2 — startup wiring in `Fleetd.main`. The guard asserts the primary's own environment is
|
||||
clean before anything else; the two adapters plug into one `CompositePeerLauncher`;
|
||||
`reapOrphanWorkers()` clears stale panes left by a prior daemon restart; `CallerResolver` is built
|
||||
last among the core services because it needs the live lead and architect bindings that everything
|
||||
above it produces.*
|
||||
*Figure 2 — startup wiring, now inside `FleetdAssembly.assembleAndStart`. The guard asserts the
|
||||
primary's own environment is clean before anything else; the two adapters plug into one
|
||||
`CompositePeerLauncher`; `reapOrphanWorkers()` clears stale panes left by a prior daemon restart;
|
||||
`CallerResolver` is built last among the core services because it needs the live lead and architect
|
||||
bindings that everything above it produces.*
|
||||
|
||||
### Why the assembly is a separate method
|
||||
|
||||
Nothing used to call `Fleetd.main` far enough to observe what it passed. So any injected wiring
|
||||
could be swapped for its inert variant — `X.none()`, `_ -> null`, `() -> Map.of()` — and the whole
|
||||
suite stayed green. That is not a theory: #602/#606 shipped exactly that defect, and a later sweep
|
||||
found 16 more call sites with the same hole.
|
||||
|
||||
Three types carry the fix:
|
||||
|
||||
- **`FleetdAssembly.assembleAndStart`** — the same statements `main` used to run inline, in the same
|
||||
order. Construction and start order is preserved deliberately; it was *not* rebuilt into
|
||||
"construct everything, then start everything", because that would change boot timing.
|
||||
- **`FleetdRuntime`** — owns the assembled objects and their single ordered `close()`. Its
|
||||
package-private accessors return **the identical instances the running daemon uses**, never a
|
||||
copy. A test that inspected a snapshot built alongside the real objects could pass while
|
||||
production silently received something else, which is the defect being closed.
|
||||
- **`ResourcePorts`** — every boot-time side effect a real daemon must do for real and a test must
|
||||
not: read the environment, connect a herdr client, open a broker, read a clock, start a scheduler,
|
||||
register the shutdown hook, bind the HTTP server. `ResourcePorts.system()` is the one production
|
||||
implementation.
|
||||
|
||||
**There is deliberately no `ResourcePorts.none()` and no smaller overload of `assembleAndStart`.**
|
||||
Adding an inert default, even for tests, would hand a future edit the exact compiling substitute
|
||||
this work exists to rule out. A test writes its own fake and owns that choice.
|
||||
|
||||
Two consequences worth knowing before you write a boot test:
|
||||
|
||||
- One statement could not move without reordering startup. `main` registered its shutdown hook
|
||||
*before* building the Javalin app, so `FleetdRuntime` is built first and the app attached
|
||||
afterwards via `attachApp`, still before HTTP listens. See that field's javadoc.
|
||||
- A wiring that differs between the lead and member herdr daemons cannot be pinned by a fixture with
|
||||
one `FakeHerdr`. When `memberHerdrSocket` is unset the assembly falls back to `memberHerdr = herdr`,
|
||||
so `router.leadAgents()` and `router.memberAgents()` become the same object and a test cannot tell
|
||||
them apart. Configure two sockets. This cost two rounds of rework on #612 alone.
|
||||
|
||||
## Flow: forward rendezvous (`fleet_send` → `fleet_reply`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user