Commit graph

11 commits

Author SHA1 Message Date
aaltshuler
4f8c71fa23 Merge remote-tracking branch 'origin/main' into ragnorc/shaping-config-integration
# Conflicts:
#	crates/omnigraph-cluster/src/lib.rs
#	crates/omnigraph-cluster/src/serve.rs
#	crates/omnigraph-server/src/lib.rs
#	crates/omnigraph-server/src/settings.rs
#	docs/user/clusters/config.md
2026-06-16 04:13:00 +03:00
Andrew Altshuler
b5658dc696
[codex] fix RFC-011 follow-up regressions (#258)
* fix rfc-011 follow-up regressions

* test(cli): remove served schema-apply tests obsoleted by the cluster 409

This PR disables server-side schema apply for cluster-backed serving (409 →
`omnigraph cluster apply`). Two system_local tests still drove *served* schema
apply against a spawned `--cluster` server and asserted the pre-409 behavior, so
they failed under `cargo test --workspace`:

- `local_cli_schema_apply_enforces_engine_layer_policy` — expected a per-actor
  policy `denied`/allow on the served route; the route now 409s for everyone
  before policy runs.
- `local_cli_schema_apply_rejects_stored_query_breakage_before_publish` —
  expected a served apply to reject a stored-query breakage; the route now 409s
  before any apply.

Both exercise a path the PR intentionally removed. Their surviving coverage:
the 409 itself is pinned by `schema_routes::schema_apply_route_refuses_cluster_backed_server_mode`
(asserts 409 + no mutation); stored-query-breakage-before-publish stays covered
by `schema_routes::schema_apply_route_rejects_stored_query_breakage_before_publish`
(single-mode); engine-layer schema_apply Cedar enforcement stays covered by
`policy_engine_chassis`. Remove the obsolete served versions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(server): report the cluster-backed schema-apply 409 after the Cedar gate

The 409 ("schema apply is disabled for cluster-backed serving") fired at the top
of `server_schema_apply`, before `authorize_request`. An authenticated-but-
unauthorized actor therefore learned the server is cluster-backed (409) instead
of getting a normal 403 — leaking topology before authorization, against the
same posture that keeps `GET /graphs` default-deny.

Move the 409 below the Cedar gate so the route reports 401 → 403 → 409: an
unauthorized actor gets 403, and only an actor authorized for `schema_apply`
sees the actionable "use `omnigraph cluster apply`" 409. (An open/unauthenticated
server still 409s, as it has no topology to protect.)

Regression: `schema_apply_route_cluster_backed_denies_unauthorized_actor_before_409`
(POLICY_YAML grants no schema_apply → act-ragnor gets 403, not 409). Addresses the
bot-review finding on #258.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 03:11:43 +03:00
aaltshuler
8d2128438e fix(cli): quote @embed annotation values in schema show so they round-trip
`render_annotations` emitted `@embed` values unquoted — `@embed(title,
model=openai/text-embedding-3-large)`. The parser stores values via
`decode_string_literal` (quotes stripped) and `annotation_kwarg` requires a
quoted `literal`, so the rendered output did not re-parse: a `model` containing
`/` or `-` is not a valid bare token. `schema show` therefore produced schema
text that `schema apply`/lint would reject.

Re-quote the positional value and every kwarg value as string literals, so
`schema show` reproduces `@embed("title", model="openai/text-embedding-3-large")`
and round-trips. Regression: `render_annotations_quotes_values_so_embed_round_trips`
parses the rendered form back through the schema grammar.

Addresses the bot-review finding on #248.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 01:25:27 +03:00
Andrew Altshuler
7c092d3206
feat(cli): add read-only profile list / profile show (RFC-011 D8) (#255)
Inspect the per-operator `~/.omnigraph/config.yaml` scope profiles without
running anything:

- `profile list [--json]` — every profile with its binding (server/cluster/store)
  and default graph; marks the `$OMNIGRAPH_PROFILE`-active one. A malformed
  (zero/two-scope) profile is reported as `invalid: <reason>`, not a hard failure.
- `profile show [<name>] [--json]` — one profile's resolved scope: binding kind +
  target, the resolved endpoint (a server's URL / a cluster's root / the store
  URI), default graph, and output format. With no name, shows the active
  (`$OMNIGRAPH_PROFILE`) profile, else the flat operator defaults.

Both are `local` (Session plane) — they read operator config only, take no
addressing flags. Display reads `OperatorProfile::binding()` + the same
`servers`/`clusters` lookups the scope resolver uses (not `resolve_scope`, which
is capability-gated and can't render all three binding kinds at once), so it is
honest about what a profile binds.

Also: RFC-011 bookkeeping (Status → Accepted; D8 shipped, D11 gated on RFC #219,
D5 deferred) and drop the stale "legacy config actor (RFC-008 window)" comment in
operator.rs (the legacy actor is gone).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:33:01 +03:00
Ragnor Comerford
74476f7f51
feat(compiler): @embed model kwarg in grammar/AST/parser (RFC-012 Phase 3)
Annotations gain optional comma-separated key=value kwargs. Annotation keeps value (existing consumers unchanged) and adds kwargs: BTreeMap with serde(default, skip_serializing_if) so empty kwargs are omitted and existing schemas' IR JSON/hash stay byte-identical. The parser rejects any @embed kwarg other than model. render_annotations shows kwargs. 3 new parser tests.
2026-06-15 21:09:15 +02:00
Andrew Altshuler
0bee746a31
feat(cli)!: excise omnigraph.yaml from the CLI; policy/queries tooling reads --cluster (#251)
The server already dropped omnigraph.yaml (cluster-only boot). This removes the
CLI's last use of the legacy `OmnigraphConfig`: graphs are addressed only via
`--store`/`--server`/`--cluster`/`--profile`/operator defaults, and actor,
output format, and bearer credentials come from `~/.omnigraph/config.yaml`.
After this change no CLI command reads `omnigraph.yaml` except `config migrate`.

Resolvers (helpers.rs): drop every legacy fallback —
- `resolve_actor` → `--as` > `operator.actor` (no `cli.actor`);
- `resolve_read_format` → `--json`/`--format` > alias > `defaults.output`;
- `resolve_branch`/`resolve_read_target` → `--branch` > alias > "main";
- `resolve_uri`/`resolve_cli_graph` → scope path only; an absent address is a
  loud error;
- `resolve_remote_bearer_token` → operator keyed chain + `OMNIGRAPH_BEARER_TOKEN`.
`GraphClient::resolve`/`resolve_with_policy` drop the `&OmnigraphConfig` param;
direct-store access carries no Cedar policy (policy lives in the cluster/server).

Flags (cli.rs): remove `--config` from every data/query command; it stays only
on `cluster *` (the cluster dir) and `config migrate` (the legacy path).

Re-home control-plane tooling to `--cluster` (RFC-011):
- `policy validate|test|explain` source the Cedar bundle from the cluster's
  applied policies; `--graph` picks a graph's bundle; `policy test` takes
  `--tests <file>`;
- `queries list|validate` source the registry + schemas from the cluster
  serving snapshot; `--graph` scopes to one graph;
- `lint` requires `--schema` (offline) or a direct/cluster graph target;
- `schema plan`/`lint` route their graph-target through the shared direct-scope
  resolver so `--store`/`--profile`/`defaults.store` addressing works.

Tests migrate from `omnigraph.yaml` fixtures to `--store`/operator-config/
`--cluster` (converged-cluster fixtures); the now-impossible command-path
RFC-008 tests are deleted (`config migrate` coverage kept). The
`OmnigraphConfig` type, `load_config`/deprecation machinery, and `config
migrate` are removed in a follow-up.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:48:39 +03:00
Andrew Altshuler
45500a690a
refactor(cli): collapse export + graphs-list onto GraphClient (RFC-009 Phase 3c) (#213)
The last two embedded-vs-remote forks move onto the enum, so every such
`if` in the CLI now lives in client.rs — the point of the refactor.

- `export<W: Write>`: the streaming verb 3b deferred (writes to a writer,
  chunks the HTTP response body, rather than returning a DTO). Embedded
  calls db.export_jsonl_to_writer; Remote streams the chunked body through.
  Opens WITHOUT policy (like reads), so it routes via resolve().
- `list_graphs`: remote-only by design (no local enumeration endpoint), so
  the Embedded arm keeps the loud "requires a remote multi-graph server"
  bail verbatim. Routing it through the enum still buys the shared
  resolve() addressing/token preamble the arm hand-rolled.

Retire the now-orphaned execute_export_to_writer /
execute_export_remote_to_writer pair, and sweep two pre-existing dead fns
while in the files: inferred_config_path (helpers.rs) and yaml_string
(output.rs, shadowed by test-local copies).

parity_matrix gains one row, parity_export — the single intended matrix
change in this phase. Export is a JSONL stream, not a single --json doc,
so it compares the two arms' output line-wise (sorted; twin graphs are
byte-copies so rows need no scrubbing). graphs-list gets no row: its
remote-only behavior is a documented exclusion, not an equality case.

Full workspace tests pass; all 12 parity rows green.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:03:45 +03:00
aaltshuler
adbb2a181c refactor(cli): consume omnigraph-api-types directly; unify the load mapping
The CLI's wire-DTO imports repoint from omnigraph_server::api to
omnigraph-api-types (the server's other exports — queries registry,
config types — still come from omnigraph-server). The local Load arm's
inline LoadOutput hand-construction in main.rs is extracted into
load_output_from_result next to load_output_from_tables in output.rs, so
both '-> LoadOutput' mappings (engine LoadResult for local, wire
IngestOutput for remote) live in one place.

Deviation from the plan, with reason: LoadOutput stays CLI-side rather
than moving into the wire-DTO crate — it is a rendered CLI output type,
not an HTTP wire DTO, and its mapping consumes a CLI clap type
(CliLoadMode). The shared crate stays strictly wire DTOs. Shapes
unchanged: the parity matrix passes textually unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 17:05:32 +03:00
aaltshuler
cd1f175396 feat(cli): omnigraph config migrate — the RFC-008 split (stage 2)
Reads a legacy omnigraph.yaml and produces the three-section split: team
half as a ready-to-review cluster.yaml proposal (graphs with TODO schema
pointers — the legacy file never knew schemas — per-graph queries
directories, policies with applies_to bindings), personal half as an
operator-config merge (actor, output/table defaults — OperatorDefaults
gains the two table keys with their cascade hops — remote graphs with
bearer_token_env become servers entries plus a printed login step, and
legacy aliases split per the RFC: content to the catalog as a manual
step, binding to an operator alias), plus a dropped-keys section with
reasons. Touches nothing without --write; with it, the operator merge is
key-level (existing entries always win; prior file backed up), and
cluster.yaml is emitted only when absent (else cluster.yaml.proposed).
--json emits the report structurally.

The completeness contract is a unit test: every top-level key of the
legacy schema must classify somewhere, or the RFC-008 map has a bug.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:32:05 +03:00
aaltshuler
a819ab500e feat(cli): keyed credentials — servers:, the token chain, login/logout (RFC-007 PR 2)
The operator config gains servers: (name -> url; never a token). A remote
command whose URL prefix-matches an operator server resolves its bearer
token through the keyed chain first — OMNIGRAPH_TOKEN_<NAME> env, then the
[<name>] section of ~/.omnigraph/credentials (created 0600 via temp+rename,
#139 finding 7; group/world-readable files refused loudly) — falling
through to the legacy chain unchanged. URL keying makes §D5 rule 3
structural: a token is only ever sent to the server it is keyed to.
Longest-prefix matching with a path-boundary check (http://h:8080 never
matches http://h:8080-evil). Inserting the keyed hop above the legacy chain
is safe by construction — no existing setup can have servers: defined.

omnigraph login <name> stores/rotates one section (token from --token or
one stdin line — the pipe flow keeps secrets out of shell history);
omnigraph logout removes it, idempotently; logging in before declaring the
server warns instead of failing (the gh model).

Coverage: URL-match/no-substring-trap, credentials round-trip preserving
sibling sections, 0600 write + over-permissive refusal, env-name mapping;
the legacy resolve test is now hermetic against a real ~/.omnigraph and
asserts byte-identical legacy behavior with no servers defined; one
spawned-binary e2e walks the whole lifecycle against an authed server:
refusal -> wrong-token login (stdin) -> rotate (--token) -> authorized read
-> env-beats-file -> non-matching-URL negative -> logout revokes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:24:51 +03:00
aaltshuler
916015c416 refactor(cli): split main.rs into cli/helpers/output modules
Verbatim moves: the clap surface (every command/subcommand/arg struct) to
cli.rs, resolution helpers (config/actor/graph/branch/query, remote HTTP,
env/token, scaffolding) to helpers.rs, human/JSON formatting to output.rs,
the in-source test mod to main_tests.rs via #[path]. main.rs (1,184 lines)
keeps main() and the dispatch match. Visibility bumps only; 22 binary
tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:14:27 +03:00