Skip to content

Architecture v0.4.0 — console polish, docs, and proof

Scope from contract §12 (binding) and decisions D21–D23. Everything from v0.3.1 plus the changes below. The transport is unchanged: JSON-RPC 2.0 over gRPC, exactly as §11 fixed it in 0.3.1.

0.4.0 is in progress

This page states the scope the contract fixes, not results. Where a claim needs evidence — multiple workers across multiple zones, autoscaling and rebalance under load, per-zone tool isolation, independent MCP clients, the role and group security matrix — the evidence belongs in the release's test-status report (reports/test-status-v0.4.0.md), which is still being filled in — the GKE run is pending. Nothing on this page should be read as a verified result ahead of that report.

Console

Nothing in the data path changed; this is the management surface.

  • Per-zone packages (§12.1). Tools, resources and prompts are listed per zone, each with an enable/disable toggle for that zone. environments[].blocked stays the environment-wide list from §9; a new per-zone map environments[].blocked_zones = {zone: [names]} adds to it, and a deploy writes the union of both into that zone's RAMEN_BLOCKED. Route: PUT /api/v1/groups/{group}/environments/{env}/zones/{zone}/blocked. The enforcement point does not move — the node still filters */list and answers -32601 — so a model connected to one zone sees only what that zone has switched on.
  • Key client types (D21). Every key now carries a client_type that is enforced on both sides. devops keys (rmn_) are accepted by /api/v1/* and rejected by workers; agent keys (rmk_) are accepted by a worker's ramen.v1.Mcp for the groups and zones they name and rejected by the console API with 403. Keys issued before 0.4.0 are read as devops when they start rmn_ and agent otherwise. The two key kinds were always separate in practice; 0.4.0 makes the separation a checked property rather than a convention.
  • Session epoch (folded in from the 0.3.0 audit backlog). Each user document carries session_epoch, sessions embed it, and a session whose epoch differs is rejected. It is bumped on password change, role change, group change, delete, and on any change to config/auth — so disabling password login, changing someone's role or deleting a user takes effect on their live sessions instead of at the next expiry.
  • Passwords and generated keys are at least 12 characters with upper, lower, digit and special characters, enforced both when they are generated and when a password is set or changed (RAMEN_MIN_PASSWORD_LEN may raise the floor, not lower it).
  • Naming and presentation. The product name appears once, in the logo. Every button, label, heading and message is sentence case, with no abbreviations in user-visible text ("service account", never "SA") and no colour names in health wording — the colour stays the signal and each cell carries a text label, so the grid reads without colour. The group page collects every per-zone action into one Actions section, the logs page becomes two panes (entry list with the newest selected, body beside it), and the sidebar shows the signed-in email and role together under the logo.

Node

The transport is unchanged, but two things about how the node reads its environment are not.

  • RAMEN_TRUST_PROXY_HOPS replaces the boolean RAMEN_TRUST_PROXY. The client address is the n-th x-forwarded-for entry counted from the right — the end proxies append to — so entries a caller supplies cannot become the address the allowlist checks. Deployed values are 2 on GCP and 1 on AWS, set by the worker chart and both console renderers; the default is 0, meaning the peer address, which is right for local and compose. RAMEN_TRUST_PROXY=1 still means one hop, and an explicit hop count overrides it in either direction. Every failure mode — trust off, no header, too few entries, an unparseable entry — falls back to the peer address, so a wrong count denies rather than admits.
  • RAMEN_REFLECTION gates gRPC server reflection. It defaults to on and is set to 0 on deployed workers by the chart and both renderers, because reflection runs ahead of every guard and would let an anonymous caller at the edge enumerate the services, ramen.v1.Admin included. Local and compose workers keep it, so grpcurl still works there without a copy of the proto.

Both came out of the independent review of the 0.4.0 transport claims, and both postdate the 0.3.2 cloud run, so neither has been exercised against a real load balancer yet.

Docs site

  • Dark only. One palette, no light/dark toggle (scheme: slate, custom primary and accent), with the tokens in docs/stylesheets/ramen.css. Contrast was measured rather than assumed: coral #F26B3A on the ink background #1F1B18 is 5.65:1 and passes WCAG AA, so the brand token was kept; the header bar's old white text on coral was 3.03:1 and failed, so the header and tabs became an ink surface with a coral rule and a coral active-tab colour.
  • A real diagram. docs/img/architecture.svg is hand-written inline SVG — no mermaid, no external renderer — and replaces the ASCII shape on How it works. It draws the path that actually runs: stdio client → ramen-mcp-bridge → gRPC through the load balancer on ramen-group / ramen-zone metadata → Rust node → Python runtime → group bucket, with the console alongside. It names the hops that are plaintext HTTP/2 unless TLS is configured, because a diagram that implies end-to-end encryption would be wrong.
  • Transport written down. Transport and what secures each hop states, per hop, what protects it and what does not, and separates what was verified on a live cluster from what has only unit and conformance tests — and from the AWS path, which had still never been applied to a real account (no longer true from 0.5.6).
  • Smaller things. The edit-this-page action and edit_uri are gone; llms.txt is still served for agents but has no navigation entry; heading permalinks are off, so headings no longer render a leading §; the badge row is one row at one height from one source.

Repository metadata

The GitHub repository carries a description, the documentation site as its homepage, and the twenty topics drafted for the launch (mcp, model-context-protocol, mcp-server, ai-agents, llm-tools, kubernetes, gke, eks, gcp, aws, rust, python, fastapi, self-hosted, high-availability, canary-deployment, terraform, helm, grpc, json-rpc), so that people and language models can find it.

Verification planned for this release

Listed here so the gap is visible, not to claim it is closed. Per §12.3 and D22/D23:

What How it is meant to be shown
Multiple workers serving across multiple zones a local kind cluster with two worker namespaces, then once on a throwaway GKE project
Autoscaling under load and rebalance moving traffic generated load against a zone, an HPA scaling it up and back down, rebalance changing the split
Per-zone tool isolation the package lists two zones return differing after a per-zone toggle
Independent MCP clients a client driven against the bridge and screenshotted, plus a published editor config
Roles and groups a matrix over roles × groups × both key types, asserting every cross-group and cross-role action is refused and that revocation is immediate
The transport claims above an independent review of the claims against the code, filed separately

Results, when they exist, go in reports/test-status-v0.4.0.md and the cloud run in reports/cloud-v0.4.0.md.

Decisions added in this release

ID Decision
D21 API keys carry a client type that is enforced: an agent key is accepted only by workers over gRPC, a devops key only by the console API; each is rejected by the other side.
D22 The autoscale and rebalance stress test runs on a local kind cluster first, then once on a throwaway GKE project.
D23 Independent-client evidence is a real MCP client driven against the bridge, plus an editor configuration the user captures.