Skip to content

test(cli): restore coverage deleted under a false justification - #107

Merged
V3RON merged 2 commits into
fix/0003-13-http-mcp-parityfrom
fix/0003-14-cli-coverage
Sep 4, 2026
Merged

test(cli): restore coverage deleted under a false justification#107
V3RON merged 2 commits into
fix/0003-13-http-mcp-parityfrom
fix/0003-14-cli-coverage

Conversation

@V3RON

@V3RON V3RON commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Stacked on #106. The last finding from the adversarial review.

ADR §12 legitimately says frontends are now serialization and one smoke test each suffices, and most of the CLI suite's deletion was right. But the test file justified specific deletions by claiming lease-lost/device-health push parsing and process-lifecycle behaviour were "covered once at daemon/dispatcher.test.ts". That is false — the dispatcher knows nothing about CLI process lifecycle or stderr rendering; its tests cover parsing, roles, ownership, error codes, the download clamp and startup parking.

So these behaviours had no coverage anywhere, and each is genuinely the CLI's own logic — exactly what §12 says to keep:

  • A lost lease exits 14 and does not re-release (asking again would only raise UNKNOWN_LEASE).
  • The own-lease-id filter on lease-lost pushes. This matters more than it looks: since pushes became owner-routed, a connection can receive a lease-lost for a lease it does not hold, and the CLI must not treat that as fatal. The test fires a push for a different lease id and asserts it is ignored, then drives the normal release path to exit 0.
  • {push: …} stderr line shapes for progress and device health.
  • --full / --no-waitfull / noWait mapping, including that absent flags omit full rather than sending false.
  • mcp lazy-load and startup-failure reporting — a load rejection surfaces as the CLI's structured stderr error rather than an uncaught throw.
  • daemon logs with the daemon dead — an explicit ADR §11 requirement, previously unproven. The test asserts no connection is ever attempted.

No new test exposed a bug; all passed against current code. The false justification comment is rewritten to say what is actually covered where, including that --bind-pid/parent-watch races survive only in the real-hardware e2e lanes.

One thing worth knowing, found while writing the push test: DeviceUnhealthyPush's public type deliberately drops the wire schema's reason: z.literal("crashed") as a constant carrying no information, so that stderr line never contains reason. Not a bug, but surprising if you expect it.

ADR 0003 §12's per-frontend smoke-test policy is correct, but
src/cli/index.test.ts:93-95 claimed lease-lost/device-health push
parsing and --bind-pid/parent-watch termination were "covered once at
daemon/dispatcher.test.ts" -- the dispatcher has no notion of CLI
process lifecycle or stderr rendering, so that behaviour had no
coverage anywhere after the ADR 0003 deletions (adversarial review
S12).

Adds tests that pin the CLI's own logic:
- a lost lease exits 14 and does not attempt to re-release it
- the own-lease-id filter on lease-lost pushes (a push for a lease
  this connection does not hold must not be treated as fatal)
- {push:...} stderr line shapes for progress/device-unhealthy/
  device-recovered
- --full/--no-wait -> full/noWait input mapping, including the
  omit-when-false case
- `mcp` command's lazy module load (not loaded for other commands)
  and its startup-failure reporting as a structured error
- `daemon logs` works without any connection attempt, per ADR §11

Also corrects the false justification comment to describe what is
actually covered where, and notes that --bind-pid/parent-watch
termination races remain untested outside the e2e lanes.
…ence

daemon.stop is accepted during startup, and an auxiliary frontend that fails to
bind asks for a stop of its own, so stop() can run to completion while convergence
is still in flight. start() then resumed and armed live machinery anyway: it
subscribed to a disposed fact bus, scheduled a heartbeat tick on a dead daemon,
started the health monitor, and emitted daemon.started after daemon.stopping -- a
fact that is not true when emitted, which agent-rules/events.md rule 3 forbids.

Two earlier fixes in this stack worked around the symptom from the outside (main.ts
defers its stop until start() settles, and stopAuxiliary now awaits the gateway).
This closes it at the root; those guards stay correct and become redundant rather
than wrong.
@V3RON
V3RON force-pushed the fix/0003-14-cli-coverage branch from 6eed8cf to 9eaec2c Compare September 4, 2026 06:25
@V3RON
V3RON merged commit a9f6aac into main Sep 4, 2026
7 of 9 checks passed
V3RON added a commit that referenced this pull request Sep 4, 2026
…e race (#108)

**Stacked on #107.** A regression this stack introduced, caught by a
verification pass over the earlier fixes.

`gatewayStarted` is settled only from inside `onSocketClaimed`'s
handler, and `DaemonServer.start()` fires that callback only once the
socket claim **and** the admin-secret write have succeeded. So a daemon
that loses the start race rejects with `DaemonAlreadyRunningError`
without the callback ever running — nothing settles `gatewayStarted`,
and the `Promise.allSettled` join added by the bind-failure fix waits on
it **forever**.

`startDaemon()` therefore hung: no rejection, no "Daemon failed to
start", no non-zero exit code. Both `main` and the pre-fix stack awaited
`daemon.start()` alone and rejected immediately, so this is a regression
introduced by that fix, not a pre-existing gap. It is reachable by
racing `simlock daemon start`, and by the CLI's own auto-launch — a
common path, not an exotic one, whenever HTTP is enabled.

The fix releases `gatewayStarted` on the `daemon.start()` failure path.
A `socketClaimed` guard keeps it from resolving early while a *claimed*
daemon's gateway is still binding, so a genuine bind failure is still
reported — the behaviour the previous PR added stays intact.

The regression test races two `startDaemon()` calls on one data
directory with distinct HTTP ports, so the only thing that can fail is
the socket claim, and asserts the second **rejects** rather than
hanging. Verified by stashing the fix: without it the test reports
`hung` instead of `rejected`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant