100 lines
4.3 KiB
Markdown
100 lines
4.3 KiB
Markdown
# The two boot paths have drifted
|
|
|
|
Last verified: 06-08-2026 @ 69d0f5e
|
|
|
|
V-639. Reads with `docs/operations.md`.
|
|
|
|
## What landed
|
|
|
|
`cmd/mavend/boot.go`. `newDaemonAPI(deps)` builds the CoreAPI with every field
|
|
set, and `startBackground(ctx, &wg, deps)` starts the voice server and every
|
|
worker through `goWorker`. `backgroundWorkers(deps)` is the pure list behind it,
|
|
so a test can compare the set without standing a daemon up. Both paths in
|
|
`run()` now read `coreAPI = newDaemonAPI(depsNow())` and one
|
|
`startBackground(...)`, where `depsNow` reads whatever the current path wired.
|
|
|
|
The shadowed `wg` is gone. Four tests in `cmd/mavend/boot_test.go`. Every
|
|
`daemonAPI` field is set on a fully wired deployment. The handler gets the API
|
|
it was built with. The worker set is asserted by name, at the full set and at
|
|
the floor.
|
|
|
|
Still by hand: unlock a locked box by passkey, ask something that needs Nexus,
|
|
and check `/tools` lists the MCP servers.
|
|
|
|
## What is wrong
|
|
|
|
`run()` in `cmd/mavend/main.go` brings the daemon up two ways. A box with a key in the
|
|
environment starts unlocked and wires everything at lines 280 to 621. A box without one
|
|
starts locked. It wires the same things again inside the unlock closure, at lines 500 to
|
|
579, after a passkey assertion.
|
|
|
|
The two lists have drifted apart. Three ways.
|
|
|
|
**Seven workers start untracked.** The unlocked path puts every one through
|
|
`goWorker(&wg, ...)`, so `waitWorkers` at line 637 can wait for them. The unlock path
|
|
starts `tl.run`, `factWorker`, `evalWorker`, `feedWkr`, `crawlWkr`, `mcp.run` and
|
|
`home.run` as bare `go func()`. Nothing waits for any of them.
|
|
|
|
That is the shutdown bug the code already documents at lines 631 to 636, reintroduced on
|
|
the other path. The comment there records what it cost the first time. `run()` never
|
|
returned, so `defer st.Close()` never sealed the database. The deployed ciphertext was
|
|
eleven days stale before anyone noticed.
|
|
|
|
**A shadowed WaitGroup hides it.** Line 529 declares `var wg sync.WaitGroup` inside the
|
|
`if voiceW != nil` block, shadowing the one from line 359. It is `Add`ed and `Done`d and
|
|
never waited. Reading the block, the voice server looks tracked. It is not.
|
|
|
|
**Two `daemonAPI` fields are never set.** The unlocked path fills `nexus` at line 295 and
|
|
`getMCPServers` at line 305. The unlock path fills neither. So after a passkey unlock,
|
|
`ResolveEntity` answers `ErrNotImplemented` with a `nexus` block configured, and
|
|
`MCPServers` answers empty with an `mcp` block configured.
|
|
|
|
The second is the worse one. Empty is not a degraded answer, it is a wrong answer, and
|
|
`/tools` renders it as "not configured".
|
|
|
|
## Why it drifted
|
|
|
|
`wireTelegramIntake` was added to both paths on 06-08-2026 (V-637) and it does use the
|
|
outer `wg`, at line 519. So the newest line on that path is correct and the older ones
|
|
around it are not. The path gets touched one line at a time and is never read whole.
|
|
|
|
The shape of `cmd/mavend` is what allows that. It is 155 files and 9,551 lines of code.
|
|
Six things live in it with no seam between them:
|
|
|
|
- the handler
|
|
- the action dispatch
|
|
- the 19 query sources
|
|
- the wiring functions
|
|
- the six background workers
|
|
- these two boot paths
|
|
|
|
Nothing in the package makes the divergence visible.
|
|
|
|
## The fix
|
|
|
|
Make the two paths call one function instead of listing the same wiring twice.
|
|
|
|
One `startBackground(ctx, &wg, deps)` that takes what it needs and starts every worker
|
|
through `goWorker`. One `newDaemonAPI(deps)` that fills every field, including `nexus` and
|
|
`getMCPServers`, so a field added later cannot reach one path and miss the other. Both
|
|
call sites then read as one call each, and a future addition has one place to go.
|
|
|
|
Delete the shadowed `wg` at line 529 as part of it.
|
|
|
|
## How it is judged
|
|
|
|
`make test` stays green.
|
|
|
|
The regression that matters is a test asserting the two paths wire the same set. Compare
|
|
the constructed `daemonAPI` field by field, and assert the worker count started under the
|
|
outer `wg` matches. Without that, this drifts again the next time a wiring line is added.
|
|
|
|
Then confirm on a locked box: unlock by passkey, ask something that needs Nexus, and check
|
|
`/tools` lists the MCP servers. Both answer wrongly today.
|
|
|
|
## Priority
|
|
|
|
Latent, not live. `deploy/mavend.json` sets `db_key_env`, so homesrv boots unlocked and
|
|
takes the correct path. This bites the locked deployment that `docs/operations.md`
|
|
describes, and it bites silently.
|