From 8f655b8622b8495f384da3c72aed11c2236b6caf Mon Sep 17 00:00:00 2001 From: Ragnor Comerford Date: Mon, 1 Jun 2026 12:33:53 +0200 Subject: [PATCH] Extract the named-graph/top-level coherence rule into one helper The rule 'a named graph uses its own graphs. block, so a populated top-level block is a config error' lived inline in single-mode server boot. Extract it to OmnigraphConfig::ensure_top_level_blocks_honored so the same definition can be shared by the CLI selection gate (next commit) and the two can't drift. Boot calls the helper; the message is reworded context-neutral (drops 'serving') so it reads correctly from both boot and the CLI. Behavior-preserving: multi-graph mode keeps its own unconditional check, and single_mode_named_graph_rejects_top_level_blocks still passes. --- crates/omnigraph-server/src/config.rs | 22 ++++++++++++++++++++++ crates/omnigraph-server/src/lib.rs | 18 ++++-------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/omnigraph-server/src/config.rs b/crates/omnigraph-server/src/config.rs index 41b4c5c..cbb096f 100644 --- a/crates/omnigraph-server/src/config.rs +++ b/crates/omnigraph-server/src/config.rs @@ -376,6 +376,28 @@ impl OmnigraphConfig { blocks } + /// A named graph uses its own `graphs.` block, so a populated + /// top-level block would be silently ignored — a config error. The single + /// definition of that rule, shared by server boot and the CLI selection + /// gate ([`OmnigraphConfig::resolve_graph_selection`]) so the two can't + /// drift. An anonymous selection (`None`, e.g. a bare URI) legitimately + /// honors the top-level blocks, so it is never rejected here. + pub fn ensure_top_level_blocks_honored(&self, selected: Option<&str>) -> Result<()> { + if let Some(name) = selected { + let unhonored = self.populated_top_level_blocks(); + if !unhonored.is_empty() { + bail!( + "named graph '{name}' uses its own `graphs.{name}.…` block, but top-level {} \ + {} set and would be ignored. Move it to `graphs.{name}` (e.g. \ + `graphs.{name}.policy.file`, `graphs.{name}.queries`).", + unhonored.join(" and "), + if unhonored.len() == 1 { "is" } else { "are" }, + ); + } + } + Ok(()) + } + /// Resolve a stored-query `.gq` file path (from a registry entry), /// relative to the config's `base_dir`. Mirrors policy-file /// resolution; the registry loader calls this to turn each entry's diff --git a/crates/omnigraph-server/src/lib.rs b/crates/omnigraph-server/src/lib.rs index 64e7570..943d0a2 100644 --- a/crates/omnigraph-server/src/lib.rs +++ b/crates/omnigraph-server/src/lib.rs @@ -937,20 +937,10 @@ pub fn load_server_settings( cli_target.as_deref().or_else(|| config.server_graph_name()) }; // A named selection must not leave a populated top-level block - // silently unused — refuse boot and point at the per-graph block. - if let Some(name) = selected { - let unhonored = config.populated_top_level_blocks(); - if !unhonored.is_empty() { - bail!( - "serving named graph '{name}', but top-level {} {} set — a named graph \ - uses its own `graphs.{name}.…` block, so the top-level value is ignored. \ - Move it to `graphs.{name}` (e.g. `graphs.{name}.policy.file`, \ - `graphs.{name}.queries`).", - unhonored.join(" and "), - if unhonored.len() == 1 { "is" } else { "are" }, - ); - } - } + // silently unused — refuse boot and point at the per-graph block. The + // same rule the CLI selection gate enforces, shared via one helper so + // the boot check and `omnigraph queries validate`/`list` can't drift. + config.ensure_top_level_blocks_honored(selected)?; // Load + identity-check now (no engine needed); the schema // type-check happens when the engine opens. let policy_file = config.resolve_policy_file_for(selected);