omnigraph/docs/releases/v0.6.0.md
devin-ai-integration[bot] 1a4d2cee97
feat: inline query strings in CLI and HTTP server (#110)
* feat(MR-656): inline query strings in CLI and HTTP server

CLI:
- Add -e / --query-string <STRING> to omnigraph read and omnigraph change
- Exactly one of --query, --query-string, --alias is required (3-way XOR)
- Empty --query-string is rejected with a clear error

HTTP:
- New POST /query (read-only, clean field names: query/name/params/branch/snapshot)
- Mutations on /query are rejected with 400 -- use POST /change instead
- ChangeRequest fields polished: query (alias query_source), name (alias query_name)
- POST /read and POST /change remain byte-compatible for existing clients

Tests:
- cli.rs: -e happy-path on read/change, mutex error vs --query, empty -e rejected
- system_local.rs: inline -e read and -e change exercise the local flow
- system_remote.rs: inline -e read/change over HTTP plus direct /query 200/400
- server.rs: /query 200, /query 400 on mutation, /change legacy field alias
- openapi.rs: new /query path, QueryRequest schema, ChangeRequest field-name polish

Docs: cli.md (-e examples), cli-reference.md (read/change rows), server.md (/query)
Co-Authored-By: Ragnor Comerford <ragnor.comerford@gmail.com>

* feat(MR-656): rename read/change to query/mutate with deprecation signals

HTTP server:
- Add POST /mutate as canonical write endpoint (pairs with POST /query).
- Mark POST /read and POST /change as deprecated. Three-channel signal:
  * OpenAPI: `deprecated: true` on the operation (every codegen flags
    the generated SDK method).
  * RFC 9745: response `Deprecation: true` header on every response.
  * RFC 8288: response `Link: </successor>; rel="successor-version"`
    pointing at /query and /mutate respectively.
- Share business logic across /mutate and /change via run_mutate(); the
  /change wrapper is the only place that adds the deprecation headers.
- ChangeRequest field aliases (query_source/query_name) preserved.
- AliasCommand serde now accepts `query`/`mutate` alongside `read`/`change`.

CLI:
- Promote `omnigraph query` / `omnigraph mutate` to top-level canonical
  subcommands (clap visible_alias keeps `omnigraph read` / `omnigraph
  change` working forever).
- Promote `omnigraph lint` / `omnigraph check` to top-level (was nested
  under `omnigraph query lint`, which is now a deprecated argv shim that
  rewrites to the canonical form).
- Argv-level preprocessing prints a one-line deprecation warning to
  stderr when any legacy spelling is used. Canonical names are silent.

Tests:
- Server: /mutate works, /change emits Deprecation+Link headers, /read
  emits Deprecation+Link headers, /query carries no deprecation signal.
- OpenAPI: /read and /change flagged deprecated; /query and /mutate not.
- CLI: canonical `lint` matches deprecated `query lint` / `query check`
  output; `read` / `change` print deprecation warnings.

Docs:
- cli.md: new canonical examples; "Deprecated names" migration table.
- cli-reference.md: top-level table updated; aliases.<name>.command
  accepts both legacy and canonical spellings.
- server.md: endpoint inventory shows /query and /mutate as canonical
  and /read and /change as deprecated; dedicated section explains the
  three-channel deprecation signal.
- og-cheet-sheet.md: use new `omnigraph lint` / `omnigraph check`.
- openapi.json regenerated.

Migration is purely cosmetic — every deprecated form continues to work
indefinitely; only the spelling changes.

Co-Authored-By: Ragnor Comerford <ragnor.comerford@gmail.com>

* fix(MR-656): address Devin Review findings on /query and /change

Two issues raised by Devin Review on PR #110:

1. `POST /query` mutation-rejection error pointed at the deprecated
   `/change` endpoint instead of the canonical `/mutate`. Fixed in
   three places: the runtime error message in `server_query`, the
   utoipa 400-response description, and the handler doc comment. The
   `QueryRequest` schema docstrings in `api.rs` got the same update so
   the openapi.json bodies match. Server and openapi tests updated.

2. `execute_change_remote` serialized `ChangeRequest` directly, which
   emits the new canonical field names `query` / `name` on the wire.
   `#[serde(alias = "query_source")]` only affects deserialization, so
   a newer CLI talking to an older server would have its `/change`
   POST body fail with "missing field: query_source". Fixed by
   extracting a `legacy_change_request_body` helper that hand-rolls
   the JSON with the legacy keys (`query_source` / `query_name`), the
   same byte-stable contract `execute_read_remote` already uses
   against `/read`. Added two unit tests on the helper to lock the
   wire shape in.

Co-Authored-By: Ragnor Comerford <ragnor.comerford@gmail.com>

* docs(dev): RFC 001 — inline + stored queries, envelope, MCP

Tracked artifact consolidating the design across MR-656 (this branch),
MR-976 (Phase 1 envelope hardening parent, with MR-977/978/979/980
sub-issues), and MR-969 (stored queries + MCP).

Sections:

* Two paths, one engine — inline `/query` + `/mutate` (this PR) coexist
  with stored `/queries/{name}` (MR-969). Same `run_query` / `run_mutate`
  backend (the fold-in landed in the previous commit).
* Request envelope ("before") — Idempotency-Key, If-Match, X-Deadline,
  X-Trace-Id, expect, dry_run, fields. Phase 1 ships the load-bearing
  subset on `/mutate`.
* Response envelope ("after") — audit_id, snapshot_id, commit_id, stats,
  warnings. Closes the provenance loop today's `ChangeOutput` leaves
  open.
* `.gq` pragmas — `@description`, `@returns`, `@mcp`. Source-of-truth
  for the stored-query agent contract; no separate YAML registry.
* Multi-graph MCP — per-graph `/graphs/{id}/mcp/tools` + `/mcp/invoke`.
  Token binds to one graph by default; cross-graph agents loop.
* Cedar split — `read`/`change` for inline, `invoke_query` for stored.
  Operators deny ad-hoc for agent groups while keeping curated tool
  list open.
* Rejected alternatives — per-env override files, compiled bundles,
  tool-name prefixing across graphs, body-field graph dispatch.

Index entry added under "Active Implementation Plans" so future agents
land on the RFC before touching queries / mutations / envelope code.
`scripts/check-agents-md.sh` clean (35 links, 34 docs).

* docs(server): clarify why run_query lacks AppState parameter

run_mutate takes state for workload admission; run_query doesn't because
reads aren't admission-gated today. Mark the asymmetry as intentional and
flag the two future events that would grow the signature: Phase 1's
`expect: { max_rows_scanned: N }` budget (MR-976) or per-actor admission
extending to stored-read invocations (MR-969). Prevents the natural
"make these symmetrical" follow-up.

* refactor(server): run_query / run_mutate take &ResolvedActor

Replace `Option<Extension<ResolvedActor>>` in the helpers with
`Option<&ResolvedActor>`. Saves MR-969's stored-query handler from
wrapping a bare actor in axum's `Extension(...)` before calling.
Handler signatures (`server_query`, `server_read`, `server_mutate`,
`server_change`) keep `Option<Extension<ResolvedActor>>` because that
is what axum injects, and unwrap at the call site with
`actor.as_ref().map(|Extension(actor)| actor)`.

Net: -13/+10 LOC, 89/0 server tests pass.

* docs(releases): v0.6.0 — describe inline + canonical-named queries (MR-656)

Extend the v0.6.0 release notes to cover the third piece of work landing
alongside the graph terminology rename and multi-graph server mode:
canonical-named `POST /query` and `POST /mutate` endpoints, the CLI's
new `-e/--query-string` flag, the top-level promotion of `lint` /
`check`, and the three-channel deprecation signal on `/read` and
`/change` (OpenAPI `deprecated: true` + RFC 9745 + RFC 8288).

Additions:

* Top blurb: "Two pieces" -> "Three pieces" with a bullet describing
  the rename + inline flow.
* Breaking Changes: new "Query / mutation rename" subsection covering
  the `ChangeRequest` field rename (with the back-compat serde aliases
  and the CLI's `legacy_change_request_body` byte-stable wire helper)
  and the `omnigraph query lint` -> `omnigraph lint` move.
* New: 5 bullets — the two endpoints, the CLI subcommands, the `-e`
  flag, the deprecation signal channels, the widened `aliases.<name>.command`
  vocabulary.
* User Impact: one bullet making explicit that the rename is cosmetic
  on the client side and migration is voluntary.
* Documentation: pointers to the updated `server.md` / `cli.md` /
  `cli-reference.md` and the new `docs/dev/rfc-001-queries-envelope-mcp.md`.

+15/-1 lines. `./scripts/check-agents-md.sh` clean.

* refactor(cli): demote `check` from visible_alias to deprecation shim

`omnigraph check` was a clap `visible_alias` on `lint`, advertised in
`--help` as an equivalent canonical name. Per MR-981 §6 (long-form
flags as canonical, short forms as visible aliases), visible aliases
on subcommand names hurt agent CX: agents emit either spelling
depending on training-data drift, and there's no length signal
pointing at the canonical name.

Changes:

* Remove `#[command(visible_alias = "check")]` from the `Lint` variant.
  `omnigraph --help` now shows only `lint`.
* Add bare `check` to `rewrite_deprecated_argv` so `omnigraph check
  <args>` still works — it rewrites to `omnigraph lint <args>` and
  emits a one-line stderr deprecation warning, matching the existing
  pattern for `read` / `change` / `query lint` / `query check`.
* Fix the nested `query check` shim to substitute `check` -> `lint` in
  the rewritten argv (previously it relied on `check` being a
  visible_alias to reach the `Lint` variant).
* New test `deprecated_check_top_level_rewrites_to_lint` covers: bare
  `check` produces identical stdout to `lint`, emits the deprecation
  warning, and `check` does NOT appear as an alias in `omnigraph
  --help`.
* Release notes updated to reflect the deprecation-shim treatment and
  cross-reference MR-981 §6 reasoning.

Cargo / Go users typing `check` still work indefinitely; one stderr
nudge per invocation teaches the canonical name. Agents see only
`lint` in `--help --json` so they emit one canonical form.

67/0 omnigraph-cli tests pass; 39 workspace test suites green.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ragnor Comerford <ragnor.comerford@gmail.com>
Co-authored-by: Ragnor Comerford <hello@ragnor.co>
2026-05-29 13:41:54 +02:00

17 KiB
Raw Permalink Blame History

Omnigraph v0.6.0

Three pieces of work land in this release:

  1. The graph terminology rename (renamed RepoGraph across the Cedar resource model, policy API, and query-lint schema source).
  2. Multi-graph server mode — one omnigraph-server process can now serve 110 graphs concurrently behind cluster routes (/graphs/{graph_id}/...), with per-graph and server-level Cedar policy, read-only GET /graphs enumeration, and CLI parity (omnigraph graphs list).
  3. Inline + canonical-named queries and mutations. New POST /query and POST /mutate endpoints pair with the CLI's new -e/--query-string flag for ad-hoc execution without a temp file. POST /read and POST /change continue serving indefinitely as deprecated aliases that carry RFC 9745 Deprecation: true and RFC 8288 Link: </successor>; rel="successor-version" response headers, plus deprecated: true in openapi.json. Same canonicalization on the CLI: omnigraph query, omnigraph mutate, and top-level omnigraph lint / omnigraph check replace omnigraph read, omnigraph change, and the nested omnigraph query lint / omnigraph query check. Every deprecated spelling remains a visible_alias that warns to stderr once per invocation.

Runtime add/remove (POST /graphs, DELETE /graphs/{id}, omnigraph graphs create) is not in v0.6.0. Operators add or remove graphs by editing omnigraph.yaml and restarting. The first cut of POST /graphs shipped behind an atomic-YAML-rewrite design that we pulled before release once its concurrency guarantees were challenged (flock-on-renamed-inode race, duplicate-check outside the critical section, and an init-cleanup path that could destroy an existing graph's schema on re-init). The correct fix is a Lance-style cluster catalog (reserve → init → publish with recovery sidecars); that work is deferred.

Breaking Changes

Graph terminology rename

  • Renamed the Cedar resource entity from Omnigraph::Repo to Omnigraph::Graph.
  • Renamed policy API terminology from repo_id to graph_id on PolicyCompiler::compile (and on the new PolicyEngine::load_graph / PolicyEngine::load_server loaders described below).
  • Renamed query-lint schema source JSON from "repo" to "graph" for schema_source.kind.

Multi-graph server mode

  • Multi-graph deployments lose flat routes. Single-graph invocation (omnigraph-server <URI>) is unchanged — same flat /snapshot, /read, /branches, etc. Multi-graph deployments serve those routes under /graphs/{graph_id}/...; bare flat paths return 404 in multi mode.
  • ServerConfig shape change (programmatic embedders only): ServerConfig { uri, policy_file } is replaced by ServerConfig { mode: ServerConfigMode }, where ServerConfigMode = Single { uri, policy_file } | Multi { graphs, config_path, server_policy_file }. Callers that use load_server_settings are unaffected; callers that construct ServerConfig directly need to wrap their fields in ServerConfigMode::Single.
  • AppState's routing surface is AppState::routing() -> &GraphRouting, where GraphRouting = Single { handle } | Multi { registry, config_path }. The previous AppState::uri(), AppState::mode(), AppState::registry() accessors and the ServerMode enum are gone — embedders read state.routing() and match on the arm they need. Per-graph URIs live on handle.uri.
  • AppState::new_multi is the new multi-graph constructor. Single-mode new_* / open_* constructors are unchanged.
  • AuthenticatedActor(Arc<str>)ResolvedActor { actor_id, tenant_id, scopes, source } (programmatic embedders only). The struct shape changes, but the HTTP contract — bearer auth and the bearer-derived-actor-identity guarantee — is unchanged. Cluster-mode call sites construct with tenant_id: None, scopes: vec![Scope::Full], source: AuthSource::Static. The new fields are forward-compat seams for future multi-tenant and OAuth deployments; they're inert in this release.
  • PolicyEngine::load(path, graph_id) removed in favor of two kind-typed loaders: PolicyEngine::load_graph(path, graph_id) for per-graph policies and PolicyEngine::load_server(path) for server-level policies. Each loader rejects rules whose action resource_kind() doesn't match the engine kind — operators who put a graph_list rule in a per-graph file (or a read rule in a server file) now get a load-time error instead of a silently-never-matching rule.
  • PolicyRequest::actor_id field removed. Actor identity is now a separate parameter on PolicyEngine::authorize(actor_id, &request). The type system enforces the server-authoritative-actor invariant: actor identity is always sourced from the bearer-token match resolved at the auth boundary; handlers cannot smuggle identity through the request body.
  • Omnigraph::init is strict by default. Initialization at a URI that already holds schema files now errors with OmniError::AlreadyInitialized instead of silently overwriting. Operators who actually want to overwrite use InitOptions { force: true } (CLI: omnigraph init --force). Closes the destructive-cleanup footgun where a failed re-init would delete an existing graph's schema files.
  • Top-level policy.file is rejected in multi-graph server mode. It remains valid for single-graph / CLI-local policy. Multi-graph deployments must move graph rules to graphs.<graph_id>.policy.file and server-scoped graph_list rules to server.policy.file.
  • Open server startup requires explicit opt-in. A server with no bearer tokens and no policy now refuses to start unless passed --unauthenticated or OMNIGRAPH_UNAUTHENTICATED=1.
  • Policy requires bearer tokens. Configuring any policy file without bearer tokens now refuses startup; otherwise every protected request would 401 before Cedar could evaluate it.
  • Tokens without policy default-deny non-read actions. Existing authenticated deployments that relied on writes or admin routes without Cedar policy must add policy rules for those actions.
  • GET /graphs requires server.policy.file in every runtime state. Even --unauthenticated mode keeps server topology closed until the operator explicitly authorizes graph_list.

Query / mutation rename

  • ChangeRequest field rename: query_sourcequery, query_namename. Both legacy names continue to deserialize via #[serde(alias = "...")], so existing clients sending the old JSON keys keep working. CLI remote calls against /change still emit the legacy keys verbatim through the legacy_change_request_body helper so a newer CLI talking to an older server keeps working byte-for-byte.
  • CLI omnigraph query lint / omnigraph query check are now top-level — canonical name is omnigraph lint. The three deprecated invocations (omnigraph query lint, omnigraph query check, and bare omnigraph check) remain as argv-level shims that rewrite to omnigraph lint and print a one-line stderr deprecation warning. check is deliberately not a clap visible_alias on lint — two equivalent canonical names would split agent emissions between them depending on training-data drift, so the deprecation pattern (rewrite + warn) gives one unambiguous canonical name in omnigraph --help.

New

  • Multi-graph mode. Invoke with omnigraph-server --config omnigraph.yaml where the YAML has a non-empty graphs: map and no single-mode selector (no server.graph, no CLI <URI> or --target). At startup the server opens every configured graph in parallel (bounded concurrency, fail-fast).
  • GET /graphs. Lists every registered graph, sorted alphabetically by graph_id. Auth-required when bearer tokens are configured; Cedar-gated by PolicyAction::GraphList against Omnigraph::Server::"root". Returns 405 in single mode. Server-scoped actions require an explicit server.policy.file in every runtime state — the management surface is closed by default even in --unauthenticated mode so that server topology is never exposed without operator opt-in.
  • CLI omnigraph graphs list. Mirrors the HTTP surface. Rejects local URI targets with a clear message — for remote multi-graph servers only.
  • CLI omnigraph init --force. Bypasses the strict-init preflight when an operator deliberately wants to recover from orphan schema files. Does NOT purge existing Lance datasets; recursive deletion needs StorageAdapter::delete_prefix (deferred — see below).
  • Per-graph Cedar policy. Each entry in the graphs: map can carry a policy.file path, loaded at startup via PolicyEngine::load_graph. Cedar's Omnigraph::Graph::"<graph_id>" resource is per-graph; the new Omnigraph::Server::"root" resource governs server-level actions.
  • Server-level Cedar policy. server.policy.file in the config governs the graph_list action on Omnigraph::Server::"root". Required to expose GET /graphs in every runtime state — without a server policy the default-deny posture rejects graph_list, including in --unauthenticated mode.
  • Cedar action vocabulary: graph_list (server-scoped). Runtime graph_create / graph_delete are reserved but not shipped — see "Deferred."
  • Canonical graph URI identity. Server startup normalizes graph root URIs before registry insertion and response output, so aliases such as /tmp/g, /tmp/g/, and file:///tmp/g cannot register as distinct graphs that actually share one Lance root.
  • POST /query and POST /mutate. Canonical inline endpoints. /query rejects mutations with a typed 400 (the D2 rule lives at the URL — read-only contract enforced before execution); body uses the clean { query, name, params, branch, snapshot } shape. /mutate accepts the same shape for mutations. Both available in single mode and per-graph multi mode (/graphs/{id}/query, /graphs/{id}/mutate). Internal call sites share two helpers (run_query, run_mutate) that take decoupled args, not request bodies — the seam MR-969's future stored-query handler plugs into.
  • CLI omnigraph query / omnigraph mutate as top-level canonical subcommands. Pairs with new top-level omnigraph lint (alias check) so query validation no longer sits under omnigraph query.
  • CLI -e, --query-string <GQ> on both omnigraph query and omnigraph mutate. 3-way mutex with --query <path> and --alias <name> — exactly one is required. Empty string rejected. Suits ad-hoc exploration, REPL workflows, and agent tool-use without temp files.
  • Three-channel deprecation signal on /read and /change: OpenAPI deprecated: true on the operation (every codegen flags the generated SDK method), RFC 9745 Deprecation: true response header, and RFC 8288 Link: </query>; rel="successor-version" (or </mutate>) response header. Auto-discoverable; no SDK breakage.
  • omnigraph.yaml aliases.<name>.command now accepts query and mutate as canonical values alongside the legacy read and change. The internal AliasCommand enum retains the legacy variant names so serialized configs stay byte-stable.

Configuration

omnigraph.yaml schema additions (all optional, single-mode unaffected):

server:
  bind: 0.0.0.0:8080
  policy:
    file: ./server-policy.yaml          # server-level Cedar (graph_list)

graphs:
  alpha:
    uri: s3://tenant-bucket/alpha
    policy:
      file: ./policies/alpha.yaml       # per-graph Cedar
  beta:
    uri: s3://tenant-bucket/beta
    # no per-graph policy → engine-layer enforcement is a no-op

Deferred

  • POST /graphs runtime graph creation and CLI omnigraph graphs create. Pulled before release after the YAML-rewrite design's correctness story didn't survive review. A future release will add a managed cluster catalog (Lance-backed reserve → init → publish with recovery sidecars) and re-expose runtime creation on top of it. Until then, operators add graphs by editing omnigraph.yaml and restarting.
  • DELETE /graphs/{id}. Never shipped in v0.6.0; deferred with the same cluster-catalog work.
  • StorageAdapter::delete_prefix. The substrate primitive a managed catalog would need. Will land alongside runtime mutation.
  • omnigraph init --force purging Lance state. Today --force only bypasses the schema-file preflight; recursive deletion of existing Lance datasets needs delete_prefix.
  • X-Actor-Id service delegation forwarding. Needs durable both-actor audit on _graph_commits.lance — out of scope.
  • Hot policy reload. Restart is cheap at N≤10 graphs.

User Impact

  • No on-disk migration is required. Existing .omni graphs from v0.5.0 (and earlier) open cleanly under v0.6.0 — Lance datasets, __manifest, _schema.pg, _schema.ir.json, __schema_state.json, _graph_commits.lance, _graph_commit_recoveries.lance all use unchanged formats. No conversion step.
  • Existing single-graph storage upgrades without migration. Server deployments may need auth/policy config changes: explicitly pass --unauthenticated for local open mode, configure tokens when using policy, and add Cedar policy for non-read authenticated actions.
  • Multi-graph adoption is opt-in. Add a graphs: map to omnigraph.yaml (and remove server.graph) to switch a deployment to multi mode.
  • Cluster routes are breaking for client SDKs targeting multi mode. Generated clients from previous v0.5.0 OpenAPI specs will hit 404 on flat paths against a multi-mode server. Regenerate against the v0.6.0 openapi.json.
  • Supported YAML policy authoring is unchanged. The Cedar Omnigraph::Graph and Omnigraph::Server entities are internally generated by compile_policy_source — operator YAML only references actions and groups.
  • Operators with unsupported raw Cedar policy files should update Omnigraph::Repo resource references to Omnigraph::Graph.
  • Endpoint and CLI rename is cosmetic on the client side. Existing callers on /read, /change, omnigraph read, omnigraph change, and omnigraph query lint keep working — they pick up the Deprecation + Link headers (or stderr deprecation warning on the CLI) so SDKs and proxies can surface the successor name automatically. New integrations should target the canonical names. ChangeRequest field names migrate at the caller's pace — both query_source/query_name and query/name accepted indefinitely.

Migration: single → multi

# Before (v0.5.0 single-mode invocation)
server:
  graph: my-graph
graphs:
  my-graph:
    uri: /var/lib/omnigraph/my-graph
policy:
  file: ./policy.yaml
# After (v0.6.0 multi-mode — drop `server.graph` and the top-level `policy`)
server:
  policy:
    file: ./server-policy.yaml      # NEW: governs GET /graphs
graphs:
  my-graph:
    uri: /var/lib/omnigraph/my-graph
    policy:
      file: ./policy.yaml           # MOVED: was top-level

Same omnigraph.yaml file; restart the server. Clients targeting the old flat routes (/snapshot, /read, …) must update to /graphs/my-graph/snapshot, etc.

To add a new graph after rollout: stop the server, append a new graphs.<id> entry, restart.

Documentation

  • Public docs, CLI help, examples, server docs, and test helpers now consistently use "graph" for the OmniGraph data artifact.
  • GitHub/source repository terminology remains spelled out as "repository" where needed.
  • New: docs/user/cli.md documents omnigraph graphs list; docs/user/server.md documents the multi-graph mode and the cluster route convention; docs/user/policy.md documents the per-graph vs server-scoped action distinction.
  • New: docs/user/server.md documents POST /query / POST /mutate and the three-channel deprecation signal on /read / /change. docs/user/cli.md documents the -e/--query-string flag with examples. docs/user/cli-reference.md shows the canonical CLI verbs (query, mutate, lint, check) with legacy spellings as visible aliases.
  • New: docs/dev/rfc-001-queries-envelope-mcp.md is the cross-cutting design doc for the inline / stored query work that started landing in this release. It sequences the v0.6.x patch series (request/response envelope hardening) and the v0.7.0 stored-query + MCP work.

Test coverage

  • GraphId newtype validation, registry race tests, init failpoints (still reachable from omnigraph init CLI).
  • Mode-inference four-rule matrix, parallel multi-graph startup, cluster routing.
  • Cedar Server resource refactor, backwards-compat for graph-only policies, kind-alignment rejection (server actions in graph files / vice versa).
  • GET /graphs enumeration, 405-in-single-mode, 403-in-Open-mode-without-server-policy, Cedar admin/viewer authorization.
  • Cluster routes with inner path params (/branches/{branch}, /commits/{commit_id}) deserialize correctly under axum 0.8 nested routing.
  • Policy-requires-tokens startup invariant enforced uniformly across single and multi mode.
  • The bearer-auth-derived-actor-identity regression test (client-supplied identity headers are ignored; the server-resolved actor is the only identity Cedar sees) stays green across the entire refactor.