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.
This commit is contained in:
Ragnor Comerford 2026-06-04 21:48:49 +02:00
parent fff2a852e6
commit 14736a9ca5
No known key found for this signature in database
5 changed files with 65 additions and 47 deletions

View file

@ -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`).

View file

@ -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 <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 <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)
<name>: { endpoint: <https://host:port> }
graphs:
@ -45,6 +46,7 @@ graphs:
bearer_token_env: <ENV_NAME>
branch: <name> # optional default branch
snapshot: <version> # optional read-pinned snapshot
policy: { file: <policy.yaml> } # per-graph Cedar policy (embedded graphs only)
queries: # per-graph stored-query registry (server-role; multi-graph mode)
<query-name>: # key MUST equal the `query <name>` symbol inside the .gq
file: <path-to-.gq> # 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: <name> # optional MCP tool-name override (defaults to <query-name>;
# must be unique across exposed queries)
server:
graph: <name>
serve: # host-role serving config (was `server:`)
graphs: [<name>] # served set; one entry = single-graph mode, omit = serve all
bind: <ip:port>
cli:
policy: { file: <server-policy.yaml> } # server-level Cedar (management endpoints)
defaults: # CLI/client defaults (was `cli:`)
graph: <name>
branch: <name>
output_format: json|jsonl|csv|kv|table
@ -77,12 +80,19 @@ aliases:
graph: <name>
branch: <name>
format: <output-format>
queries: # top-level registry — applies only to a bare-URI (anonymous) graph; a graph served by name uses its `graphs.<id>.queries`. Mirrors top-level `policy`.
<query-name>: { file: <path-to-.gq> } # 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.<name>.policy` |
| top-level `queries:` | `graphs.<name>.queries` |
| `project:` | removed (no consumer) |
| `uri:` | `storage:` (embedded) / `server:` (remote) |
## Output formats (`query` command, alias: `read`)
- `json` — pretty-printed object with metadata + rows

View file

@ -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`.

View file

@ -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 <name>` or `server.graph`) uses its own `graphs.<name>.policy.file`,
exactly as in multi-graph mode. Top-level `policy.file` applies only to an
**anonymous** graph — one served by a bare `<URI>` 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.<graph_id>.policy.file` and `graph_list`
rules to `server.policy.file`.
(`--graph <name>` or `serve.graphs`) uses its own `graphs.<name>.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 `<URI>`, 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
<ACTOR>` (top-level flag) — `--as` wins, otherwise `cli.actor` is used,
<ACTOR>` (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

View file

@ -8,17 +8,17 @@ Axum 0.8 + tokio + utoipa-generated OpenAPI. **Two modes** (v0.6.0+): single-gra
`omnigraph-server <URI>` or `omnigraph-server --graph <name> --config omnigraph.yaml`. Routes are flat — `/snapshot`, `/read`, `/branches`, etc.
**Config follows graph identity.** A bare `<URI>` is an *anonymous* graph and uses the **top-level** `policy.file` / `queries:`. A graph chosen by **name** (`--graph` / `server.graph`) uses its own `graphs.<name>.{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.<name>.…` (move the block there). Bare-`<URI>` single mode is unchanged.*
**Config follows graph identity.** A graph chosen by **name** (`--graph` / `serve.graphs`) uses its own `graphs.<name>.{policy.file, queries}` — the same block multi-graph mode uses. A bare `<URI>` 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 `<URI>` 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.<name>.…`.*
### 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 `<URI>`, 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 `<URI>`, 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 `<URI>` → single
2. CLI `--graph <name>` → 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