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>
This commit is contained in:
Andrew Altshuler 2026-06-15 03:02:07 +03:00 committed by GitHub
parent a4d08a4184
commit 7eeced3e88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 191 additions and 79 deletions

View file

@ -164,10 +164,10 @@ fn optimize_with_server_flag_errors_wrong_plane() {
let output = output_failure(cli().arg("optimize").arg("--server").arg("prod"));
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("`optimize` is a storage-plane command")
&& stderr.contains("--server/--graph address the data plane and do not apply")
stderr.contains("`optimize` is a direct (storage-native) command")
&& stderr.contains("--server/--graph address a served graph and do not apply")
&& stderr.contains("Use --target <name>, a storage URI, or --cluster <dir> --cluster-graph <id>."),
"wrong-plane guard message not found; got: {stderr}"
"wrong-capability guard message not found; got: {stderr}"
);
}
@ -178,9 +178,9 @@ fn optimize_with_remote_target_errors_storage_plane() {
let output = output_failure(cli().arg("optimize").arg("https://graph.example.invalid"));
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("`optimize` is a storage-plane command and needs direct storage access")
stderr.contains("`optimize` is a direct (storage-native) command and needs direct storage access")
&& stderr.contains("remote server"),
"storage-plane remote-target message not found; got: {stderr}"
"direct remote-target message not found; got: {stderr}"
);
}
@ -584,12 +584,12 @@ query list_people() {
.arg("http://127.0.0.1:8080"),
);
let stderr = String::from_utf8_lossy(&output.stderr);
// RFC-010 Slice 1: the storage-plane verbs now share one declared message
// RFC-010/011: the direct (storage-native) verbs share one declared message
// (was: "query lint is only supported against local graph URIs …").
assert!(
stderr.contains("`lint` is a storage-plane command and needs direct storage access")
stderr.contains("`lint` is a direct (storage-native) command and needs direct storage access")
&& stderr.contains("remote server"),
"storage-plane remote-target message not found; got: {stderr}"
"direct remote-target message not found; got: {stderr}"
);
}

View file

@ -25,22 +25,22 @@ fn version_command_prints_current_cli_version() {
}
#[test]
fn help_groups_commands_by_plane() {
// RFC-010 Slice 2: `--help` clusters commands by plane (declaration order
// in the Command enum) and explains the planes in an after_help legend.
// Pinned lightly — the legend phrase + the cluster ordering — to avoid
// brittle full-text assertions on clap's help body.
fn help_groups_commands_by_capability() {
// RFC-010 Slice 2 / RFC-011 Slice B: `--help` clusters commands (declaration
// order in the Command enum) and explains the capability each needs in an
// after_help legend. Pinned lightly — the legend phrase + the cluster
// ordering — to avoid brittle full-text assertions on clap's help body.
let output = output_success(cli().arg("--help"));
let stdout = stdout_string(&output);
assert!(
stdout.contains("COMMANDS BY PLANE"),
"plane legend (after_help) missing from --help:\n{stdout}"
stdout.contains("COMMANDS BY CAPABILITY"),
"capability legend (after_help) missing from --help:\n{stdout}"
);
// The Commands list precedes the legend, so first occurrences sit in the
// list and must appear in plane order: a data verb, then a storage verb,
// then the control verb.
// list and must appear in order: an `any` data verb, then a `direct` verb,
// then the `control` verb.
let pos = |needle: &str| {
stdout
.find(needle)
@ -48,11 +48,11 @@ fn help_groups_commands_by_plane() {
};
assert!(
pos("query") < pos("optimize"),
"data commands should be listed before storage commands"
"data (any) commands should be listed before direct commands"
);
assert!(
pos("optimize") < pos("cluster"),
"storage commands should be listed before the control command"
"direct commands should be listed before the control command"
);
}
@ -120,9 +120,9 @@ fn schema_plan_with_server_flag_errors_wrong_plane() {
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("`schema plan` is a storage-plane command")
stderr.contains("`schema plan` is a direct (storage-native) command")
&& stderr.contains("Use --target <name>, a storage URI, or --cluster <dir> --cluster-graph <id>."),
"schema plan wrong-plane message not found; got: {stderr}"
"schema plan wrong-capability message not found; got: {stderr}"
);
}