feat(devices): uninstall the agent on delete, prompt to force when unreachable (VAPP-55) #66

Merged
nrupard merged 2 commits from feat/VAPP-55-delete-uninstall-agent-prompt into main 2026-06-30 18:17:20 +02:00
Owner

What

Implements the delete-device UI half of the uninstall-on-delete flow (VAPP-55). Pairs with the server orchestration (VS-70, vervain-server PR #90) and the agent self-uninstall (VA-87, merged). The delete dialog now reflects that deleting uninstalls the agent, and prompts to proceed when the agent is unreachable instead of silently deleting or erroring.

Server fn

remove_device(device_id, force) returns a typed DeleteOutcome instead of Result<(), _>:

  • Deleted - agent uninstalled (if reachable) and record removed (ack ok).
  • AgentUnreachable - agent offline / ack timed out, nothing deleted (ack agent-unreachable).
  • UninstallFailed - agent reached but uninstall errored, nothing deleted (ack uninstall-failed).

AgentUnreachable / UninstallFailed are states the dialog reacts to, so they come back as Ok; genuine failures (not-authorized, db-error, ...) stay ServerFnError. The single-node removedevices now carries the top-level force boolean. Tokens are the kebab-case strings pinned in VS-70.

Dialog (device_detail.rs)

  • Name-confirm copy now states deleting uninstalls the agent from the machine.
  • On AgentUnreachable the dialog switches to a force-confirm step: "The agent is offline and can't be uninstalled remotely. Delete the device anyway? The agent will remain installed on the machine and must be removed manually." Its "Delete anyway" re-issues remove_device(.., force = true).
  • On UninstallFailed, a distinct retry message; the record is kept.
  • Reachable happy path unchanged (now with the agent uninstalled server-side).
  • A force_prompt signal drives the two steps; reset when the dialog opens. The name-confirm gate applies only to the first (non-force) attempt.

Acceptance criteria

  • The delete dialog states that deleting uninstalls the agent.
  • When the server reports the agent unreachable, the UI prompts to proceed (force) rather than silently deleting or erroring.
  • Confirming "Delete anyway" force-deletes the record; cancelling leaves it intact.
  • Reachable deletes complete as before (now with the agent uninstalled server-side).
  • just check passes (verified equivalently in the rust-builder image, see below).

Verification

In ghcr.io/niceguyit/rust-builder-glibc:v1.0.1-rust1.94-trixie:

  • cargo fmt --check clean.
  • cargo clippy --all-targets --features server -- -D warnings clean.
  • cargo check --target wasm32-unknown-unknown clean (client).
  • cargo check --features desktop clean (the desktop CI build triggers on src/**; just check does not compile this feature).
  • cargo test --features server --bin vervain-app -> 24 passed.

Sequencing

Merge after VS-70 (PR #90) merges and deploys to staging, so the dialog's "deleting uninstalls the agent" copy is truthful end-to-end. The server tolerates a UI that never sends force, and this UI tolerates the new tokens, so the two can land in either order without breaking, but the copy is only accurate once the server uninstalls.

#VAPP-55

## What Implements the delete-device UI half of the uninstall-on-delete flow (VAPP-55). Pairs with the server orchestration (VS-70, vervain-server PR #90) and the agent self-uninstall (VA-87, merged). The delete dialog now reflects that deleting uninstalls the agent, and prompts to proceed when the agent is unreachable instead of silently deleting or erroring. ## Server fn `remove_device(device_id, force)` returns a typed `DeleteOutcome` instead of `Result<(), _>`: - `Deleted` - agent uninstalled (if reachable) and record removed (ack `ok`). - `AgentUnreachable` - agent offline / ack timed out, nothing deleted (ack `agent-unreachable`). - `UninstallFailed` - agent reached but uninstall errored, nothing deleted (ack `uninstall-failed`). `AgentUnreachable` / `UninstallFailed` are states the dialog reacts to, so they come back as `Ok`; genuine failures (`not-authorized`, `db-error`, ...) stay `ServerFnError`. The single-node `removedevices` now carries the top-level `force` boolean. Tokens are the kebab-case strings pinned in VS-70. ## Dialog (device_detail.rs) - Name-confirm copy now states deleting uninstalls the agent from the machine. - On `AgentUnreachable` the dialog switches to a force-confirm step: "The agent is offline and can't be uninstalled remotely. Delete the device anyway? The agent will remain installed on the machine and must be removed manually." Its "Delete anyway" re-issues `remove_device(.., force = true)`. - On `UninstallFailed`, a distinct retry message; the record is kept. - Reachable happy path unchanged (now with the agent uninstalled server-side). - A `force_prompt` signal drives the two steps; reset when the dialog opens. The name-confirm gate applies only to the first (non-force) attempt. ## Acceptance criteria - [x] The delete dialog states that deleting uninstalls the agent. - [x] When the server reports the agent unreachable, the UI prompts to proceed (force) rather than silently deleting or erroring. - [x] Confirming "Delete anyway" force-deletes the record; cancelling leaves it intact. - [x] Reachable deletes complete as before (now with the agent uninstalled server-side). - [x] `just check` passes (verified equivalently in the rust-builder image, see below). ## Verification In `ghcr.io/niceguyit/rust-builder-glibc:v1.0.1-rust1.94-trixie`: - `cargo fmt --check` clean. - `cargo clippy --all-targets --features server -- -D warnings` clean. - `cargo check --target wasm32-unknown-unknown` clean (client). - `cargo check --features desktop` clean (the desktop CI build triggers on `src/**`; `just check` does not compile this feature). - `cargo test --features server --bin vervain-app` -> 24 passed. ## Sequencing Merge after VS-70 (PR #90) merges and deploys to staging, so the dialog's "deleting uninstalls the agent" copy is truthful end-to-end. The server tolerates a UI that never sends `force`, and this UI tolerates the new tokens, so the two can land in either order without breaking, but the copy is only accurate once the server uninstalls. #VAPP-55
feat(devices): uninstall the agent on delete, prompt to force when unreachable (VAPP-55)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 29s
f34c5fae4c
The delete-device dialog now reflects the VS-70 server flow: a normal delete uninstalls the agent from the machine before removing the record, and an offline agent gets a "Delete anyway?" prompt instead of a silent delete or an opaque error.

`remove_device` gains a `force` flag and returns a typed `DeleteOutcome` (Deleted / AgentUnreachable / UninstallFailed) instead of `Result<(), _>`: the unreachable and uninstall-failed results are states the dialog branches on, so they ride back as `Ok` while genuine failures (not-authorized, db-error, ...) stay errors. The single-node `removedevices` now carries `force` and its ack maps `ok` -> Deleted, `agent-unreachable` -> AgentUnreachable, `uninstall-failed` -> UninstallFailed (these are the kebab-case tokens pinned in VS-70).

In the dialog: the name-confirm copy now says deleting uninstalls the agent; on `AgentUnreachable` the dialog switches to a force-confirm step ("The agent is offline and can't be uninstalled remotely. Delete the device anyway? ...") whose "Delete anyway" re-issues with `force: true`; `UninstallFailed` surfaces a distinct retry message and keeps the record. The reachable happy path is unchanged (now with the agent uninstalled server-side). A `force_prompt` signal drives the two steps and is reset when the dialog opens.

Verified in the rust-builder-glibc image: cargo fmt, clippy --all-targets --features server -D warnings, cargo check --target wasm32-unknown-unknown, cargo check --features desktop, and cargo test --features server --bin vervain-app all pass.

#VAPP-55

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(devices): wait longer than the server's uninstall timeout for the delete ack (VAPP-55 review)
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 29s
Create release / Create release from merged PR (pull_request) Has been skipped
b20b2515e1
The delete server fn awaited vervain-server's ack for the default 10s `control::REQUEST_TIMEOUT`, but VS-70's server waits up to 10s for the connected agent's uninstall ack before it replies `agent-unreachable`. With equal timeouts the client (which starts its timer first) expired before the server's reply arrived, so a stale or silent agent connection surfaced as a generic "no ack from vervain-server" error instead of the "Delete anyway?" force prompt the result is meant to drive.

Give the delete its own `DELETE_ACK_TIMEOUT` (20s) that clears the server's 10s wait by a margin, so the `agent-unreachable` (and a slow-but-successful uninstall) result reaches the dialog. The connect timeout stays at 10s (connect is fast).

Verified in the rust-builder-glibc image: cargo fmt, clippy --all-targets --features server -D warnings, and cargo check --target wasm32-unknown-unknown all pass.

#VAPP-55

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/VAPP-55-delete-uninstall-agent-prompt 2026-06-30 18:17:20 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-apps!66
No description provided.