Skip to content

Add JSON state API for daemon #76

@VincentShipsIt

Description

@VincentShipsIt

Add JSON state API for daemon

Executive Summary

Once daemon mode (#70) and headless EC2 (#60) ship, ShipCode runs without a desktop window. There is no way to ask "what's running, what failed, what's queued" from outside the main process. Symphony solves this with a small HTTP server (SPEC §13.7) that exposes a JSON state endpoint plus a refresh trigger. This issue adds the equivalent: a loopback-only API and a minimal HTML dashboard, suitable for curl, scripts, and browser inspection on a headless host.

Implementation Checklist

  • Confirm scope and affected code paths
  • Implement the requested behavior
  • Add or update focused tests
  • Run verification and document evidence

Problem Statement

The pipeline state today lives in the Electron main process and is only observable through the renderer. Daemon and headless modes need a programmatic surface that does not require IPC, does not require the desktop app to be running, and does not push state to a third-party. Operators need to confirm "is anything stuck?", trigger an immediate reconcile after editing config, and look up the log path for a single issue's run.

Goals

  • GET /api/v1/state returns running pipelines, retry queue, totals, and current rate-limit headroom.
  • GET /api/v1/<issue_number> returns runtime detail for one issue, including log paths.
  • POST /api/v1/refresh enqueues an immediate reconcile tick and returns 202.
  • GET / returns a minimal server-rendered HTML dashboard.
  • Bind to 127.0.0.1 by default; require explicit config to bind elsewhere.

Non-Goals

  • Not adding write endpoints beyond /refresh.
  • Not adding authentication in this issue (loopback-only is the security boundary).
  • Not building a rich SPA dashboard — server-rendered HTML is enough.
  • Not exposing the desktop renderer over the network.

User Stories with Acceptance Criteria

Story 1: Operator inspects daemon state

  • As an operator on a headless box, I run curl localhost:PORT/api/v1/state and get a JSON snapshot.
  • Acceptance: response includes running[], retrying[], totals, rate_limits; HTTP 200; valid JSON.

Story 2: Operator looks up one issue

  • As an operator, I run curl localhost:PORT/api/v1/123 for a known active issue.
  • Acceptance: response includes phase, attempt, log file paths, last error if any; HTTP 200.

Story 3: Refresh on demand

  • As an operator who just edited WORKFLOW.md, I POST /api/v1/refresh and the dispatcher runs immediately.
  • Acceptance: HTTP 202; reconcile tick observed in logs within 1 second.

Story 4: Closed issue returns 404

  • As a script, I query an issue that is not currently tracked.
  • Acceptance: HTTP 404 with {error: {code:'not_found', message: ...}}.

Functional Requirements

  • New process owns the HTTP server, sourcing pipeline state from the main process via existing in-process channels (or directly when daemon is the main process).
  • Default bind: 127.0.0.1:<port>; port configurable; bind address configurable.
  • Read-only endpoints except POST /refresh.
  • All non-GET methods on read-only endpoints return HTTP 405.
  • Error envelope: {error: {code, message}} for all 4xx/5xx.
  • HTML dashboard at / lists running and retrying pipelines with links to per-issue detail.

Non-Functional Requirements

  • Server start failure (port in use, etc.) does not crash the pipeline; logs and continues without API.
  • /api/v1/state returns within 100ms for up to 100 concurrent issues.
  • No persistent connections required; plain HTTP/1.1.

Success Criteria

  • curl http://127.0.0.1:PORT/api/v1/state returns the documented shape with the daemon idle.
  • Querying an issue not in the registry returns HTTP 404 with the documented error envelope.
  • POST /api/v1/refresh triggers a dispatcher tick observable in logs within 1 second.
  • PUT /api/v1/state returns HTTP 405.
  • Killing the API server while pipelines run does not interrupt any in-flight phase.

Out of Scope

  • Auth, TLS, multi-tenant scoping.
  • WebSocket / SSE streams.
  • Mutation endpoints beyond /refresh.
  • Cross-host federation of multiple daemons.

Dependencies

Verification Plan

  • Integration test: start the server with a fake pipeline registry, hit each documented endpoint, assert shapes and status codes.
  • Bind-address test: confirm default refuses connections from non-loopback.
  • Failure-mode test: simulate port-in-use; assert daemon continues without API.
  • Manual: run on a headless box, confirm dashboard renders in a browser via SSH port-forward.

Risks & Open Questions

  • Risk: Endpoint shape drift across versions; document the schema and version under /api/v1/.
  • Risk: Loopback-only assumption broken when users tunnel; document recommended SSH-tunnel usage.
  • Open Q: Should /api/v1/state include recent log tail snippets, or only paths?
  • Open Q: Where does this live — apps/cli, packages/server, or inside the existing daemon?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request
    No fields configured for Feature.

    Projects

    Status

    Deferred

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions