Surface Console#

Surface ships a local Surface Console server for any producer that emits a Surface-format run snapshot. The current CLI, routes, and generated assets still use console; treat that as an implementation name, not product language.

The Surface Console over a producer run: status rollups, an attention band for the stale rate-limit claim, and a filterable claim feed

Quick start#

From a Builder or producer project directory:

npx surface console

The Surface Console reads .surface/runs/latest.json by default. The server reads the file fresh on every request, so no restart is needed when the producer writes a new run.

Pass a custom path or port explicitly:

surface console --read-model .surface/runs/latest.json --port 4242

Use --port 0 to let the operating system assign a free port. The startup message prints the assigned URL after the server has bound it.

Or use a config file:

{
  "port": 4242,
  "readModelPath": ".surface/runs/latest.json",
  "vocab": {
    "projectName": "My project",
    "projectKind": "repo governance"
  },
  "theme": {
    "brandName": "Veritas"
  }
}
surface console --config surface.config.json

Merge multiple producer bundles into one view#

surface console also accepts repeatable --input bundle paths, mirroring surface report --input. This is additive to --read-model: any --input switches the console onto a merge-and-project path. With more than one input the console validates each producer bundle, merges them order-independently (mergeBundlesDetailed), and projects the merged ledger into the read model. Each input bundle is watched, so editing any producer bundle refreshes the view live.

npx surface console \
  --input examples/console-multi-producer/ci-producer.bundle.json \
  --input examples/console-multi-producer/review-producer.bundle.json \
  --input examples/console-multi-producer/security-producer.bundle.json

The merged view adds two multi-producer affordances:

  • Producer attribution. Every claim card names the producer(s) that asserted it, built from each input bundle's optional producerId (falling back to source). An identical shared claim contributed by several producers dedups to one card that still lists all of them. Because a merged bundle never carries a top-level producerId (merge.md §5 rule 3), attribution is carried as projection metadata keyed by claim id, built during the merge step — not read back off the merged bundle.
  • Merge collisions. Same claim id, different content across producers is surfaced in a dedicated "Merge collisions" section that names the colliding producers. The lexicographically-first content is kept and every losing record is reported there, never silently dropped (merge.md §6). The examples/console-multi-producer/ demo ships a build-digest collision between the CI and security producers.

Implementation note: surface console, *.console.json, /console.js, and /console.css are current implementation names. Product-facing language should describe the Operator experience as the Surface Console.

Developer map#

Surface keeps its own standalone Surface Console. Keep product-specific trust behavior in this repo, and use Kontour UI only as the shared presentation system across Kontour products.

Area Location Purpose
CLI command src/cli.ts, src/commands/console.ts Parses surface console flags and starts the local server.
Local server and API routes src/console/server.ts HTTP transport only: routing, SSE, and request/response mechanics. Delegates domain orchestration to the runtime.
Console runtime src/console/runtime.ts SurfaceConsoleRuntime owns read-model loading, run catalog, projection creation, HTML assembly, and Claim Package authoring — usable without starting an HTTP server.
Projection contract src/console/projection.ts, src/console/claim-detail-projection.ts, src/console/types.ts Converts producer read models into the UI shape consumed by the Console, including the per-claim detail projection (guidance, gap labels, policy facts, integrity scope) the browser renders from.
Standalone UI shell src/console/shell.ts Owns the dependency-free HTML shell served by /.
Standalone UI asset sources src/console/client/parts/, src/console/styles/parts/ Owns the editable browser behavior and Kontour UI-compatible token aliases for the standalone Surface Console.
Generated UI asset constants scripts/build-console-assets.mjs, src/console/client/index.js, src/console/styles/index.css, src/console/assets.generated.ts, src/console/script.ts, src/console/styles.ts Concatenates ordered client and CSS parts, then converts JS/CSS assets into TypeScript constants consumed by /console.js and /console.css; regenerate with npm run build:console-assets.
Browser coverage tests/browser/console.spec.ts Starts bin/surface.mjs console and verifies the real standalone page on desktop and mobile.
Trust-state chip scripts/build-console-assets.mjs, scripts/build-trust-state-css.mjs, scripts/check-console-token-drift.mjs, tests/browser/support/ui-trust-state.ts Renders claim statuses with Kontour UI's trust-state chip. At build time the Console bundles ui's renderer (@kontourai/ui/trust-state) and inlines @kontourai/ui/trust-state.css, and the Trust Panel gets the same rules with ui's light values as fallbacks, so the CLI and the panel stay standalone. The --check modes fail when a generated copy differs from the installed @kontourai/ui; the token gate fails when a --k-trust-* token is missing or drifts.
Trust Snapshot Viewer assets scripts/build-pages-extras.mjs, scripts/pages-extras/ Adds the viewer page to the Pages site, with token CSS copied from the installed public @kontourai/ui package; the trust panel browser tests load the same page.
Package boundary guard tests/package-files.test.ts, scripts/check-package-contents.mjs Keeps Kontour UI dev-only, prevents React/runtime leakage, and verifies published package contents.

Run directory convention#

Producers write one file per run:

.surface/runs/
  <run-id>.console.json   # full read model for that run
  latest.json               # index → { latestRunId, readModelPath }

latest.json is either a full read model or an index pointer. If it contains kind: "surface-console-index" and a readModelPath, the server resolves the model from that path relative to the repo root. Otherwise it uses the file directly.

Run history#

When multiple *.console.json files exist in the run directory, the Surface Console toolbar shows a run picker that lets you compare runs without restarting the server.

The /api/runs endpoint returns a sorted list of available runs:

GET /api/runs
→ [{ runId, generatedAt, claimCount, verifiedCount, attentionCount, fileName }, ...]

Select a specific run by appending ?run=<runId> to the /api/read-model endpoint, or by using the run picker in the UI.

Surface Console features#

Metric chips#

The header row shows summary chips for total claims, verified count, and attention count. Each chip is clickable and filters the claim list to matching claims. The active filter is highlighted with a raised border; clicking again clears it.

Claim feed#

Each claim card shows:

  • Claim ID and surface path
  • The claim status as Kontour UI's shared trust-state chip: its display label (Verified, Needs refresh, Disputed, Pending review, No evidence, and so on), a glyph unique to the status, and a line style, with the status's own colour in light and dark. The card's left edge uses the same colour. A product vocab.statusLabels override replaces the visible label; the default label stays available to assistive technology.
  • Impact level badge
  • Policy ID when present

Master-detail layout (desktop)#

On viewports wider than 900 px the Surface Console uses a sticky side panel instead of a modal overlay. Clicking a claim slides the detail panel in from the right without covering the claim list. The list and panel are visible simultaneously.

Claim detail#

The detail panel shows evidence, events, policy context, and integrity scope for the selected claim. The integrity scope is where producers can expose what a verified claim is anchored to: a source revision, working-tree digest, file fingerprints, or producer configuration hashes. For claims that are not yet verified, the panel shows contextual guidance describing what is needed to reach the verified state. For a stale claim the guidance follows why it is stale: the read-model claim's staleReason, or else the reason on its freshness-breach gap; only a commit reason is described as outdated evidence.

Evidence summary is the human-readable producer summary. Observed result is reserved for structured runtime output such as a pass/fail result, command, exit code, stdout, or stderr. If a producer only supplies a summary, the Surface Console does not duplicate it as an observed result.

Eval summary#

When a producer runs an eval cycle (e.g., via veritas eval record), it can write a generic EvalSummary into the run snapshot. The Surface Console uses this to show post-hoc review context alongside the live trust state.

EvalSummary shape (see src/types.ts):

interface EvalSummary {
  reviewed: boolean;
  reviewedAt?: string;
  confidence?: "low" | "medium" | "high";
  outcome?: "accepted" | "accepted-with-changes" | "rejected";
  falsePositiveCount?: number;
  missedIssueCount?: number;
  timeToResolutionMinutes?: number;
  notes?: string[];
  metadata?: Record<string, unknown>;
}

This is a producer-agnostic shape. Producers may put domain-specific fields under metadata.

API reference#

Route Method Description
/ GET Surface Console HTML shell
/api/read-model GET Current (or ?run=<id>) read model JSON
/api/console-model GET Current (or ?run=<id>) Surface Console projection
/api/runs GET Sorted list of available run snapshots
/api/claims POST Add a claim to the local claim store
/api/claims/:id PUT Update a claim in the local claim store
/api/claims/:id DELETE Remove a claim from the local claim store
/console.js GET Compiled Surface Console script; current route name
/console.css GET Surface Console styles; current route name

Surface owns the Surface Console shell, status model, claim browser, and metadata drilldown. Producers own the read model and vocabulary that make the Console meaningful for their domain.

Source: docs/reference/console.md