Files
Maven/docs/plans/25-the-two-boot-paths.md
T
claude 661b5c1099 the audit write-ups, so every agent starts with them (V-638)
A repo-wide sweep on 06-08-2026 at 06c1cf2. Three docs, three tasks.

docs/plans/24-no-deadline-on-the-turn-path.md (V-638). Nothing between a
mavweb handler and llama-server can be cancelled, and one hop has a timeout.
Replier takes no context, the ipc client sets no conn deadline and checks ctx
once, and the ipc server dispatches under Background. Four commits, and the
pattern to copy is already in internal/voice/client.go:101.

docs/plans/25-the-two-boot-paths.md (V-639). The passkey-unlock path starts
seven workers outside the WaitGroup that shutdown waits on, shadows that
WaitGroup at main.go:529, and builds a daemonAPI with no nexus and no
getMCPServers. Latent, because db_key_env means the box boots unlocked.

docs/evals/2026-08-06-routing-trajectory.md (V-464). The deterministic path
and the cascade now score the same 69/91, and the cascade has not been
re-measured since V-626 and V-627. Either the model still earns its place or
it is costing 1.17s a turn for nothing. Dated, so it is not edited later.

Committed with --no-verify, on the owner's instruction of 06-08-2026. The
pre-commit hook refuses master and the alternative was three PRs for three
markdown files. Markdown is already exempt from the size cap for the same
reason: docs land as one batch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:41:03 +04:00

3.5 KiB

The two boot paths have drifted

Last verified: 06-08-2026 @ 06c1cf2

V-639. Reads with docs/operations.md.

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 Added and Doned 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.