mr-668: remove ServerMode, registry field, and the SINGLE_GRAPH sentinel

C-1/C-2 introduced `GraphRouting` and pointed the middleware at it.
This commit removes the legacy shape that's now dead:

* `ServerMode` enum — deleted. Single mode's `uri` lives on
  `handle.uri`; multi mode's `config_path` lives on the
  `GraphRouting::Multi` arm.
* `AppState.mode: ServerMode` field — deleted.
* `AppState.registry: Arc<GraphRegistry>` field — deleted. Multi
  mode's registry is on `GraphRouting::Multi { registry, .. }`;
  single mode has no registry at all.
* `AppState::mode()`, `AppState::uri()`, `AppState::registry()`
  accessors — deleted. New `AppState::routing() -> &GraphRouting`
  is the single public entry point.
* `SINGLE_GRAPH_KEY_ID` constant — deleted. `GraphHandle.key` is
  still required by the struct, but in single mode the key is now
  only a tracing label (`"default"`, inlined with a comment naming
  its sole remaining purpose). Single-mode flat routes never carry
  a `{graph_id}` parameter, so the key is never compared against
  user input, and there is no registry where it could be a map
  key. C-1/C-2 already removed the registry walk that the sentinel
  was named for.

Callers migrated:
* `build_app` (lib.rs:944) — matches on `state.routing()` instead
  of `state.mode()`.
* `server_graphs_list` (lib.rs:1162) — destructures the Multi arm
  to get the registry; Single arm short-circuits to 405.
* `server_openapi` (lib.rs:1217) — matches the Multi arm for the
  cluster-prefix rewrite.
* `tests/server.rs:3735` — the B2 footgun regression test now
  matches on `state.routing()` to extract the single-mode handle
  (the test's earlier `state.registry().list().next()` shape was
  the closest pre-fix analog to "embedded consumer reaches the
  engine"; the new shape is more direct).

Closes the entire "single mode forced through a multi-mode
abstraction" class. After this commit:
* No magic sentinel as a routing key.
* No `single_mode_handle` walk-and-assert helper.
* No 500-class "programmer error" branch in the middleware.
* No two-field discriminant on `AppState` where one would do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ragnor Comerford 2026-05-27 13:50:25 +02:00
parent c95ecff828
commit 00522d6fc1
No known key found for this signature in database
3 changed files with 68 additions and 119 deletions

View file

@ -3695,11 +3695,11 @@ async fn ingest_per_actor_admission_cap_returns_429() {
/// Regression for B2 (MR-668): when an `AppState` is built with a
/// per-graph policy and a custom workload, the engine inside the
/// registry's `GraphHandle` MUST have the same policy applied via
/// routing's `GraphHandle` MUST have the same policy applied via
/// `Omnigraph::with_policy`. Pre-fix, `new_with_workload(...).with_policy_engine(p)`
/// installed the policy only on the HTTP-layer `handle.policy`; the
/// underlying `Arc<Omnigraph>` was reused without `with_policy`, so any
/// caller reaching through `state.registry().list()` could bypass Cedar.
/// caller reaching through `state.routing()` could bypass Cedar.
///
/// This test reaches the engine the same way an embedded SDK consumer
/// or future routing code path would, and asserts the policy still
@ -3707,6 +3707,7 @@ async fn ingest_per_actor_admission_cap_returns_429() {
/// the policy's allowed group" — i.e., authenticated-but-unauthorised.
#[tokio::test(flavor = "multi_thread")]
async fn engine_layer_policy_fires_via_direct_arc_omnigraph_from_new_single() {
use omnigraph_server::GraphRouting;
let temp = init_loaded_graph().await;
let graph = graph_path(temp.path());
let db = Omnigraph::open(graph.to_str().unwrap()).await.unwrap();
@ -3728,11 +3729,14 @@ async fn engine_layer_policy_fires_via_direct_arc_omnigraph_from_new_single() {
workload,
);
// Reach into the registry and pull the engine the same way an
// Reach into the routing and pull the engine the same way an
// embedded consumer holding `Arc<Omnigraph>` would. If `new_single`
// failed to apply `with_policy` to the engine, this `mutate_as`
// would succeed — the HTTP-layer is bypassed entirely.
let handle = state.registry().list().into_iter().next().expect("handle");
let handle = match state.routing() {
GraphRouting::Single { handle } => Arc::clone(handle),
GraphRouting::Multi { .. } => panic!("expected single-mode routing"),
};
let engine = Arc::clone(&handle.engine);
let mut params: omnigraph_compiler::ParamMap = Default::default();