Commit graph

25 commits

Author SHA1 Message Date
Ragnor Comerford
922666fc70
fix(config,cli): reject --resolved+--show-origin; drop dead ActiveContext.server
config view --resolved --show-origin silently dropped --show-origin; add clap
conflicts_with so the parser rejects the combo loudly. Remove the unused `server`
field from ActiveContext (parsed, always written None, never read) — server-qualified
active context is a V2/V3 concern; don't ship an unfulfilled field.
2026-06-05 12:46:23 +02:00
Ragnor Comerford
c7c82beafb
fix(config): error when OMNIGRAPH_CONFIG names a missing file
An explicitly-set OMNIGRAPH_CONFIG pointing at a missing path was silently dropped
(no global layer, confusing downstream 'graph not found'). Bail naming the file, so an
explicitly-named global config is required like --config; the default ~/.omnigraph path
stays optional.
2026-06-05 12:42:13 +02:00
Ragnor Comerford
d0a44cba23
fix(config): eager-resolve the storage URI so it can't diverge from entry.uri
resolve_all_paths_in_place absolutized the derived entry.uri but left the raw
Storage::Bare/Block uri relative, so config view showed `uri: /abs` beside
`storage: ./rel` and a future reader of storage.uri (V2 region/endpoint threading)
would mis-resolve. Resolve the storage uri too. The eager test now exercises the
storage block form — the coverage gap that let this slip.
2026-06-05 12:40:23 +02:00
Ragnor Comerford
34c0af8469
fix(cli): serialize the resolved locator so config view --resolved omits no field
config view --resolved hand-listed locator fields and dropped an embedded graph's
region/endpoint/policy_file/selected — exactly the fields one debugs an S3 endpoint or
per-graph policy with. Derive Serialize on GraphLocator (internally tagged `kind:`) and
serialize+prune in print_resolved_locator, so every field is reported and a new field
can't be forgotten by hand.
2026-06-05 12:38:29 +02:00
Ragnor Comerford
938248dfcf
refactor(config): drop merge_layers .. so new fields can't be silently unmerged
merge_layers destructured OmnigraphConfig with a `..` rest-pattern, so a config
field added later would be silently dropped from the cross-layer fold (a drift trap).
Bind every field explicitly (intentionally-ignored ones as `_`) so a new field fails
to compile here until it is dispositioned — the compiler is the guard.
2026-06-05 12:36:28 +02:00
Ragnor Comerford
d52cd1d0a3
feat(config): attribute layered config errors to their layer's file
Wrap the global/project layer loads with file context (in global config <path> /
in project config <path>) so a strict v1 error names which file is at fault — a
loud, attributable failure across layers (invariant 13). The single-layer
load_config_in path keeps its raw message (one obvious file). Pairs with the
layer-prefixed deprecation warnings the layered loader already surfaces.
2026-06-05 11:52:14 +02:00
Ragnor Comerford
67a07cfec3
feat(config,cli): omnigraph use active-context graph selection
Add omnigraph use <graph>: validate the graph resolves in the merged config (loud
fail otherwise), then write ~/.omnigraph/state/active.yaml ({graph}). The layered
loader reads it as the State layer between Global and Project, so a bare command
targets the active graph — Project still overrides State, State overrides Global.
The State layer is synthetic (sets defaults.graph only) and raises no version/legacy
warnings.
2026-06-05 11:48:28 +02:00
Ragnor Comerford
2bf3e45d08
feat(cli): omnigraph config view (merged config / origins / locator)
Add config view [--resolved] [--show-origin] [--json] [<graph>]: prints the merged
layered config (null/empty values pruned for readability), or with --show-origin the
layer each field came from (sorted, deterministic), or with --resolved <graph> the
typed GraphLocator (embedded vs remote). Suppress the always-empty legacy
project:/server: blocks on serialize.
2026-06-05 11:41:32 +02:00
Ragnor Comerford
059fbe4c4a
feat(config,cli): global-first layered config load
Add load_layered_config: load the global ~/.omnigraph/config.yaml layer under the
project ./omnigraph.yaml (or --config) layer and merge them, returning the merged
config, its provenance, and per-layer deprecation warnings. The CLI's load_cli_config
now uses it — so a graph/server/defaults defined only in the global config is usable
from any directory with no project file. The server stays single-layer (a deployment
manifest must not pick up ambient $HOME state).

Global and cwd-default files are optional (absent = no layer); an explicit project
--config still errors if missing. base_dir stays the highest loaded layer's config
dir so relative ad-hoc --query paths resolve as before. The CLI test harness pins
OMNIGRAPH_HOME to an empty temp dir so tests never read the developer's real global
config.
2026-06-05 11:31:10 +02:00
Ragnor Comerford
d3ebc29c05
feat(config): layered merge engine + per-field provenance
Add merge_layers(Vec<LoadedLayer>) -> (OmnigraphConfig, Provenance): folds parsed
config layers low->high into one merged config plus a dotted-path origin map.
Settings-objects deep-merge per leaf (a higher layer's Some/non-empty wins, None
inherits); named-resource maps (servers/graphs/aliases/queries) union by key with
a higher layer's entry replacing the lower wholesale (no intra-entry bleed); lists
and scalars replace. Provenance is a sorted BTreeMap side-table, so the merged
OmnigraphConfig shape and all its accessors stay unchanged — only config view reads
it. Pure structure: every layer is already version-gated and path-resolved before
merge. Not yet wired into loading.
2026-06-05 11:19:53 +02:00
Ragnor Comerford
046230be6b
feat(config): eager per-layer path resolution at load
Add resolve_all_paths_in_place, run at the end of load_single_layer (after
normalize_graphs fills entry.uri), rewriting every relative path/URI field —
graph uris, per-graph + top-level policy/queries files, serve.policy, auth.env_file,
query.roots — to absolute against that layer's base_dir. A scheme URI passes
through. This makes a layer self-contained so the upcoming merge composes
absolute-only configs and never mis-resolves a path against the wrong layer's dir.
Factor the join logic into resolve_uri_against/resolve_path_against, reused by the
existing resolve_config_* methods. Behavior-preserving: the per-field resolvers
pass already-absolute values through to the same result.
2026-06-05 11:14:50 +02:00
Ragnor Comerford
47b2a440f9
feat(config): resolve the global config dir/file (OMNIGRAPH_HOME/XDG/~)
Add global_config_dir()/global_config_file() — RFC-002 §5 global-first resolution:
OMNIGRAPH_CONFIG (explicit file) > OMNIGRAPH_HOME (dir) > $XDG_CONFIG_HOME/omnigraph
> ~/.omnigraph, with config.yaml as the file. The resolution is factored through
an injected-env form so it is hermetically testable without mutating process env.
Not yet wired into loading — that is the layered loader.
2026-06-05 11:08:22 +02:00
Ragnor Comerford
349673283a
refactor(config): extract load_single_layer; add Layer/Provenance + dirs
Split the body of load_config_in into a load_single_layer(cwd, path) helper that
loads and fully processes one config file (version-gating, legacy scan, normalize)
— the seam the upcoming layered loader needs — while load_config_in keeps its exact
single-layer semantics (an explicit --config still errors on a missing file).

Add the precedence-ordered Layer enum (Default < Global < State < Project) and a
Provenance side-table newtype for the merge engine, plus the dirs dependency
(already in the lock tree via lance, so zero new crates). No behavior change.
2026-06-05 11:08:09 +02:00
Ragnor Comerford
fff2a852e6
refactor(config): remove top-level policy:/queries: under v1
Top-level `policy:`/`queries:` (the single-graph-mode blocks) are removed under
`version: 1` — policy and stored queries belong on the owning `graphs.<name>`
entry. Both are rejected at load (pointing at the per-graph block) and honored-
but-warned under the legacy schema; the per-graph `graphs.<name>.policy`/`queries`
blocks are nested, so the key scan leaves them untouched. Drops the now-illegal
empty top-level `policy: {}` from the shared CLI test helpers.
2026-06-04 21:43:03 +02:00
Ragnor Comerford
304ac5ec23
refactor(config,cli,server): rename the server: config block to serve:
Introduces the `serve:` host-role block in its RFC-002 shape — `graphs:` is a
served-set list, replacing the single `server.graph` scalar. The legacy
`server:` block is folded into `serve:` under the legacy schema and rejected
under `version: 1` (pointing at the new spelling, noting the scalar->list
change). Serving a true subset (`serve.graphs` with more than one entry) is
rejected with a route-unification hint; one entry (single-graph) and none
(serve all) preserve today's behavior.

Renames the accessors (server_* -> serve_*) and repoints the server boot call
sites; migrates the init scaffold to `serve:`. `servers:` (the remote endpoint
map) is deliberately unaffected — the key scan is exact-match.
2026-06-04 21:38:25 +02:00
Ragnor Comerford
56ff5eb9ec
refactor(config,cli): rename the cli: config block to defaults: under v1
`defaults:` is the canonical CLI/client-defaults block. The legacy spelling
`cli:` is accepted as a serde alias and honored under the legacy schema, but
rejected under `version: 1` (pointing at the new spelling) and flagged by a
deprecation warning. Generalizes the version-gated key scan into
`legacy_top_level_keys`, which now drives both the v1 rejection and the legacy
warnings via a shared migration-hint table. Renames the config accessors
(cli_* -> default_*) and repoints the CLI call sites; migrates the init
scaffold, the example config, and the shared test helpers to `defaults:`.
2026-06-04 21:31:38 +02:00
Ragnor Comerford
5f693ac646
feat(config): reject removed project: key under version: 1
Add a raw top-level key-presence scan (reject_legacy_top_level_keys_under_v1)
so the v1 schema can reject known-but-removed legacy keys that serde_ignored
cannot surface (they stay struct fields for legacy parsing). The first such
key is `project:` — it has no consumer; it is rejected under `version: 1`
(naming the key) and stays honored-but-warned under the legacy schema.

Drop `project:` from the `omnigraph init` scaffold so generated configs load
clean under v1.
2026-06-04 21:20:54 +02:00
Ragnor Comerford
c2db7b5002
fix(cli,server): correct remaining --target references to --graph
L6 renamed the flag but left stragglers. Two were functional: the
`resolve_target_uri` "URI must be provided …" error still named `--target`, and
`docker/entrypoint.sh` passed `--target` to omnigraph-server (which now only
accepts `--graph`) — so the container failed to boot. Both fixed (plus the
entrypoint smoke test's expected args). The rest are code comments across the
config/server/cli crates and tests, and the cheat sheet, swept `--target` →
`--graph`. `--target-branch` (policy explain) is a distinct flag and untouched;
past release notes keep `--target` (accurate for those versions).
2026-06-04 08:56:39 +02:00
Ragnor Comerford
72125c7b4f
feat(config): deprecation_warnings() for the legacy schema and uri:
Add a pure `OmnigraphConfig::deprecation_warnings() -> Vec<String>` so the CLI and
server can surface load-time migration notices while the config crate stays
stdio-agnostic. It flags a config with no `version:` (the legacy schema — gated on
a new `loaded_from_file` so there's nothing to warn about when no `omnigraph.yaml`
exists) and any graph still using the legacy `uri:` key (vs `storage:`/`server:`).
Unused until the next commit wires the emit sites. 4 config tests added.
2026-06-04 08:33:37 +02:00
Ragnor Comerford
03ff9db1b3
fix(config): gate unknown-field strictness on version via serde_ignored
`#[serde(deny_unknown_fields)]` was applied unconditionally as a struct
attribute, so the `version` discriminator did not actually gate strictness: a
legacy (no-`version:`) config was wrongly rejected for any unrecognized key,
contradicting the documented contract (RFC-002 §3: no version = legacy-lenient,
`version: 1` = strict) and the code's own comments.

Make strictness a function of `version`, decided at one point in
`load_config_in`: parse once via `serde_ignored` (collecting ignored fields at
all depths), then reject unknowns only under `version: 1`; legacy tolerates them
(restoring the pre-existing behavior). Drop `deny_unknown_fields` from the two
legacy-spanning structs (`OmnigraphConfig`, `TargetConfig`) and keep it on the
v1-only typed blocks (`StorageBlock`, `ServerEntry`), which have no legacy form.
This removes the double-parse (`check_config_version` is gone — the version is
read from the parsed struct) and makes v1 strictness comprehensive: a misspelled
nested key (e.g. under `cli:`) now errors instead of being silently dropped.

Turns the previous commit's regression test green and pins v1 comprehensive
strictness.
2026-06-03 16:26:49 +02:00
Ragnor Comerford
925ddb7c7f
test(config): legacy config with unknown field must load (red)
A config without a `version:` is the legacy/lenient schema (RFC-002): an
unrecognized top-level key must be tolerated, not rejected. This test pins that
contract and fails today because `deny_unknown_fields` is applied unconditionally
(struct-wide), so the `version` discriminator does not actually gate strictness.

Intentionally red — goes green in the next commit, which gates unknown-field
strictness on `version`.
2026-06-03 16:21:59 +02:00
Ragnor Comerford
f454de9906
style: apply rustfmt across the workspace
Addresses a review finding: cargo fmt --check was failing on the V0/L1/L2 crates (and some pre-existing engine drift). cargo fmt --all --check is now clean. No behavior change.
2026-06-03 15:48:47 +02:00
Ragnor Comerford
b5690d5d8e
feat(config): typed GraphLocator, servers map, resolve_graph (RFC-002 §1/§2)
Add the typed graph schema behind the version gate: per-graph storage: (string or block) XOR server:+graph_id:, a servers: map, and resolve_graph -> typed GraphLocator (local alias -> srv/gid qualified -> cli.graph default). uri stays a defaulted String filled by normalize_graphs from storage/server, so all existing .uri readers are unchanged and this commit is config-only (server reject-Remote is L5). deny_unknown_fields rejects typos; Storage has a hand-rolled string-or-block Deserialize for precise unknown-field errors. region/endpoint parsed but not yet engine-threaded (V2); GraphLocator.graph_id carried but unused on the wire until V2. 26 config tests pass; cargo test --workspace --locked green (per review).
2026-06-03 15:48:06 +02:00
Ragnor Comerford
fd64f8abc4
feat(config): version-gate scaffold for the typed schema
load_config reads the optional top-level 'version:' discriminator and rejects unsupported versions (>1) before parsing; no-version (legacy) and 'version: 1' both still parse via the existing lenient struct. Forward-compat gate (RFC-002 §3) added as a no-op so the typed GraphLocator schema can tighten in following commits without breaking legacy files. 14 config tests pass (incl. version:1 parses, version:2 rejected).
2026-06-03 15:23:32 +02:00
Ragnor Comerford
c5ea4875d5
refactor: extract omnigraph-config crate from omnigraph-server
Move config.rs (schema + loader + resolvers) into a new clean-leaf crate
(serde/serde_yaml/clap/color-eyre). The server aliases it via
`pub use omnigraph_config as config;` so every internal `config::` path and
the `pub use config::{...}` re-exports resolve unchanged. No behavior change;
the 12 config tests move verbatim and pass. First step of RFC-002 V0.
2026-06-02 23:49:40 +02:00