Skip to content

Fix graphs list enumeration via a served-registry scope path#377

Open
ragnorc wants to merge 11 commits into
mainfrom
claude/graphs-list-enumeration-a6h8l5
Open

Fix graphs list enumeration via a served-registry scope path#377
ragnorc wants to merge 11 commits into
mainfrom
claude/graphs-list-enumeration-a6h8l5

Conversation

@ragnorc

@ragnorc ragnorc commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What & why

omnigraph graphs list --server <name> was broken against any populated multi-graph server: the RFC-011 D7 require-graph guard ran inside the shared GraphClient::resolve() preamble and bailed with "pass --graph" before list_graphs() could run — the enumeration command only "enumerated" via its error text, which also broke --json consumers. Both remedies the error suggested made it worse: --graph/default_graph corrupted the URL to /graphs/<id>/graphs (404), and the command's own --uri option was dead (rejected at runtime despite its help text).

This fixes the class, not the instance: graphs list now resolves through a served-registry path (GraphClient::resolve_registry, synchronous by construction, so the async D7 probe structurally cannot run on it and no /graphs/<id> is ever appended); a declarative ScopeFlag × Capability matrix in guard_addressing rejects nonsense flags loudly (graphs list --graph/--store/--as, optimize --as, queries --store, …) instead of silently dropping them or failing late; and the verb's declared capability is threaded into GraphClient resolution, replacing the hardcoded Capability::Any. The dead --uri option is removed.

Backing issue / RFC

  • Fixes an accepted issue: Closes #
  • Implements / is an accepted RFC
  • Trivial fast-lane — no issue/RFC required

None of the above: maintainer-internal process (bug reported and dispositioned in-session). The change preserves accepted RFC-011 behavior — Decision 7's candidate-listing error still fires on data verbs (pinned by system_remote.rs step 4), and Decision 2/9 addressing semantics are unchanged; graphs list is exempted because it is the enumeration D7 performs.

Checklist

  • Change is focused (one logical change: served-registry scope resolution, in six single-purpose commits)
  • Tests added/updated for behavior changes — two red→green pairs (each red commit lands immediately before its fix commit and was confirmed failing with the predicted symptom, including the /graphs/<id>/graphs URL corruption captured verbatim), a ScopeFlag × Capability matrix unit test, scope/client unit tests, and the previously-red #[ignore]d e2e graphs_list_against_multi_graph_server now passes
  • Public docs updated — docs/user/cli/reference.md (new graphs list command-table row, served/direct capability bullets, loud-rejection classes, D7 exemption note) and the CLI after_help legend
  • Reviewed against invariants — no Hard Invariant touched; closes the deny-list "silently dropped flag / failed later with an unrelated message" pattern the guard was built to prevent; every pre-existing pinned error text (guard clauses, remediation tails, alias message, D7 probe text, scope bails) is byte-identical

Notes for reviewers

  • Reading order = commit order. Repo rule 12 pairs are visible in history: test(cli): pin scope-flag applicability matrix (red)fix(cli): declarative ScopeFlag × Capability addressing guard, and test(cli): pin graphs-list served-registry resolution (red)fix(cli): graphs list resolves through the served registry path. The bracketing refactors (registry_client extraction; capability threading) are behavior-neutral.
  • Deliberate tightenings (previously silently ignored or failing late, now loud at the guard): --as on served/direct/local verbs, --store on served/control/local verbs. --as/--store remain consumed on data-plane (any) verbs — the parity harness and direct-write actor attribution depend on that. The served-write --as rejection keeps its own downstream message.
  • default_graph is deliberately ignored on the registry path (an explicit --graph is rejected, a config default is skipped): rejecting the default would make graphs list unusable in any profile that sets one. Commented at resolve_registry.
  • Verification: all nine CLI suites green (parity matrix 12/12, system_local 27/27, 65 bin unit tests), compiler (279) and api-types suites green, docs cross-link check green, plus a live smoke against a policy-enabled cluster server (enumeration, --json on stdout, fast guard rejections, D7 intact on query). The engine/cluster/server crates are byte-untouched by this diff; their suites hit the session's disk allowance locally, so CI is the canonical gate for them.
  • The e2e that pins the fixed behavior remains #[ignore]d (loopback-dependent); the non-socket owners are the guard matrix unit test and the synchronous resolve_registry unit test, which run every PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB


Generated by Claude Code


Note

Medium Risk
CLI addressing and scope resolution changed across many verbs; behavior is heavily tested but wrong flag acceptance could still confuse operators or break scripts.

Overview
Fixes graphs list so it hits the server’s bare GET /graphs registry URL instead of going through GraphClient::resolve(), where the RFC-011 D7 “pick a graph” probe blocked listing and --graph could turn the path into /graphs/<id>/graphs.

graphs list now uses synchronous resolve_registry (shared registry_client with the D7 probe). The dead --uri subcommand flag is removed; scope comes from global --server / --profile only, with default_graph ignored on this path.

guard_addressing is generalized to a ScopeFlag × Capability matrix (server, cluster, graph, store, --as, profile), with per-command refinements for init, cluster maintenance, and read-only control verbs. Misapplied flags fail up front instead of being dropped or failing later.

Each verb’s capability is threaded from main into GraphClient::resolve / resolve_with_policy. Capability::Served scope resolution rejects store- and cluster-bound profiles for registry commands.

Docs and OpenAPI comments are trimmed (internal RFC/MR refs); user CLI reference documents registry scope and the new rejection rules. Tests cover the matrix, registry URL shape, scope errors, and integration cases for graphs list, init, optimize, and queries list.

Reviewed by Cursor Bugbot for commit 7d3a095. Bugbot is set up for automated code reviews on this repo. Configure here.

Greptile Summary

This PR fixes graph enumeration by resolving graphs list at the server registry scope. The main changes are:

  • Adds a synchronous registry client that targets the bare server URL.
  • Introduces a capability-based scope-flag guard for CLI commands.
  • Threads command capabilities through graph-client resolution.
  • Rejects unsupported addressing flags before command dispatch.
  • Updates CLI tests, documentation, and generated API text.

Confidence Score: 5/5

This looks safe to merge.

  • The previously reported explicit flag-loss paths are now rejected before dispatch.
  • Registry resolution uses the bare server URL and skips graph-specific resolution.
  • No additional distinct blocking issue qualifies for this review round.

Important Files Changed

Filename Overview
crates/omnigraph-cli/src/client.rs Adds registry-scoped resolution and threads command capabilities through normal client resolution.
crates/omnigraph-cli/src/planes.rs Replaces ad hoc addressing checks with a capability and scope-flag matrix.
crates/omnigraph-cli/src/main.rs Routes graph enumeration through the registry client and passes capabilities to graph resolvers.
crates/omnigraph-cli/src/scope.rs Rejects store- and cluster-bound scopes for served registry commands.

Reviews (5): Last reviewed commit: "fix(cli): reject --profile on verbs that..." | Re-trigger Greptile

Context used:

  • Context used - AGENTS.md (source)
  • Context used - CLAUDE.md (source)

Comment thread crates/omnigraph-cli/src/planes.rs Outdated
Comment thread crates/omnigraph-cli/src/planes.rs Outdated
ragnorc pushed a commit that referenced this pull request Jul 20, 2026
Red half of the review-follow-up pair for PR #377: the capability-level
matrix still admitted two per-verb silent drops.

- init_with_store_flag_errors_instead_of_ignoring_it: `init` addresses
  its target positionally and never reads --store; today the guard
  admits the flag and init succeeds on the positional URI while
  silently ignoring the store address.
- queries_list_with_as_flag_errors: only `cluster apply`/`approve` read
  the actor among control verbs; today `queries list --as` silently
  drops the identity and fails later asking for a cluster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ef516c0. Configure here.

Comment thread crates/omnigraph-cli/src/planes.rs Outdated
Comment thread crates/omnigraph-cli/src/planes.rs Outdated
ragnorc pushed a commit that referenced this pull request Jul 20, 2026
Green half of the review-follow-up pair. The capability rows for
--store and --as gain the same per-command refinement shape as
accepts_cluster_addressing:

- --store on `direct` excludes `init`, which addresses its target with
  a required positional URI and never reads the flag; the five
  maintenance verbs keep consuming it via resolve_maintenance_uri.
- --as on `control` is limited to `cluster apply`/`cluster approve`,
  the only control verbs that attribute an actor; read-only control
  verbs (status/plan/validate, policy, queries) now reject it loudly.

Closes both PR #377 review findings: the guard no longer admits the
per-verb silent drops it exists to prevent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
claude added 8 commits July 20, 2026 23:09
Extract GraphClient::registry_client(server) — the bare server base URL
(never /graphs/<id>) plus the keyed bearer-token chain — and rewire the
RFC-011 D7 multi-graph probe through it, so GET /graphs has exactly one
addressing owner. Behavior-identical: the probe's None-base early return
was unreachable (resolve_server_flag with a Some server never returns
None), and unknown-server errors still propagate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Red half of the red-green pair for the declarative ScopeFlag x Capability
addressing guard. Predicted failures against current code:

- graphs_list_rejects_store_scope (rewrites the old remote-only-message
  test): today the flag flows through and fails late in the client with
  "requires a remote multi-graph server" instead of the guard rejection.
- graphs_list_rejects_graph_selector: today --graph passes the guard and
  corrupts the URL to /graphs/<id>/graphs, failing with a connection
  error to the wrong route.
- graphs_list_rejects_as_actor: today --as is silently ignored and the
  command fails with "no graph addressed".
- optimize_with_as_flag_errors: today --as is silently ignored on the
  Direct maintenance verb.
- queries_list_with_store_flag_errors: today --store is silently ignored
  and the command fails asking for a cluster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Replace guard_addressing's three ad-hoc flag checks with one exhaustive
ScopeFlag enum and a flag_applies matrix keyed by capability (--cluster
keeps its per-command accepts_cluster_addressing refinement). Adding a
flag or capability now forces a matrix row at compile time, the same
can't-silently-drift construction as the exhaustive command_plane match.

Deliberate tightenings (previously silently ignored or failed late with
an unrelated message):
- served (graphs list) now rejects --graph (it used to corrupt the
  registry URL to /graphs/<id>/graphs), --store, and --as at the guard;
- direct maintenance verbs reject --as (they record no actor);
- control/local verbs reject --store; local verbs reject --graph.

Load-bearing behavior preserved: --store/--as stay consumed on the data
(any) capability — embedded reads accept-and-ignore --as, and the
served-write --as rejection keeps its own downstream message. All
pinned guard message texts are byte-identical; served gains its own
remediation tail. command_label now descends into GraphsCommand
("graphs list"). accepts_server_addressing is folded into the matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Red half of the red-green pair for the served-registry resolution path.
Predicted failures against current code:

- graphs_list_without_scope_needs_server: today the graph-shaped "no
  graph addressed" advice points at flags graphs list cannot consume.
- graphs_list_rejects_store_bound_profile: today the store scope flows
  through to the embedded arm and fails late with "requires a remote
  multi-graph server".
- graphs_list_rejects_cluster_bound_profile: today the hardcoded Any
  capability yields the misleading "not valid for graph data commands"
  error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
The enumeration verb no longer routes through the graph-addressed
GraphClient::resolve() preamble, whose RFC-011 D7 require-graph probe
demanded --graph before list_graphs() could run — and whose URL builder
then corrupted the target to /graphs/<id>/graphs when --graph was
supplied. GraphClient::resolve_registry resolves a server scope
(--server / --profile / defaults.server) straight to the bare base URL
via the shared registry_client; it is synchronous by design, so the
async D7 probe structurally cannot fire on this path, and a scope's
default_graph is deliberately ignored (the registry is server-scoped;
rejecting a config default would make graphs list unusable in any
profile that sets one).

scope_from_binding gains Served arms: a store-bound scope fails with
scope-shaped advice and a cluster-bound scope points at
`omnigraph cluster status` instead of the misleading graph-data error.
The dead `--uri` option on `graphs list` (rejected at runtime by
reject_positional_remote since RFC-011, contradicting its help text) is
removed, and the unreachable embedded arm of list_graphs becomes a
defensive internal-invariant bail.

Turns the commit-4 red tests green; the ignored e2e
graphs_list_against_multi_graph_server step 3 is the end-to-end owner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
resolve()/resolve_with_policy() take the verb's declared capability
(planes::command_capability) instead of hardcoding Capability::Any, so
scope resolution and the addressing guard share one classification.
Behavior-neutral: every current caller is a data-plane (Any) verb —
registry-scoped `graphs list` already resolves via resolve_registry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Red half of the review-follow-up pair for PR #377: the capability-level
matrix still admitted two per-verb silent drops.

- init_with_store_flag_errors_instead_of_ignoring_it: `init` addresses
  its target positionally and never reads --store; today the guard
  admits the flag and init succeeds on the positional URI while
  silently ignoring the store address.
- queries_list_with_as_flag_errors: only `cluster apply`/`approve` read
  the actor among control verbs; today `queries list --as` silently
  drops the identity and fails later asking for a cluster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Green half of the review-follow-up pair. The capability rows for
--store and --as gain the same per-command refinement shape as
accepts_cluster_addressing:

- --store on `direct` excludes `init`, which addresses its target with
  a required positional URI and never reads the flag; the five
  maintenance verbs keep consuming it via resolve_maintenance_uri.
- --as on `control` is limited to `cluster apply`/`cluster approve`,
  the only control verbs that attribute an actor; read-only control
  verbs (status/plan/validate, policy, queries) now reject it loudly.

Closes both PR #377 review findings: the guard no longer admits the
per-verb silent drops it exists to prevent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
@ragnorc
ragnorc force-pushed the claude/graphs-list-enumeration-a6h8l5 branch from f89bf7d to 663422c Compare July 20, 2026 23:12
Comment thread crates/omnigraph-cli/src/planes.rs
Audience-neutrality pass over rendered surfaces (docs rule: prefer
stable public identifiers over organization-specific labels):

- CLI --help text (clap doc comments): drop RFC-011/MR-668/MR-981
  parentheticals from flag and command help.
- omnigraph-server boot error: "removed in RFC-011" -> "has been
  removed".
- OpenAPI document: drop MR-668 from the graphs-list summary and
  RFC-011/RFC-022 from schema descriptions (openapi.json regenerated).
- Engine duplicate-key error: drop the "see MR-957" tail; the in-source
  test now pins the precondition wording instead of the tracker id.

IETF citations (RFC 9745/8288) and code comments are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Comment thread crates/omnigraph-cli/src/planes.rs Outdated
claude added 2 commits July 21, 2026 00:26
init never resolves a scope, so an explicit --profile (which may carry
a store binding) is silently discarded today — the same two-address
ambiguity the init --store rejection closed. Red half of the pair;
predicted symptom: init succeeds on the positional URI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Green half of the pair. The Profile row joins the per-command
refinement pattern: a profile is consumed wherever scope resolution
runs (data/served resolvers, resolve_maintenance_uri,
require_cluster_scope), so `init` (positional target only), the
`cluster` family (--config), and local verbs now reject an explicit
--profile instead of silently discarding it — including any store
binding it carries. The ambient $OMNIGRAPH_PROFILE default remains
ignored by those verbs (config default vs explicit intent, the same
rule as default_graph on the registry path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017fAXpLbhodrJMydMytJZEB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants