Skip to content

[triage:ui-02-gateway-client-duplication] Gateway client logic forked across capsem-tray and capsem-tui #152

Description

@ebursztein

Imported from Capsem triage report ui-02-gateway-client-duplication.md.

  • Severity: medium
  • Category: duplication
  • Area: capsem-tray, capsem-tui
  • Location: crates/capsem-tray/src/gateway.rs:1-201 and crates/capsem-tui/src/gateway_provider.rs:135-525
  • Confidence: verified

Summary

The tray and the TUI each independently reimplement the same loopback gateway
client: token discovery, gateway.port/gateway.token file reading, Bearer
auth, status fetch, and the identical VM-action endpoint set
(/vms/{id}/stop, /vms/{id}/pause, /vms/{id}/resume, /vms/{id}/delete,
/vms/create, /purge, /profiles/list). There is no shared gateway-client
crate (confirmed: capsem-core and capsem-proto expose none). This violates the
project's "one way to do things" / "capsem-core is the shared library"
invariant and means endpoint or auth changes must be made in (at least) two
Rust places plus the TS client.

Evidence

Tray (crates/capsem-tray/src/gateway.rs):

  • discover() reads run_dir/gateway.port + run_dir/gateway.token (lines 66-88).
  • stop_vm/delete_vm/suspend_vm/resume_vm hit /vms/{id}/stop|delete|pause|resume (159-177).
  • provision_temp() POSTs /vms/create (180-200).

TUI (crates/capsem-tui/src/gateway_provider.rs):

  • gateway_port() reads run_dir/gateway.port (187-191); fetch_token GETs /token (135-148).
  • invoke_action() hits the same /vms/{id}/stop|pause|resume|delete, /vms/create, /purge (324-441).
  • fetch_status GETs /status and deserializes a StatusResponse/VmSummary pair (150-166, 532-572).

Both define their own StatusResponse/VmSummary serde structs with
overlapping fields. The endpoint paths and the port/token discovery rules are
duplicated verbatim.

Notably the two clients have already diverged in subtle ways:

  • The tray's parse_port_file validates the port (rejects non-numeric/overflow,
    gateway.rs:35-40), while the TUI silently swallows a bad port with
    .ok()? and falls back to 19222 (gateway_provider.rs:187-191).
  • The tray authenticates /status with a Bearer token; the TUI does too, but
    fetches the token from a different endpoint shape. Divergence like this is
    exactly what a shared client prevents.

Impact

Endpoint, auth, or discovery changes must be applied in multiple crates and can
silently drift (as the port-validation difference shows). Higher maintenance
cost and a real risk of behavioral inconsistency between the tray and the TUI
talking to the same gateway.

Suggested fix

Extract a single capsem-gateway-client (or a module in capsem-core, honoring
the thin-shell rule) that owns: port/token discovery, the authenticated
reqwest::Client, the status/profiles structs, and the VM-action methods. Have
both the tray's GatewayClient and the TUI's GatewayProvider delegate to it.

Triage

Confirmed from the local reviewed report in /Users/elie/git/capsem/tmp/bugs/ui-02-gateway-client-duplication.md. Track implementation in the triage sprint; add regression coverage before fixing.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions