Skip to content

Share typed command reports between CLI and HTTP instead of reparsing JSON #482

Description

@forkwright

Finding

akroasis-server describes itself as a typed, durable HTTP interface whose endpoints mirror the CLI without forking. Its handlers currently implement that contract by invoking text/JSON-oriented CLI dispatch into a Vec<u8>, parsing those bytes back into serde_json::Value, and returning Json<Value>.

The actual report structs remain private inside CLI modules, so the server has no compile-time relationship to the schema it claims to expose. A same-process typed result is serialized and reparsed merely to cross an internal module boundary.

Verified against main c8e671845adca3e5b57a205d08aca31eadbc6000.

Evidence

  • crates/akroasis-server/src/lib.rs:1-15 calls the crate a “Typed axum HTTP backend” and durable service interface, says every response matches a CLI --json report, and says frontends/agents drive the same logic “without forking.”
  • crates/akroasis-server/src/mesh.rs:24-66 implements all three mesh endpoints by:
    1. constructing a CLI MeshCommand { json: true };
    2. dispatching into Vec<u8>;
    3. calling serde_json::from_slice;
    4. returning Json<serde_json::Value>.
  • crates/akroasis-server/src/radio.rs:103-120 uses the same rendered-bytes/reparse path for radio detection.
  • The schema-owning CLI types are private. Examples include StatusReport, NodesReport, and TopologyReport in crates/akroasis/src/mesh/mod.rs:41-92, and DetectReport / DetectedRadioReport in crates/akroasis/src/radio/detect.rs:13-27. Import, export, and vault JSON reports follow the same local pattern.
  • Closed audit: no MCP / --json / programmatic interface for any CLI surface #126 explicitly recommended a shared typed command-report layer and repeatedly recorded that its incremental JSON slices did not close the eventual shared CLI/MCP/API boundary. The issue was nevertheless closed before that boundary existed, and no open successor owns it.

Why this matters

The HTTP API is coupled to presentation bytes rather than to a type contract:

  • a harmless CLI formatting or framing change can break an HTTP endpoint at runtime;
  • every endpoint gains an internal JSON parse failure that cannot exist when returning the typed value directly;
  • Json<Value> hides schema changes from Rust, API documentation, and downstream compile-time checks;
  • error classification is layered around CLI dispatch rather than around command-domain results; and
  • adding a second programmatic client encourages another consumer of rendered JSON instead of one reusable command surface.

This is not a legitimate process boundary. Both layers run in the same workspace and intend to expose the same report.

Desired correction

Extract report types and non-presentational command execution into a shared public/internal library surface. Command functions should return typed reports (or a typed report enum) plus typed errors. The human CLI may render those values as tables/text or serialize them to JSON; the HTTP API should return Json<Report> directly.

Keep interactive-only commands explicitly outside the programmatic surface until they have a non-interactive request contract. Do not replace the current loop with a generic Value-returning dispatcher—the schema types are the point.

Done when:

  • mesh and radio HTTP handlers do not call CLI dispatch or parse rendered bytes;
  • CLI JSON and HTTP JSON serialize the same report types;
  • changing a report field produces compile-time changes in both consumers;
  • API responses use concrete Json<T> types rather than Json<Value> where the schema is known;
  • shared error/domain execution is independent of terminal rendering; and
  • audit: no MCP / --json / programmatic interface for any CLI surface #126’s stated shared CLI/API report boundary has a live owner and executable contract tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions