feat(cockpit): serve the Python cores over a 127.0.0.1 FastAPI backend (#351) - #362
Merged
Merged
Conversation
#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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pyis a thin route layer (decode → callthe core → serialize the typed result); the cores stay framework-agnostic. A
forge-cockpitconsole entry point runs uvicorn on 127.0.0.1 — restoring thelaunch command #355 retired with the desktop UI, now serving the backend.
GET /api/healthGET /api/fleetdiscovery.discover_fleetPOST /api/controlcontrol.control(start/stop/restart)GET /api/logslogs.read_logsPOST /api/provisionprovision.provision(install/uninstall)GET /api/usageusage.collect_usage+ by session/day/model aggregatesScope 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
control, logs, provision, usage, reusing the
forge_cockpit/*.pycoresunchanged. Verified:
test_server.pymonkeypatches each core at the serverboundary and asserts the endpoint returns its data;
build_config().host == "127.0.0.1".runner.envnever 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
tokenfield, and no response mentionsrunner.env/ghp_/*_pat.fastapi+uvicornadded topyproject.toml(MIT /BSD-3-Clause),
httpxdev-only for TestClient;uv lockregenerated; licensegate reports zero exceptions. Verified:
node plugin/scripts/gates/license.mjs→
license: clean — ... 5 Python dep(s), 0 documented exception(s).core's data and asserts loopback-only bind (not 0.0.0.0). Verified: 20 new
tests, all green.
Verification (this run)
Notes
provisioncross-references the live fleet;the HTTP surface passes
forcestraight through and leaves that cross-ref tothe UI flow (Cockpit v2: browser UI — fleet overview + usage/cost panel + xterm.js terminal #354) — the core guard is unchanged for direct callers.
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