ADR-055: The local console is a stdlib web server over `cplat` JSON; it acts only through Claude Code
Status: Accepted (2026-10-26)
Builds on: ADR-053: A domain model based on Backstage’s system model, with environments (the domain model), ADR-054: The 5.0 interface: system.yaml checked by the renderer, noun plugins with tombstones, and a migration that proves itself (the renderer is the only validator of system.yaml), ADR-032 — Every run is recorded in a private run store (run records), ADR-043 — One budget per run tree, kept in a local ledger and checked at safe points (budgets), spec 122
Context
Section titled “Context”Spec 122 adds a console that a user starts with cplat ui: three places (Inbox, Board, System), side panels, a ⌘K palette, actions and notifications. It runs on the user’s machine, from the platform checkout that the marketplace installed, often offline, and it must add no server, no telemetry and no second place where state lives. Four choices in it outlive the spec:
- UI technology. The repo already has an Astro site (
site/) and a Next.js template. Both need Node, an npm install and a build step. The plugin checkout is not built on install,devbox run test-fastruns only pytest, and every third-party asset needs a license entry and a pin. - How the console reads data. It could import the
cplatmodules or run the CLI. The spec makes the JSON ofcplat model show,system status,spec list,runsandneedsthe only source. - How it changes anything. The console could call
git,ghorkubectl, or editsystem.yaml. That would be a second write path next to the slash commands. - Where notifications are sent from. Webhook URLs and SMTP credentials must stay on the machine, so delivery cannot run in a GitHub workflow.
Decision
Section titled “Decision”- A Python standard-library server with HTML rendered on the server and one hand-written script.
cplat uirunshttp.server.ThreadingHTTPServerbound to127.0.0.1on a free port. Pages and side panels are HTML fragments built in Python (scripts/cplat/console/), so pytest can test what each page shows. The browser loads one CSS file and one dependency-free JavaScript file fromscripts/cplat/console/static/. They handle the palette, the keyboard, side panels, polling and the actions panel. There is no npm, no build step, no CDN, no web font and no vendored library. POST requests need a token that is created on each start and embedded in the page, and requests whoseHostis not127.0.0.1:<port>are refused (DNS rebinding). - The console reads only
cplatJSON, run as a subprocess. Each source is onecplat … --jsoncall with a timeout. It is cached with the time it was read and refreshed in the background, and when it fails it is marked unavailable on its own. A crash or a hang in one source never affects another, and the console sees exactly what a user or an agent sees on the command line. Repo content comes from a snapshot of each system repo’smain(and of open draft spec pull requests), downloaded withghinto the user’s cache. The console never reads a working tree and never writes to one. - Every change is a slash command run by Claude Code. An action shows the exact command (and, for
system.yaml, the diff that the renderer computed on a scratch copy), and runs it on confirm ascplat headless "<command>"in the target repo. That gives budgets, run records and resumes for free. The console itself never runsgitwrites,ghwrites orkubectl. Withoutclaudeon the PATH it is read-only and says so. - Notifications are delivered locally.
cplat ui notify(started by the console as a child process, or from cron or launchd with--once) comparescplat needs,cplat runsand the alert and smoke issues with a local state file and sends to GitHub, a Slack incoming webhook, SMTP e-mail and the desktop. Settings are in~/.config/sdlc-foundry/notify.yaml. Secrets come only from the environment or the OS keychain.
Consequences
Section titled “Consequences”- The console has no dependencies outside the Python standard library, plus
pyyamlandruamel.yaml, whichcplatalready declares. It works offline after the marketplace install, andreuse lintneeds no new license. - New views are Python functions that return HTML and are tested with pytest. Behaviour that lives only in the browser (key handling, focus) is kept small and is checked by static tests against the markup contract (
data-key,data-action,aria-*). - Each refresh starts a few short
cplatprocesses, about one per source every 30 seconds. That is cheap compared with theghandkubectlcalls inside them. - Whatever the console shows can be reproduced from the command line, and whatever it does is a command in a run record.
- A later hosted or multi-user console would need its own ADR. This one stays local, single-user and acts with the user’s credentials.
Not included
Section titled “Not included”- A JavaScript framework, a bundler, or reusing the Astro site for the console.
- Editing files, merging pull requests or promoting through
kubectlfrom the console. - A Slack app (webhooks only) and any telemetry.