Skip to content

feat(cockpit): serve the Python cores over a 127.0.0.1 FastAPI backend (#351) - #362

Merged
dngioidev merged 1 commit into
mainfrom
feat/351-cockpit-fastapi-backend
Aug 3, 2026
Merged

feat(cockpit): serve the Python cores over a 127.0.0.1 FastAPI backend (#351)#362
dngioidev merged 1 commit into
mainfrom
feat/351-cockpit-fastapi-backend

Conversation

@dngioidev

Copy link
Copy Markdown
Owner

Closes #351

What

Cockpit v2 (ADR-0008, epic #350) — the localhost HTTP backend. A FastAPI
(ASGI) app served by uvicorn binds 127.0.0.1 only and exposes each existing
framework-agnostic Python core UNCHANGED as a loopback JSON endpoint. New module
tools/runner-ui/forge_cockpit/server.py is a thin route layer (decode → call
the core → serialize the typed result); the cores stay framework-agnostic. A
forge-cockpit console entry point runs uvicorn on 127.0.0.1 — restoring the
launch command #355 retired with the desktop UI, now serving the backend.

Endpoint Core
GET /api/health liveness
GET /api/fleet discovery.discover_fleet
POST /api/control control.control (start/stop/restart)
GET /api/logs logs.read_logs
POST /api/provision provision.provision (install/uninstall)
GET /api/usage usage.collect_usage + by session/day/model aggregates

Scope boundary: loopback hardening (Host/Origin/DNS-rebinding/CSRF/capability
token) is #352; PTY-over-websocket terminal is #353; browser UI is #354. This PR
is the HTTP surface only, with the minimal correct 127.0.0.1 bind.

Acceptance criteria

  • AC.1 — FastAPI/uvicorn app binds 127.0.0.1 only and exposes discovery,
    control, logs, provision, usage, reusing the forge_cockpit/*.py cores
    unchanged. Verified: test_server.py monkeypatches each core at the server
    boundary and asserts the endpoint returns its data; build_config().host == "127.0.0.1".
  • AC.2 — ADR-0006 invariants hold end to end: PAT-free, runner.env
    never read (shellout's hard backstop unchanged), usage metadata-only; no
    endpoint exposes a secret/PAT. Verified: tests assert the usage surface is
    metadata-only (no content/message/prompt keys), provision ignores a smuggled
    token field, and no response mentions runner.env/ghp_/*_pat.
  • AC.3fastapi + uvicorn added to pyproject.toml (MIT /
    BSD-3-Clause), httpx dev-only for TestClient; uv lock regenerated; license
    gate reports zero exceptions. Verified: node plugin/scripts/gates/license.mjs
    license: clean — ... 5 Python dep(s), 0 documented exception(s).
  • AC.4 — pytest (headless, TestClient) covers each endpoint returning its
    core's data and asserts loopback-only bind (not 0.0.0.0). Verified: 20 new
    tests, all green.

Verification (this run)

uv run pytest -q         → 122 passed  (20 new server tests + existing cores)
node .../gates/license.mjs → license: clean — 5 Python dep(s), 0 documented exception(s)
pnpm verify              → 58 files, 675 tests passed

Notes

  • The clobber/mis-target guard in provision cross-references the live fleet;
    the HTTP surface passes force straight through and leaves that cross-ref to
    the UI flow (Cockpit v2: browser UI — fleet overview + usage/cost panel + xterm.js terminal #354) — the core guard is unchanged for direct callers.
  • Runtime transitive deps (starlette, pydantic, pydantic-core, anyio, sniffio,
    idna, click, h11, typing-extensions) are all MIT/BSD/PSF. certifi (MPL-2.0)
    appears only on the dev/test path via httpx (TestClient), not at runtime,
    and the license gate inspects declared deps (not transitive), so it is not a
    gate finding — flagged here for honesty, not a blocker.

🤖 Generated with Claude Code

#351)

Cockpit v2 (ADR-0008, epic #350): re-architect the retired PySide6 desktop UI
into a local web app, starting with the HTTP backend. A FastAPI (ASGI) app
served by uvicorn binds 127.0.0.1 only and exposes each framework-agnostic core
UNCHANGED as a loopback JSON endpoint:

  GET  /api/health    - liveness
  GET  /api/fleet     - discovery.discover_fleet
  POST /api/control   - control.control (start/stop/restart)
  GET  /api/logs      - logs.read_logs
  POST /api/provision - provision.provision (install/uninstall)
  GET  /api/usage     - usage.collect_usage (+ by session/day/model aggregates)

forge_cockpit/server.py is a thin route layer: decode -> call the core ->
serialize the typed result. No business logic lives here; the cores stay
framework-agnostic. A forge-cockpit console entry point runs uvicorn on
127.0.0.1 - restoring the launch command #355 removed with the desktop UI, now
serving the backend instead of a Qt window.

Security (ADR-0006, inherited): PAT-free, runner.env never read (shellout's hard
backstop still applies), usage metadata-only. No endpoint adds a field that
could carry a secret; explicit serializers keep responses to the cores' typed
fields. Loopback hardening (Host/Origin/CSRF/capability token) is deliberately
deferred to #352; the PTY-over-websocket terminal is #353; the browser UI is
#354.

Deps: fastapi + uvicorn added to pyproject (both permissive - MIT / BSD-3-Clause),
httpx added dev-only for FastAPI's TestClient; uv.lock regenerated. The license
gate classifies the three new declared names (fastapi=MIT, uvicorn/httpx=
BSD-3-Clause) and stays green with ZERO exceptions.

Tests: pytest TestClient suite covers each endpoint returning its core's data,
the 127.0.0.1-only bind (never 0.0.0.0), and the metadata-only/no-PAT invariants.
122 pytest pass; 675 JS tests pass; license gate clean.

Closes #351

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SATRHKa6mDHDuirhP6QuwL
@dngioidev
dngioidev merged commit adc5f5c into main Aug 3, 2026
7 checks passed
@dngioidev
dngioidev deleted the feat/351-cockpit-fastapi-backend branch August 3, 2026 11:10
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.

Cockpit v2: localhost backend — serve the Python cores over 127.0.0.1

1 participant