From 14736a9ca58e9ad135d1150acb1f26e47a1ab6ca Mon Sep 17 00:00:00 2001 From: Ragnor Comerford Date: Thu, 4 Jun 2026 21:48:49 +0200 Subject: [PATCH] docs: update CLI/server/policy docs for the v1 config schema reshape Rewrites the `omnigraph.yaml` schema reference and the CLI/policy/server config examples for `version: 1`: `cli:` -> `defaults:`, `server:` -> `serve:` (with the `graphs:` list), top-level `policy:`/`queries:` -> per-graph, `project:` removed, `uri:` -> `storage:`. Adds a legacy-spelling migration table and notes the legacy fallbacks. Updates testing.md's config-test inventory. --- docs/dev/testing.md | 9 +++++--- docs/user/cli-reference.md | 32 +++++++++++++++++--------- docs/user/cli.md | 18 +++++++++------ docs/user/policy.md | 47 +++++++++++++++++++------------------- docs/user/server.md | 6 ++--- 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/docs/dev/testing.md b/docs/dev/testing.md index e8122d7..200f384 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -39,9 +39,12 @@ The engine's `tests/` is the principal coverage surface; most graph-shaped behav | `recovery.rs` | Open-time recovery sweep — sidecar I/O, classifier dispatch (NoMovement / RolledPastExpected / UnexpectedAtP1 / UnexpectedMultistep / InvariantViolation), all-or-nothing decision, roll-forward via `ManifestBatchPublisher::publish`, roll-back via `Dataset::restore`, audit row in `_graph_commit_recoveries.lance`, `OpenMode::ReadOnly` skip path | | `composite_flow.rs` | Compositional/narrative end-to-end stories — multi-step flows that compose mechanics covered by other test files. Catches integration regressions where individual operations all pass their unit tests but their composition breaks (sequential merges, post-merge main writes, time-travel through merge DAG, reopen consistency over multi-merge histories). | -**Config & CLI (RFC-002 V1a)** — these live outside the engine `tests/` dir: the -version-gated config-strictness and `deprecation_warnings` tests are inline in -`omnigraph-config` (`src/lib.rs` test module); the `GraphLocator` discriminant + +**Config & CLI (RFC-002 V1a + V1-remainder schema reshape)** — these live outside +the engine `tests/` dir: the version-gated config-strictness, the v1 +legacy-key rejection / legacy-fold renames (`cli:`→`defaults:`, `server:`→`serve:`, +removed `project:` and top-level `policy:`/`queries:`), and `deprecation_warnings` +tests are inline in `omnigraph-config` (`src/lib.rs` test module); the +`GraphLocator` discriminant + graph-identity tests are inline in `omnigraph-cli` (`src/main.rs`) plus `tests/cli.rs` / `tests/system_local.rs`; the server embedded-only rejection tests are in `omnigraph-server/tests/server.rs` (`multi_graph_startup`). diff --git a/docs/user/cli-reference.md b/docs/user/cli-reference.md index a5bb37c..d6eee90 100644 --- a/docs/user/cli-reference.md +++ b/docs/user/cli-reference.md @@ -20,19 +20,20 @@ A reference for the `omnigraph` binary's command surface and `omnigraph.yaml` sc | `run list \| show \| publish \| abort` | transactional run ops | | `schema plan \| apply \| show (alias: get)` | migrations | | `lint` (alias: `check`) | offline / graph-backed query validation. Replaces `query lint` / `query check`, which are kept as deprecated argv-level shims that print a one-line warning and rewrite to `omnigraph lint` | -| `queries validate \| list` | operate on the server-side stored-query registry (the `queries:` block). `validate` type-checks every stored query against the live schema offline (opens the selected graph; exits non-zero on any breakage), catching schema drift without restarting the server; `list` prints the selected registry's query names, MCP exposure, and typed params. For per-graph registries, pass `--graph ` or set `cli.graph`; with no graph selection, `list` shows only top-level `queries:`. Distinct from `lint`, which validates a single `.gq` file | +| `queries validate \| list` | operate on the server-side stored-query registry (the `queries:` block). `validate` type-checks every stored query against the live schema offline (opens the selected graph; exits non-zero on any breakage), catching schema drift without restarting the server; `list` prints the selected registry's query names, MCP exposure, and typed params. For per-graph registries, pass `--graph ` or set `defaults.graph`; with no graph selection, `list` shows only top-level `queries:`. Distinct from `lint`, which validates a single `.gq` file | | `optimize` | non-destructive Lance compaction (skips tables with `Blob` columns; `--json` reports a `skipped` field) | | `cleanup --keep N --older-than 7d --confirm` | destructive version GC | | `embed` | offline JSONL embedding pipeline | -| `policy validate \| test \| explain` | Cedar tooling. Selects `cli.graph`, else `server.graph`, else top-level `policy.file` | +| `policy validate \| test \| explain` | Cedar tooling. Selects `defaults.graph`, else `serve.graphs`, else top-level `policy.file` | | `version` / `-v` | print `omnigraph 0.3.x` | ## `omnigraph.yaml` schema ```yaml version: 1 # omit for the legacy schema (lenient, deprecation-warned); - # `1` = strict: unknown/typo'd keys are rejected at any depth -project: { name } + # `1` = strict: unknown/typo'd keys, and the removed legacy + # keys (`project:`, `cli:`, `server:`, top-level + # `policy:`/`queries:`), are rejected at any depth servers: # named remote endpoints (referenced by graphs.<>.server) : { endpoint: } graphs: @@ -45,6 +46,7 @@ graphs: bearer_token_env: branch: # optional default branch snapshot: # optional read-pinned snapshot + policy: { file: } # per-graph Cedar policy (embedded graphs only) queries: # per-graph stored-query registry (server-role; multi-graph mode) : # key MUST equal the `query ` symbol inside the .gq file: # relative to this config's directory @@ -52,10 +54,11 @@ graphs: expose: true # default true: listed in the MCP catalog (GET /queries); set false to hide (still HTTP-callable) tool_name: # optional MCP tool-name override (defaults to ; # must be unique across exposed queries) -server: - graph: +serve: # host-role serving config (was `server:`) + graphs: [] # served set; one entry = single-graph mode, omit = serve all bind: -cli: + policy: { file: } # server-level Cedar (management endpoints) +defaults: # CLI/client defaults (was `cli:`) graph: branch: output_format: json|jsonl|csv|kv|table @@ -77,12 +80,19 @@ aliases: graph: branch: format: -queries: # top-level registry — applies only to a bare-URI (anonymous) graph; a graph served by name uses its `graphs..queries`. Mirrors top-level `policy`. - : { file: } # mcp.expose defaults to true -policy: - file: ./policy.yaml ``` +**Legacy spellings** (honored only when `version:` is omitted; each emits a load-time deprecation warning, and is rejected under `version: 1`): + +| Legacy | v1 | +|---|---| +| `cli:` | `defaults:` | +| `server:` (`graph:` scalar) | `serve:` (`graphs:` list) | +| top-level `policy:` | `graphs..policy` | +| top-level `queries:` | `graphs..queries` | +| `project:` | removed (no consumer) | +| `uri:` | `storage:` (embedded) / `server:` (remote) | + ## Output formats (`query` command, alias: `read`) - `json` — pretty-printed object with metadata + rows diff --git a/docs/user/cli.md b/docs/user/cli.md index 1433651..7cc894d 100644 --- a/docs/user/cli.md +++ b/docs/user/cli.md @@ -71,14 +71,14 @@ and configure the matching `bearer_token_env` in `omnigraph.yaml`. ## Multi-graph servers (v0.6.0+) -Against a multi-graph server (started with `--config omnigraph.yaml` referencing a non-empty `graphs:` map), use `omnigraph graphs list` to enumerate the registered graphs. The server must configure bearer tokens and `server.policy.file` with a rule that allows `graph_list`; `/graphs` is closed by default even when the server runs with `--unauthenticated`. +Against a multi-graph server (started with `--config omnigraph.yaml` referencing a non-empty `graphs:` map), use `omnigraph graphs list` to enumerate the registered graphs. The server must configure bearer tokens and `serve.policy.file` with a rule that allows `graph_list`; `/graphs` is closed by default even when the server runs with `--unauthenticated`. ```bash OMNIGRAPH_BEARER_TOKEN=admin-token \ omnigraph graphs list --uri http://server.example.com --json ``` -For config-driven clients, set the remote graph's `bearer_token_env` to an environment variable containing a token whose actor is authorized by `server.policy.file`. +For config-driven clients, set the remote graph's `bearer_token_env` to an environment variable containing a token whose actor is authorized by `serve.policy.file`. `list` rejects local URI targets — it's for remote multi-graph servers only. @@ -118,14 +118,18 @@ also pass `--schema`. query roots: ```yaml +version: 1 +servers: + dev: + endpoint: http://127.0.0.1:8080 graphs: local: - uri: ./demo.omni + storage: ./demo.omni dev: - uri: http://127.0.0.1:8080 + server: dev bearer_token_env: OMNIGRAPH_BEARER_TOKEN -cli: +defaults: graph: local branch: main @@ -137,10 +141,10 @@ query: The config file can also define: -- server bind defaults +- `serve:` bind / served-set / server-level policy defaults - auth env files - query aliases for common read and change commands -- `policy.file` for Cedar authorization rules +- per-graph `policy:` for Cedar authorization rules When policy is enabled, `schema apply` is authorized through the `schema_apply` action and is typically limited to admins on protected `main`. diff --git a/docs/user/policy.md b/docs/user/policy.md index 5c4d575..a983ea5 100644 --- a/docs/user/policy.md +++ b/docs/user/policy.md @@ -33,31 +33,30 @@ Server-scoped actions cannot use `branch_scope` or `target_branch_scope` — the In multi mode (`omnigraph.yaml` with a non-empty `graphs:` map), policy files attach at two levels: ```yaml -server: +serve: policy: file: ./server-policy.yaml # server-level: graph_list graphs: alpha: - uri: s3://tenant-bucket/alpha + storage: s3://tenant-bucket/alpha policy: file: ./policies/alpha.yaml # per-graph: read, change, branch_*, schema_apply beta: - uri: s3://tenant-bucket/beta + storage: s3://tenant-bucket/beta # no per-graph policy → no engine-layer Cedar enforcement on beta ``` **Config follows graph identity, not server mode.** A graph served by **name** -(`--graph ` or `server.graph`) uses its own `graphs..policy.file`, -exactly as in multi-graph mode. Top-level `policy.file` applies only to an -**anonymous** graph — one served by a bare `` with no `graphs:` entry. -Serving a **named** graph (single- or multi-graph mode) while top-level -`policy.file` (or `queries:`) is populated **refuses boot**, naming the block, -since the top-level value would otherwise be silently shadowed by the per-graph -block. Move per-graph rules to `graphs..policy.file` and `graph_list` -rules to `server.policy.file`. +(`--graph ` or `serve.graphs`) uses its own `graphs..policy.file`, +and `graph_list` rules go under `serve.policy.file`. (Under the legacy schema — +no `version:` — a top-level `policy.file` applied to an **anonymous** graph +served by a bare ``, and serving a **named** graph while top-level +`policy.file`/`queries:` was populated refused boot to avoid silent shadowing; +`version: 1` removes the top-level blocks entirely in favor of the per-graph and +`serve.policy` blocks.) -Each graph's HTTP request flows through its own per-graph policy. The management endpoint (`GET /graphs`) flows through the server-level policy. When `server.policy.file` is unset, `GET /graphs` is denied in every runtime state, including `--unauthenticated`; with bearer tokens configured, it returns 403 after admission control because `graph_list` is not a `read`-equivalent action. The operator must explicitly authorize via `server-policy.yaml` to expose `/graphs`. +Each graph's HTTP request flows through its own per-graph policy. The management endpoint (`GET /graphs`) flows through the server-level policy. When `serve.policy.file` is unset, `GET /graphs` is denied in every runtime state, including `--unauthenticated`; with bearer tokens configured, it returns 403 after admission control because `graph_list` is not a `read`-equivalent action. The operator must explicitly authorize via `server-policy.yaml` to expose `/graphs`. Example server-level policy: @@ -77,19 +76,21 @@ rules: `omnigraph.yaml`: ```yaml -policy: - file: ./policy.yaml # Cedar rules + groups - tests: ./policy.tests.yaml # declarative test cases - -cli: +version: 1 +graphs: + my-graph: + storage: ./graph.omni + policy: { file: ./policy.yaml } # Cedar rules + groups; `policy.tests.yaml` sibling auto-discovered +defaults: + graph: my-graph actor: act-andrew # default actor for CLI direct-engine writes ``` Each per-graph rule may use at most one of `branch_scope` or `target_branch_scope`. Server-scoped rules (`graph_list`) take neither — they have no branch context. -`cli.actor` is the default actor identity for CLI direct-engine writes +`defaults.actor` is the default actor identity for CLI direct-engine writes when `policy.file` is configured. Override per-invocation with `--as -` (top-level flag) — `--as` wins, otherwise `cli.actor` is used, +` (top-level flag) — `--as` wins, otherwise `defaults.actor` is used, otherwise no actor. With policy configured and neither set, the engine-layer footgun guard intentionally denies the write (silent bypass via "I forgot the actor" is exactly what the guard prevents). Remote @@ -98,9 +99,9 @@ bearer token. ## CLI -Policy tooling resolves its graph like server single-mode policy: `cli.graph` -wins, otherwise `server.graph` is used, otherwise the top-level `policy.file` -is validated/tested/explained as the anonymous policy. +Policy tooling resolves its graph like server single-graph policy: `defaults.graph` +wins, otherwise `serve.graphs` is used, otherwise (legacy schema only) the +top-level `policy.file` is validated/tested/explained as the anonymous policy. - `omnigraph policy validate` — parse + count actors, exit 1 on parse error. - `omnigraph policy test` — run cases in `policy.tests.yaml`, exit 1 on any expectation mismatch. @@ -131,7 +132,7 @@ reaches `authorize_request()` without a matching policy permit. |---|---|---|---| | **Open** | no | no | Every request is permitted. Refuses to start unless `--unauthenticated` or `OMNIGRAPH_UNAUTHENTICATED=1` is set — the operator must explicitly opt in. | | **DefaultDeny** | yes | no | Every authenticated request for an action other than `read` is rejected with HTTP 403. Closes the "tokens but forgot the policy file" trap — an operator who sets up auth and forgot to point at a policy file used to ship the illusion of protection. | -| **PolicyEnabled** | yes | yes | Authenticated requests that reach a configured policy engine are evaluated by Cedar. Server-scoped actions still require `server.policy.file`. | +| **PolicyEnabled** | yes | yes | Authenticated requests that reach a configured policy engine are evaluated by Cedar. Server-scoped actions still require `serve.policy.file`. | The classifier is `classify_server_runtime_state` in `crates/omnigraph-server/src/lib.rs`; it returns `Err` for the "no diff --git a/docs/user/server.md b/docs/user/server.md index a13fe9b..07ed802 100644 --- a/docs/user/server.md +++ b/docs/user/server.md @@ -8,17 +8,17 @@ Axum 0.8 + tokio + utoipa-generated OpenAPI. **Two modes** (v0.6.0+): single-gra `omnigraph-server ` or `omnigraph-server --graph --config omnigraph.yaml`. Routes are flat — `/snapshot`, `/read`, `/branches`, etc. -**Config follows graph identity.** A bare `` is an *anonymous* graph and uses the **top-level** `policy.file` / `queries:`. A graph chosen by **name** (`--graph` / `server.graph`) uses its own `graphs..{policy.file, queries}` — the same block multi-graph mode uses. ⚠️ *Changed from v0.6.0, which always used top-level config in single mode: a named-graph config that puts `policy`/`queries` at top-level now **refuses boot** and points you at `graphs..…` (move the block there). Bare-`` single mode is unchanged.* +**Config follows graph identity.** A graph chosen by **name** (`--graph` / `serve.graphs`) uses its own `graphs..{policy.file, queries}` — the same block multi-graph mode uses. A bare `` is an *anonymous* graph with no per-graph config; under `version: 1` the top-level `policy:` / `queries:` blocks are **removed**, so attach policy or stored queries by giving the graph a named `graphs:` entry. ⚠️ *Under the legacy schema (no `version:`) a bare `` still falls back to top-level `policy.file` / `queries:`, and a named-graph config that puts `policy`/`queries` at top-level **refuses boot**, pointing you at `graphs..…`.* ### Multi-graph mode (v0.6.0+) -`omnigraph-server --config omnigraph.yaml` with a non-empty `graphs:` map and **no** single-mode selector (no `server.graph`, no ``, no `--graph`). The server opens every configured graph in parallel at startup (bounded concurrency = 4, fail-fast on the first open error). Routes are nested under `/graphs/{graph_id}/...`. Bare flat paths return 404 in multi mode. +`omnigraph-server --config omnigraph.yaml` with a non-empty `graphs:` map and **no** single-mode selector (no `serve.graphs`, no ``, no `--graph`). The server opens every configured graph in parallel at startup (bounded concurrency = 4, fail-fast on the first open error). Routes are nested under `/graphs/{graph_id}/...`. Bare flat paths return 404 in multi mode. Mode inference (four-rule matrix): 1. CLI positional `` → single 2. CLI `--graph ` → single -3. `server.graph` in config → single +3. `serve.graphs` in config → single 4. `--config` + non-empty `graphs:` + no single-mode selector → **multi** 5. otherwise → error with migration hint