Extract the named-graph/top-level coherence rule into one helper

The rule 'a named graph uses its own graphs.<name> 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.
This commit is contained in:
Ragnor Comerford 2026-06-01 12:33:53 +02:00
parent 200fcbb215
commit 8f655b8622
No known key found for this signature in database
2 changed files with 26 additions and 14 deletions

View file

@ -376,6 +376,28 @@ impl OmnigraphConfig {
blocks
}
/// A named graph uses its own `graphs.<name>` 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

View file

@ -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);