omnigraph/crates/omnigraph-cli/src/planes.rs

305 lines
12 KiB
Rust
Raw Normal View History

feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
//! Declared CLI "planes" (RFC-010 Slice 1).
//!
//! Every subcommand belongs to exactly one plane. This classification is the
//! single source of truth the wrong-plane guard consumes — and that later
//! RFC-010 slices (the capability surface, plane-grouped help) will consume
//! too. The `command_plane` match is **exhaustive on purpose**: adding a
//! `Command` variant is a compile error until its plane is declared, so the
//! surface cannot silently drift from the command set.
//!
//! See [docs/dev/rfc-010-cli-planes-restructure.md].
use color_eyre::Result;
use color_eyre::eyre::bail;
use crate::cli::{Cli, Command, QueriesCommand, SchemaCommand};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Plane {
/// Runs against a graph, embedded **or** via `--server` (the `GraphClient`
/// axis). The only plane on which the data-plane addressing flags
/// (`--server`/`--graph`) apply.
Data,
/// Direct storage access; no server. Maintenance + local-only inspection
/// that must work with the server down.
Storage,
/// Operates on a cluster directory, not a graph URI.
Control,
/// Touches no graph at all — session / config / local tooling.
Session,
}
impl std::fmt::Display for Plane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Plane::Data => "data",
Plane::Storage => "storage",
Plane::Control => "control",
Plane::Session => "session",
})
}
}
feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) (#237) * feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) User-facing CLI errors and --help now speak a single "capability" vocabulary — what a command needs — instead of the internal four-plane jargon. Behavior is unchanged: the --server/--graph allow set is identical (the served-graph capabilities `any` ∪ `served` = the old `Data` plane, since `graphs` was already allowed). Only error text and the --help legend change. - planes.rs: add `Capability { Any, Served, Direct, Control, Local }` derived from the existing exhaustive `command_plane` classifier (which stays as the drift guard) plus the one Data→Served refinement (`graphs`). `guard_addressing` now allows `--server`/`--graph` on `{Any, Served}` and rejects elsewhere with a capability-worded message. The mapping reflects *current* behavior (`queries list` → Local, `queries validate` → Direct); it converges to the RFC end-state table when later slices re-route those verbs. - scope.rs: `resolve_scope` takes `Capability` instead of `Plane`, so the whole addressing path speaks one vocabulary; call sites in client.rs (Any) and the 3 maintenance verbs in main.rs (Direct) updated. - helpers.rs: the storage-direct remote rejection reworded to "direct (storage-native) command". - cli.rs: the --help legend is now "COMMANDS BY CAPABILITY". - Tests: the 5 assertions pinning the old plane text updated; added planes.rs unit tests proving the allow set is exactly {Any, Served} (behavior-preservation), the per-verb mapping, and distinct capability phrases. Full omnigraph-cli suite: 225 green (222 + 3 new), zero behavior-test changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(cli): capability vocabulary in the CLI reference + maintenance addressing Rename the reference's "Command planes" section to "Command capabilities" (any/served/direct/control/local), reword the error examples, and update the maintenance doc's addressing note + its section cross-link to match Slice B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 03:02:07 +03:00
/// What a command *needs*, in the user-facing vocabulary (RFC-011). This is the
/// language CLI errors and `--help` speak; `Plane` stays the internal classifier
/// (`Capability` is derived from it, so the two cannot drift).
///
/// - `any` — graph-scoped data; served via a server scope, or direct against a
/// store scope. Accepts `--server`/`--graph`.
/// - `served` — requires a server. Accepts `--server`/`--graph`.
/// - `direct` — storage-native; opens storage directly, never through a server.
/// - `control` — operates on a cluster (control plane).
/// - `local` — addresses no graph at all.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Capability {
Any,
Served,
Direct,
Control,
Local,
}
impl Capability {
/// A human phrase for error messages (`` `optimize` is a {…} command ``).
pub(crate) fn describe(self) -> &'static str {
match self {
Capability::Any => "data",
Capability::Served => "served",
Capability::Direct => "direct (storage-native)",
Capability::Control => "cluster control",
Capability::Local => "local",
}
}
/// `--server`/`--graph` are served-graph addressing: they apply only to the
/// capabilities that reach a graph through a server.
fn accepts_server_addressing(self) -> bool {
matches!(self, Capability::Any | Capability::Served)
}
}
/// The capability a subcommand needs, derived from its `Plane` (the exhaustive
/// classifier) plus the one Data→Served refinement: `graphs` is remote-only.
///
/// This reflects *current enforced behavior*, so messages stay truthful:
/// `queries list` is `Local` (reads config today) and `queries validate` is
/// `Direct` (opens a graph directly today). Both converge to the RFC end-state
/// (served / control) only when later slices re-route them.
pub(crate) fn command_capability(cmd: &Command) -> Capability {
if let Command::Graphs { .. } = cmd {
return Capability::Served;
}
match command_plane(cmd) {
Plane::Data => Capability::Any,
Plane::Storage => Capability::Direct,
Plane::Control => Capability::Control,
Plane::Session => Capability::Local,
}
}
feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
/// The plane a subcommand belongs to. Exhaustive — a new `Command` variant
/// will not compile until classified. Descends into the nested enums where
/// the plane differs per subcommand (`schema plan` is storage while `schema
/// show`/`apply` are data; `queries validate` opens the graph while `queries
/// list` only reads config).
pub(crate) fn command_plane(cmd: &Command) -> Plane {
match cmd {
Command::Query { .. }
| Command::Mutate { .. }
| Command::Load { .. }
| Command::Ingest { .. }
| Command::Branch { .. }
| Command::Snapshot { .. }
| Command::Export { .. }
| Command::Commit { .. }
| Command::Graphs { .. } => Plane::Data,
Command::Schema {
command: SchemaCommand::Show { .. } | SchemaCommand::Apply { .. },
} => Plane::Data,
Command::Schema {
command: SchemaCommand::Plan { .. },
} => Plane::Storage,
Command::Queries {
command: QueriesCommand::Validate { .. },
} => Plane::Storage,
Command::Queries {
command: QueriesCommand::List { .. },
} => Plane::Session,
Command::Init { .. }
| Command::Optimize { .. }
| Command::Repair { .. }
| Command::Cleanup { .. }
| Command::Lint { .. } => Plane::Storage,
Command::Cluster { .. } => Plane::Control,
Command::Policy { .. }
| Command::Embed(_)
| Command::Login { .. }
| Command::Logout { .. }
| Command::Config { .. }
| Command::Version => Plane::Session,
}
}
/// User-facing label for a subcommand (descends one level for the nested
/// families so messages read `schema plan`, `queries validate`, etc.).
pub(crate) fn command_label(cmd: &Command) -> &'static str {
match cmd {
Command::Version => "version",
Command::Login { .. } => "login",
Command::Logout { .. } => "logout",
Command::Config { .. } => "config",
Command::Embed(_) => "embed",
Command::Init { .. } => "init",
Command::Load { .. } => "load",
Command::Ingest { .. } => "ingest",
Command::Branch { .. } => "branch",
Command::Schema { command } => match command {
SchemaCommand::Plan { .. } => "schema plan",
SchemaCommand::Apply { .. } => "schema apply",
SchemaCommand::Show { .. } => "schema show",
},
Command::Lint { .. } => "lint",
Command::Queries { command } => match command {
QueriesCommand::Validate { .. } => "queries validate",
QueriesCommand::List { .. } => "queries list",
},
Command::Snapshot { .. } => "snapshot",
Command::Export { .. } => "export",
Command::Commit { .. } => "commit",
Command::Query { .. } => "query",
Command::Mutate { .. } => "mutate",
Command::Policy { .. } => "policy",
Command::Optimize { .. } => "optimize",
Command::Repair { .. } => "repair",
Command::Cleanup { .. } => "cleanup",
Command::Cluster { .. } => "cluster",
Command::Graphs { .. } => "graphs",
}
}
/// The verbs that address an existing graph through a cluster scope
/// (`--cluster <root> --graph <id>`): the storage-maintenance commands.
/// `init` is storage-plane too but *creates* a graph (cluster graphs are born
/// from `cluster apply`, not `init`), and `schema plan` / `lint` take a
/// positional URI — none consume cluster addressing, so the guard rejects
/// `--cluster`/`--graph` on them rather than silently dropping the flag.
pub(crate) fn accepts_cluster_addressing(cmd: &Command) -> bool {
matches!(
cmd,
Command::Optimize { .. } | Command::Repair { .. } | Command::Cleanup { .. }
)
}
/// Reject a scope-addressing flag (`--server`/`--cluster`/`--graph`) on a verb
/// that cannot consume it, rather than silently dropping it (the old behavior:
/// e.g. `optimize --server prod` dropped `--server` and failed later with an
/// unrelated message). Each flag has a distinct valid surface:
/// - `--server` → served-graph scopes (`any`/`served`);
/// - `--cluster` → the cluster-maintenance verbs (optimize/repair/cleanup);
/// - `--graph` → any multi-graph scope: a served scope *or* a cluster one.
/// RFC-010 Slice 1, generalized for RFC-011 cluster addressing.
feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
pub(crate) fn guard_addressing(cli: &Cli) -> Result<()> {
if cli.server.is_none() && cli.cluster.is_none() && cli.graph.is_none() {
feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
return Ok(());
}
feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) (#237) * feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) User-facing CLI errors and --help now speak a single "capability" vocabulary — what a command needs — instead of the internal four-plane jargon. Behavior is unchanged: the --server/--graph allow set is identical (the served-graph capabilities `any` ∪ `served` = the old `Data` plane, since `graphs` was already allowed). Only error text and the --help legend change. - planes.rs: add `Capability { Any, Served, Direct, Control, Local }` derived from the existing exhaustive `command_plane` classifier (which stays as the drift guard) plus the one Data→Served refinement (`graphs`). `guard_addressing` now allows `--server`/`--graph` on `{Any, Served}` and rejects elsewhere with a capability-worded message. The mapping reflects *current* behavior (`queries list` → Local, `queries validate` → Direct); it converges to the RFC end-state table when later slices re-route those verbs. - scope.rs: `resolve_scope` takes `Capability` instead of `Plane`, so the whole addressing path speaks one vocabulary; call sites in client.rs (Any) and the 3 maintenance verbs in main.rs (Direct) updated. - helpers.rs: the storage-direct remote rejection reworded to "direct (storage-native) command". - cli.rs: the --help legend is now "COMMANDS BY CAPABILITY". - Tests: the 5 assertions pinning the old plane text updated; added planes.rs unit tests proving the allow set is exactly {Any, Served} (behavior-preservation), the per-verb mapping, and distinct capability phrases. Full omnigraph-cli suite: 225 green (222 + 3 new), zero behavior-test changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(cli): capability vocabulary in the CLI reference + maintenance addressing Rename the reference's "Command planes" section to "Command capabilities" (any/served/direct/control/local), reword the error examples, and update the maintenance doc's addressing note + its section cross-link to match Slice B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 03:02:07 +03:00
let capability = command_capability(&cli.command);
feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
let label = command_label(&cli.command);
let cluster_ok = accepts_cluster_addressing(&cli.command);
if cli.server.is_some() && !capability.accepts_server_addressing() {
bail!(
"`{label}` is a {} command; --server addresses a served graph and does not apply.{}",
capability.describe(),
remediation(capability, &cli.command),
);
}
if cli.cluster.is_some() && !cluster_ok {
bail!(
"`{label}` is a {} command; --cluster addresses a cluster-managed graph for \
maintenance (optimize/repair/cleanup) and does not apply.{}",
capability.describe(),
remediation(capability, &cli.command),
);
}
if cli.graph.is_some() && !(capability.accepts_server_addressing() || cluster_ok) {
bail!(
"`{label}` is a {} command; --graph selects a graph within a server or cluster \
scope and does not apply.{}",
capability.describe(),
remediation(capability, &cli.command),
);
}
Ok(())
}
/// The "what to do instead" tail for a wrong-address error, by capability.
/// Includes its own leading space when non-empty so the caller appends it
/// directly — an empty tail (the served-addressing capabilities, which only
/// reach this fn for a misplaced `--cluster`/`--graph`) leaves no trailing space.
fn remediation(capability: Capability, cmd: &Command) -> &'static str {
match capability {
Capability::Direct => match cmd {
Command::Init { .. } => " Pass a storage URI.",
Command::Optimize { .. } | Command::Repair { .. } | Command::Cleanup { .. } => {
" Pass a storage URI, or --cluster <dir> --graph <id>."
}
_ => " Pass a storage URI.",
feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
},
Capability::Control => " It operates on a cluster (pass --config <dir>).",
Capability::Local => " It does not address a graph.",
Capability::Any | Capability::Served => "",
}
feat(cli): RFC-010 Slice 1 — declared plane capability surface + honest addressing (#217) * feat(cli): declared plane capability surface + wrong-plane guard (RFC-010 Slice 1) New `planes.rs` is the single source of truth for which plane each subcommand belongs to (Data / Storage / Control / Session). `command_plane` is an exhaustive match — adding a `Command` variant is a compile error until its plane is declared, so the surface cannot silently drift from the command set. It descends into the nested enums where the plane differs per subcommand (`schema plan` is storage while `schema show/apply` are data; `queries validate` opens the graph while `queries list` reads only config). `guard_addressing` runs once in `main` before dispatch: the data-plane addressing flags `--server`/`--graph` on any non-data verb now fail with one declared, pinned error instead of being silently ignored (`optimize --server prod` previously dropped `--server`). `init`'s message drops the `--target` half since it takes only a positional URI today. Test: `cli_schema_config::schema_plan_with_server_flag_errors_wrong_plane` pins the per-subcommand label, proving the guard descends into the nested enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): storage-plane verbs fail loudly on a remote target (RFC-010 Slice 1) `optimize`/`repair`/`cleanup` switch from `resolve_uri` to `resolve_local_uri`, so a `--target` (or positional URI) that resolves to a remote server now fails with a declared storage-plane message instead of whatever `Omnigraph::open` said about an `http(s)://` URI. The `resolve_local_graph` bail is reworded to that storage-plane message, so every storage verb already on the local resolver (`schema plan`, `queries validate`, `lint`) speaks with one voice. Net: `optimize --target knowledge` resolves to the graph's storage URI and runs embedded; `optimize --target prod` (remote) fails loudly; `optimize --server` is caught earlier by the guard. Positional-URI invocations are unchanged. Tests (pinned strings, per RFC-010's test plan): optimize happy path on a local graph, `optimize --server` wrong-plane error, `optimize <https>` storage-plane error; the existing `query_lint_rejects_http_targets_without_schema` assertion is updated to the new shared message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:45:58 +03:00
}
feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) (#237) * feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) User-facing CLI errors and --help now speak a single "capability" vocabulary — what a command needs — instead of the internal four-plane jargon. Behavior is unchanged: the --server/--graph allow set is identical (the served-graph capabilities `any` ∪ `served` = the old `Data` plane, since `graphs` was already allowed). Only error text and the --help legend change. - planes.rs: add `Capability { Any, Served, Direct, Control, Local }` derived from the existing exhaustive `command_plane` classifier (which stays as the drift guard) plus the one Data→Served refinement (`graphs`). `guard_addressing` now allows `--server`/`--graph` on `{Any, Served}` and rejects elsewhere with a capability-worded message. The mapping reflects *current* behavior (`queries list` → Local, `queries validate` → Direct); it converges to the RFC end-state table when later slices re-route those verbs. - scope.rs: `resolve_scope` takes `Capability` instead of `Plane`, so the whole addressing path speaks one vocabulary; call sites in client.rs (Any) and the 3 maintenance verbs in main.rs (Direct) updated. - helpers.rs: the storage-direct remote rejection reworded to "direct (storage-native) command". - cli.rs: the --help legend is now "COMMANDS BY CAPABILITY". - Tests: the 5 assertions pinning the old plane text updated; added planes.rs unit tests proving the allow set is exactly {Any, Served} (behavior-preservation), the per-verb mapping, and distinct capability phrases. Full omnigraph-cli suite: 225 green (222 + 3 new), zero behavior-test changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(cli): capability vocabulary in the CLI reference + maintenance addressing Rename the reference's "Command planes" section to "Command capabilities" (any/served/direct/control/local), reword the error examples, and update the maintenance doc's addressing note + its section cross-link to match Slice B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 03:02:07 +03:00
#[cfg(test)]
mod tests {
use clap::Parser;
use super::*;
#[test]
fn server_addressing_allowed_exactly_on_any_and_served() {
// The behavior-preservation contract: `--server`/`--graph` apply to the
// served-graph capabilities (`any`, `served`) and nothing else. This is
// the old "Data plane only" allow set, re-expressed — graphs (the one
// Data→Served verb) was already allowed.
assert!(Capability::Any.accepts_server_addressing());
assert!(Capability::Served.accepts_server_addressing());
assert!(!Capability::Direct.accepts_server_addressing());
assert!(!Capability::Control.accepts_server_addressing());
assert!(!Capability::Local.accepts_server_addressing());
}
#[test]
fn command_capability_classifies_representative_verbs() {
let cap = |args: &[&str]| {
command_capability(&Cli::try_parse_from(args).unwrap().command)
};
feat(cli)!: remove legacy data-plane addressing (--target, positional http→remote, --as-on-served) (#238) * feat(cli): --server accepts a literal URL (RFC-011 Decision 2) `resolve_server_flag` now treats a `--server` value containing `://` as a literal base URL (trailing slash trimmed; `--graph` appends `/graphs/<id>`), bypassing the operator-config `servers:` registry; a bare name still resolves through the registry. This is the replacement the upcoming `--uri http(s)://` deprecation points at, and a small ergonomic win on its own (`--server https://host` with no config entry). Token resolution for a literal-URL server falls to the legacy OMNIGRAPH_BEARER_TOKEN chain, same as a positional URL today. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(cli): address the parity-matrix arms with global --store/--server flags Prep for removing the positional-http→remote dispatch. The parity harness addressed both arms with a positional graph right after the verb (`omnigraph <verb> <addr> <args…>`), which only parses for top-level verbs — for nested subcommands (`schema show`, `branch list`, …) the address landed in the subcommand slot and BOTH arms failed identically, so the test passed vacuously (matching exit codes, never comparing output). Address both arms with the global flags instead — local `--store <graph>` (embedded), remote `--server <url>` (served) — appended after the verb + args, valid regardless of nesting. The previously-vacuous nested-verb parity checks now actually compare embedded vs remote (and pass — parity holds), and the remote arm no longer relies on the positional-URL dispatch that's about to be removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli)!: --as on a served write is a hard error (was a silent no-op) A served write resolves the actor server-side from the bearer token, so `--as` could never set identity there — it was silently ignored. It now errors (in the remote write factory, before any HTTP call), pointing the user at removing `--as` or writing directly with `--store`. Reads don't carry `--as`, so this is write-path only. BREAKING for any script that passed `--as` to a remote write (it was a no-op, so behavior is unchanged except the now-explicit error). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli)!: a positional/--uri http(s):// URL no longer dispatches to a server Remote graphs must be addressed with `--server <url>` (or a named server / a profile binding one). A positional or `--uri` `http(s)://` URL on a data verb now errors instead of silently routing to the remote HTTP client — the scheme no longer carries transport semantics. The discriminator is `via_server`: a remote URL produced by a server scope is fine; a remote URL from a positional/`--uri` source is rejected (`reject_positional_remote` in both GraphClient factories). Storage verbs are unaffected — they already reject remote URIs through `resolve_local_graph` with the existing "direct (storage-native)" error. Migrated the gh-host keyed-credential system test to `--server <url>` (the literal URL still prefix-matches the operator server for token resolution). BREAKING: scripts addressing a server by a bare URL must switch to `--server <url>`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli)!: remove the --target flag (use --store / --profile / --server) Removes the legacy named-graph flag and threads its parameter out of the whole resolver chain. `--target` resolved a graph name through `omnigraph.yaml`'s `graphs:` map; its replacements (`--store <uri>`, `--profile <name>`, `--server <name>`) all ship. - Drops the 22 `target` clap fields + the `--cluster` exclusion that named it. - Threads `target`/`cli_target` out of `resolve_uri`/`resolve_cli_graph`/ `resolve_local_graph`/`resolve_local_uri`/`resolve_storage_uri`/ `resolve_remote_bearer_token`/`apply_server_flag`/`execute_query_lint`/ `resolve_selected_graph`/`resolve_registry_selection_for_list`/ `execute_queries_{validate,list}`, the two `GraphClient` factories, and `ScopeFlags`/`ResolvedScope`. - Keeps the shared `OmnigraphConfig::resolve_target_uri` 3-arg (server boot uses it); the CLI passes None for the explicit-target arm. The `cli.graph` default (omnigraph.yaml bare-command fallback) is unchanged — its removal belongs to the omnigraph.yaml excision. - Operator/file aliases that bind a `graph` name still work: the name is now resolved to a URI inline (a positional URI wins). - Error messages and `--graph`/`--server`/`--store` help text no longer name `--target`; the queries-list selection hint points at `cli.graph`. BREAKING. Tests updated (named-target resolution rewritten onto `cli.graph`; positional-URI tests unchanged). Full omnigraph-cli suite green (228). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(cli): drop --target and positional-http addressing; --as-on-served is an error Update the user docs for the legacy data-plane addressing removals: - the CLI `--target` flag is gone — address graphs with a positional URI, `--store`, `--profile`, or `--server <name|url>`; - a positional `http(s)://` URI no longer dispatches to a server (use `--server`); - `--as` on a served write is now rejected (was a silent no-op). Touches cli/reference.md (addressing intro, capability table, error examples, scopes), cli/index.md (the remote-read example → --server), operations/maintenance + policy, and the cluster docs' data-plane load guidance. The server's own `--target` boot flag is unchanged (server.md untouched). Also fixes a pre-existing broken maintenance link in search/indexes.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): --store is loudly exclusive with a positional URI / --server; test graphs→Served Address two Greptile findings on the RFC-011 slices: - Slice A (P1): `--store` combined with a positional URI silently dropped the URI (`scope.rs` did `store.or(uri)`); `--store` + `--server` errored with a misleading "positional URI" message. Now both combinations fail loudly with a declared `--store is exclusive with a positional URI and --server` error. - Slice B (P2): the `command_capability` unit test never exercised the one Data→Served refinement (`graphs`); added the assertion so deleting that guard can't pass silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 04:29:16 +03:00
// The one Data→Served refinement — if the `graphs` guard were deleted,
// every other assertion here would still pass.
assert_eq!(cap(&["omnigraph", "graphs", "list"]), Capability::Served);
feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) (#237) * feat(cli): RFC-011 Slice B — capability vocabulary (any/served/direct/control/local) User-facing CLI errors and --help now speak a single "capability" vocabulary — what a command needs — instead of the internal four-plane jargon. Behavior is unchanged: the --server/--graph allow set is identical (the served-graph capabilities `any` ∪ `served` = the old `Data` plane, since `graphs` was already allowed). Only error text and the --help legend change. - planes.rs: add `Capability { Any, Served, Direct, Control, Local }` derived from the existing exhaustive `command_plane` classifier (which stays as the drift guard) plus the one Data→Served refinement (`graphs`). `guard_addressing` now allows `--server`/`--graph` on `{Any, Served}` and rejects elsewhere with a capability-worded message. The mapping reflects *current* behavior (`queries list` → Local, `queries validate` → Direct); it converges to the RFC end-state table when later slices re-route those verbs. - scope.rs: `resolve_scope` takes `Capability` instead of `Plane`, so the whole addressing path speaks one vocabulary; call sites in client.rs (Any) and the 3 maintenance verbs in main.rs (Direct) updated. - helpers.rs: the storage-direct remote rejection reworded to "direct (storage-native) command". - cli.rs: the --help legend is now "COMMANDS BY CAPABILITY". - Tests: the 5 assertions pinning the old plane text updated; added planes.rs unit tests proving the allow set is exactly {Any, Served} (behavior-preservation), the per-verb mapping, and distinct capability phrases. Full omnigraph-cli suite: 225 green (222 + 3 new), zero behavior-test changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(cli): capability vocabulary in the CLI reference + maintenance addressing Rename the reference's "Command planes" section to "Command capabilities" (any/served/direct/control/local), reword the error examples, and update the maintenance doc's addressing note + its section cross-link to match Slice B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 03:02:07 +03:00
assert_eq!(cap(&["omnigraph", "optimize", "graph.omni"]), Capability::Direct);
assert_eq!(cap(&["omnigraph", "schema", "plan", "--schema", "s.pg", "graph.omni"]), Capability::Direct);
assert_eq!(cap(&["omnigraph", "cluster", "status", "--config", "."]), Capability::Control);
assert_eq!(cap(&["omnigraph", "version"]), Capability::Local);
assert_eq!(cap(&["omnigraph", "queries", "list"]), Capability::Local);
}
#[test]
fn every_capability_describes_distinctly() {
let phrases = [
Capability::Any.describe(),
Capability::Served.describe(),
Capability::Direct.describe(),
Capability::Control.describe(),
Capability::Local.describe(),
];
for (i, a) in phrases.iter().enumerate() {
assert!(!a.is_empty());
for b in &phrases[i + 1..] {
assert_ne!(a, b);
}
}
}
}