2026-06-11 15:16:51 +03:00
|
|
|
//! init/config scaffolding, schema plan/apply, graphs listing, version.
|
|
|
|
|
//! Moved verbatim from tests/cli.rs in the modularization.
|
|
|
|
|
|
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
|
|
use lance::index::DatasetIndexExt;
|
|
|
|
|
use omnigraph::db::{Omnigraph, ReadTarget};
|
|
|
|
|
use serde_json::Value;
|
|
|
|
|
use tempfile::tempdir;
|
|
|
|
|
|
|
|
|
|
mod support;
|
|
|
|
|
|
|
|
|
|
use support::*;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn version_command_prints_current_cli_version() {
|
|
|
|
|
let output = output_success(cli().arg("version"));
|
|
|
|
|
let stdout = stdout_string(&output);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
stdout.trim(),
|
|
|
|
|
format!("omnigraph {}", env!("CARGO_PKG_VERSION"))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
feat(cli): plane-grouped --help + clap 4.6.1 (RFC-010 Slice 2) (#220)
* chore(deps): bump clap to 4.6.1
Workspace constraint "4" → "4.6" so the resolver picks up the 4.6 line
(a plain `cargo update` stayed on 4.5.x). clap 4.5.58 → 4.6.1
(clap_builder 4.6.0, clap_derive 4.6.1). Minor bump, no API breakage; the
workspace builds and all CLI suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): group --help by plane (RFC-010 Slice 2)
Slice 1 declared the planes (the command_plane table + the wrong-plane
guard); this makes them visible in `--help`. clap can't print labeled
heading rows between subcommand groups (verified against the source —
help_heading is args-only, {subcommands} is one flat block), so per the
chosen approach: cluster + legend.
- Reorder the `Command` enum into plane bands (clap lists subcommands in
declaration order): data (query, mutate, load, branch, snapshot, export,
commit, schema, graphs) → storage/local-graph ops (init, optimize,
repair, cleanup, lint, queries) → control (cluster) → session (policy,
embed, login, logout, config, version). No magic display_order numbers —
the source order IS the help order, with band comments for readers. The
band placement matches `command_plane` (lint/queries are storage-plane:
they reject --server), so the help grouping and the guard agree.
- Add an `after_help` legend on `Cli` naming the planes. Written to
describe the planes (not enumerate every command) so it doesn't drift.
Help-polish (post-review): hide the deprecated `ingest` from the list
(still a valid command); trim the long `login` and `--as` descriptions to
one line each so the columns don't blow up.
The behavioral source of truth for planes stays `planes::command_plane`;
this ordering is its cosmetic counterpart.
Test: `help_groups_commands_by_plane` pins the legend phrase + the cluster
ordering (query < optimize < cluster). Doc: a line under cli-reference's
*Command planes* section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): qualify mixed-plane commands in the --help legend
Addresses the Greptile P2 on #220: the legend placed `schema` entirely in
Data and `queries` entirely in Storage, but per `command_plane` the
subcommands differ — `schema plan` is storage-plane (rejects --server) and
`queries list` is session (no graph). A user reading the legend then running
`schema plan --server` would hit a rejection contradicting it. The Commands
list is one entry per top-level command (necessarily coarse), so the legend
carries the nuance: `schema [plan: storage]` and `queries [list: session]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:49:40 +03:00
|
|
|
#[test]
|
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
|
|
|
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.
|
feat(cli): plane-grouped --help + clap 4.6.1 (RFC-010 Slice 2) (#220)
* chore(deps): bump clap to 4.6.1
Workspace constraint "4" → "4.6" so the resolver picks up the 4.6 line
(a plain `cargo update` stayed on 4.5.x). clap 4.5.58 → 4.6.1
(clap_builder 4.6.0, clap_derive 4.6.1). Minor bump, no API breakage; the
workspace builds and all CLI suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): group --help by plane (RFC-010 Slice 2)
Slice 1 declared the planes (the command_plane table + the wrong-plane
guard); this makes them visible in `--help`. clap can't print labeled
heading rows between subcommand groups (verified against the source —
help_heading is args-only, {subcommands} is one flat block), so per the
chosen approach: cluster + legend.
- Reorder the `Command` enum into plane bands (clap lists subcommands in
declaration order): data (query, mutate, load, branch, snapshot, export,
commit, schema, graphs) → storage/local-graph ops (init, optimize,
repair, cleanup, lint, queries) → control (cluster) → session (policy,
embed, login, logout, config, version). No magic display_order numbers —
the source order IS the help order, with band comments for readers. The
band placement matches `command_plane` (lint/queries are storage-plane:
they reject --server), so the help grouping and the guard agree.
- Add an `after_help` legend on `Cli` naming the planes. Written to
describe the planes (not enumerate every command) so it doesn't drift.
Help-polish (post-review): hide the deprecated `ingest` from the list
(still a valid command); trim the long `login` and `--as` descriptions to
one line each so the columns don't blow up.
The behavioral source of truth for planes stays `planes::command_plane`;
this ordering is its cosmetic counterpart.
Test: `help_groups_commands_by_plane` pins the legend phrase + the cluster
ordering (query < optimize < cluster). Doc: a line under cli-reference's
*Command planes* section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): qualify mixed-plane commands in the --help legend
Addresses the Greptile P2 on #220: the legend placed `schema` entirely in
Data and `queries` entirely in Storage, but per `command_plane` the
subcommands differ — `schema plan` is storage-plane (rejects --server) and
`queries list` is session (no graph). A user reading the legend then running
`schema plan --server` would hit a rejection contradicting it. The Commands
list is one entry per top-level command (necessarily coarse), so the legend
carries the nuance: `schema [plan: storage]` and `queries [list: session]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:49:40 +03:00
|
|
|
let output = output_success(cli().arg("--help"));
|
|
|
|
|
let stdout = stdout_string(&output);
|
|
|
|
|
|
|
|
|
|
assert!(
|
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
|
|
|
stdout.contains("COMMANDS BY CAPABILITY"),
|
|
|
|
|
"capability legend (after_help) missing from --help:\n{stdout}"
|
feat(cli): plane-grouped --help + clap 4.6.1 (RFC-010 Slice 2) (#220)
* chore(deps): bump clap to 4.6.1
Workspace constraint "4" → "4.6" so the resolver picks up the 4.6 line
(a plain `cargo update` stayed on 4.5.x). clap 4.5.58 → 4.6.1
(clap_builder 4.6.0, clap_derive 4.6.1). Minor bump, no API breakage; the
workspace builds and all CLI suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): group --help by plane (RFC-010 Slice 2)
Slice 1 declared the planes (the command_plane table + the wrong-plane
guard); this makes them visible in `--help`. clap can't print labeled
heading rows between subcommand groups (verified against the source —
help_heading is args-only, {subcommands} is one flat block), so per the
chosen approach: cluster + legend.
- Reorder the `Command` enum into plane bands (clap lists subcommands in
declaration order): data (query, mutate, load, branch, snapshot, export,
commit, schema, graphs) → storage/local-graph ops (init, optimize,
repair, cleanup, lint, queries) → control (cluster) → session (policy,
embed, login, logout, config, version). No magic display_order numbers —
the source order IS the help order, with band comments for readers. The
band placement matches `command_plane` (lint/queries are storage-plane:
they reject --server), so the help grouping and the guard agree.
- Add an `after_help` legend on `Cli` naming the planes. Written to
describe the planes (not enumerate every command) so it doesn't drift.
Help-polish (post-review): hide the deprecated `ingest` from the list
(still a valid command); trim the long `login` and `--as` descriptions to
one line each so the columns don't blow up.
The behavioral source of truth for planes stays `planes::command_plane`;
this ordering is its cosmetic counterpart.
Test: `help_groups_commands_by_plane` pins the legend phrase + the cluster
ordering (query < optimize < cluster). Doc: a line under cli-reference's
*Command planes* section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): qualify mixed-plane commands in the --help legend
Addresses the Greptile P2 on #220: the legend placed `schema` entirely in
Data and `queries` entirely in Storage, but per `command_plane` the
subcommands differ — `schema plan` is storage-plane (rejects --server) and
`queries list` is session (no graph). A user reading the legend then running
`schema plan --server` would hit a rejection contradicting it. The Commands
list is one entry per top-level command (necessarily coarse), so the legend
carries the nuance: `schema [plan: storage]` and `queries [list: session]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:49:40 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// The Commands list precedes the legend, so first occurrences sit in the
|
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
|
|
|
// list and must appear in order: an `any` data verb, then a `direct` verb,
|
|
|
|
|
// then the `control` verb.
|
feat(cli): plane-grouped --help + clap 4.6.1 (RFC-010 Slice 2) (#220)
* chore(deps): bump clap to 4.6.1
Workspace constraint "4" → "4.6" so the resolver picks up the 4.6 line
(a plain `cargo update` stayed on 4.5.x). clap 4.5.58 → 4.6.1
(clap_builder 4.6.0, clap_derive 4.6.1). Minor bump, no API breakage; the
workspace builds and all CLI suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): group --help by plane (RFC-010 Slice 2)
Slice 1 declared the planes (the command_plane table + the wrong-plane
guard); this makes them visible in `--help`. clap can't print labeled
heading rows between subcommand groups (verified against the source —
help_heading is args-only, {subcommands} is one flat block), so per the
chosen approach: cluster + legend.
- Reorder the `Command` enum into plane bands (clap lists subcommands in
declaration order): data (query, mutate, load, branch, snapshot, export,
commit, schema, graphs) → storage/local-graph ops (init, optimize,
repair, cleanup, lint, queries) → control (cluster) → session (policy,
embed, login, logout, config, version). No magic display_order numbers —
the source order IS the help order, with band comments for readers. The
band placement matches `command_plane` (lint/queries are storage-plane:
they reject --server), so the help grouping and the guard agree.
- Add an `after_help` legend on `Cli` naming the planes. Written to
describe the planes (not enumerate every command) so it doesn't drift.
Help-polish (post-review): hide the deprecated `ingest` from the list
(still a valid command); trim the long `login` and `--as` descriptions to
one line each so the columns don't blow up.
The behavioral source of truth for planes stays `planes::command_plane`;
this ordering is its cosmetic counterpart.
Test: `help_groups_commands_by_plane` pins the legend phrase + the cluster
ordering (query < optimize < cluster). Doc: a line under cli-reference's
*Command planes* section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): qualify mixed-plane commands in the --help legend
Addresses the Greptile P2 on #220: the legend placed `schema` entirely in
Data and `queries` entirely in Storage, but per `command_plane` the
subcommands differ — `schema plan` is storage-plane (rejects --server) and
`queries list` is session (no graph). A user reading the legend then running
`schema plan --server` would hit a rejection contradicting it. The Commands
list is one entry per top-level command (necessarily coarse), so the legend
carries the nuance: `schema [plan: storage]` and `queries [list: session]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:49:40 +03:00
|
|
|
let pos = |needle: &str| {
|
|
|
|
|
stdout
|
|
|
|
|
.find(needle)
|
|
|
|
|
.unwrap_or_else(|| panic!("'{needle}' not found in --help:\n{stdout}"))
|
|
|
|
|
};
|
|
|
|
|
assert!(
|
|
|
|
|
pos("query") < pos("optimize"),
|
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
|
|
|
"data (any) commands should be listed before direct commands"
|
feat(cli): plane-grouped --help + clap 4.6.1 (RFC-010 Slice 2) (#220)
* chore(deps): bump clap to 4.6.1
Workspace constraint "4" → "4.6" so the resolver picks up the 4.6 line
(a plain `cargo update` stayed on 4.5.x). clap 4.5.58 → 4.6.1
(clap_builder 4.6.0, clap_derive 4.6.1). Minor bump, no API breakage; the
workspace builds and all CLI suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): group --help by plane (RFC-010 Slice 2)
Slice 1 declared the planes (the command_plane table + the wrong-plane
guard); this makes them visible in `--help`. clap can't print labeled
heading rows between subcommand groups (verified against the source —
help_heading is args-only, {subcommands} is one flat block), so per the
chosen approach: cluster + legend.
- Reorder the `Command` enum into plane bands (clap lists subcommands in
declaration order): data (query, mutate, load, branch, snapshot, export,
commit, schema, graphs) → storage/local-graph ops (init, optimize,
repair, cleanup, lint, queries) → control (cluster) → session (policy,
embed, login, logout, config, version). No magic display_order numbers —
the source order IS the help order, with band comments for readers. The
band placement matches `command_plane` (lint/queries are storage-plane:
they reject --server), so the help grouping and the guard agree.
- Add an `after_help` legend on `Cli` naming the planes. Written to
describe the planes (not enumerate every command) so it doesn't drift.
Help-polish (post-review): hide the deprecated `ingest` from the list
(still a valid command); trim the long `login` and `--as` descriptions to
one line each so the columns don't blow up.
The behavioral source of truth for planes stays `planes::command_plane`;
this ordering is its cosmetic counterpart.
Test: `help_groups_commands_by_plane` pins the legend phrase + the cluster
ordering (query < optimize < cluster). Doc: a line under cli-reference's
*Command planes* section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): qualify mixed-plane commands in the --help legend
Addresses the Greptile P2 on #220: the legend placed `schema` entirely in
Data and `queries` entirely in Storage, but per `command_plane` the
subcommands differ — `schema plan` is storage-plane (rejects --server) and
`queries list` is session (no graph). A user reading the legend then running
`schema plan --server` would hit a rejection contradicting it. The Commands
list is one entry per top-level command (necessarily coarse), so the legend
carries the nuance: `schema [plan: storage]` and `queries [list: session]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:49:40 +03:00
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
pos("optimize") < pos("cluster"),
|
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
|
|
|
"direct commands should be listed before the control command"
|
feat(cli): plane-grouped --help + clap 4.6.1 (RFC-010 Slice 2) (#220)
* chore(deps): bump clap to 4.6.1
Workspace constraint "4" → "4.6" so the resolver picks up the 4.6 line
(a plain `cargo update` stayed on 4.5.x). clap 4.5.58 → 4.6.1
(clap_builder 4.6.0, clap_derive 4.6.1). Minor bump, no API breakage; the
workspace builds and all CLI suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): group --help by plane (RFC-010 Slice 2)
Slice 1 declared the planes (the command_plane table + the wrong-plane
guard); this makes them visible in `--help`. clap can't print labeled
heading rows between subcommand groups (verified against the source —
help_heading is args-only, {subcommands} is one flat block), so per the
chosen approach: cluster + legend.
- Reorder the `Command` enum into plane bands (clap lists subcommands in
declaration order): data (query, mutate, load, branch, snapshot, export,
commit, schema, graphs) → storage/local-graph ops (init, optimize,
repair, cleanup, lint, queries) → control (cluster) → session (policy,
embed, login, logout, config, version). No magic display_order numbers —
the source order IS the help order, with band comments for readers. The
band placement matches `command_plane` (lint/queries are storage-plane:
they reject --server), so the help grouping and the guard agree.
- Add an `after_help` legend on `Cli` naming the planes. Written to
describe the planes (not enumerate every command) so it doesn't drift.
Help-polish (post-review): hide the deprecated `ingest` from the list
(still a valid command); trim the long `login` and `--as` descriptions to
one line each so the columns don't blow up.
The behavioral source of truth for planes stays `planes::command_plane`;
this ordering is its cosmetic counterpart.
Test: `help_groups_commands_by_plane` pins the legend phrase + the cluster
ordering (query < optimize < cluster). Doc: a line under cli-reference's
*Command planes* section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): qualify mixed-plane commands in the --help legend
Addresses the Greptile P2 on #220: the legend placed `schema` entirely in
Data and `queries` entirely in Storage, but per `command_plane` the
subcommands differ — `schema plan` is storage-plane (rejects --server) and
`queries list` is session (no graph). A user reading the legend then running
`schema plan --server` would hit a rejection contradicting it. The Commands
list is one entry per top-level command (necessarily coarse), so the legend
carries the nuance: `schema [plan: storage]` and `queries [list: session]`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:49:40 +03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 15:16:51 +03:00
|
|
|
#[test]
|
|
|
|
|
fn init_creates_graph_successfully_on_missing_local_directory() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema = fixture("test.pg");
|
|
|
|
|
|
|
|
|
|
let output = output_success(cli().arg("init").arg("--schema").arg(&schema).arg(&graph));
|
|
|
|
|
let stdout = stdout_string(&output);
|
|
|
|
|
|
|
|
|
|
assert!(stdout.contains("initialized"));
|
|
|
|
|
assert!(graph.join("_schema.pg").exists());
|
|
|
|
|
assert!(graph.join("__manifest").exists());
|
2026-06-11 23:34:04 +03:00
|
|
|
// RFC-008 stage 3: init no longer scaffolds the legacy config file.
|
|
|
|
|
assert!(!temp.path().join("omnigraph.yaml").exists());
|
2026-06-11 15:16:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_plan_json_reports_supported_additive_change() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("next.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
|
|
|
" age: I32?\n}",
|
|
|
|
|
" age: I32?\n nickname: String?\n}",
|
|
|
|
|
);
|
|
|
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("plan")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(payload["supported"], true);
|
|
|
|
|
assert_eq!(payload["step_count"], 1);
|
|
|
|
|
assert_eq!(payload["steps"][0]["kind"], "add_property");
|
|
|
|
|
assert_eq!(payload["steps"][0]["type_kind"], "node");
|
|
|
|
|
assert_eq!(payload["steps"][0]["type_name"], "Person");
|
|
|
|
|
assert_eq!(payload["steps"][0]["property_name"], "nickname");
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
#[test]
|
|
|
|
|
fn schema_plan_with_server_flag_errors_wrong_plane() {
|
|
|
|
|
// RFC-010 Slice 1: `schema plan` is storage-plane while `schema show/apply`
|
|
|
|
|
// are data-plane — the guard rejects --server on plan with the per-subcommand
|
|
|
|
|
// label (proving command_plane/command_label descend into the nested enum).
|
|
|
|
|
let output = output_failure(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("plan")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(fixture("test.pg"))
|
|
|
|
|
.arg("--server")
|
|
|
|
|
.arg("prod"),
|
|
|
|
|
);
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
assert!(
|
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
|
|
|
stderr.contains("`schema plan` is a direct (storage-native) 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
|
|
|
&& stderr.contains("Pass a storage URI, or --cluster <dir> --cluster-graph <id>."),
|
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
|
|
|
"schema plan wrong-capability message not found; got: {stderr}"
|
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
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 15:16:51 +03:00
|
|
|
#[test]
|
|
|
|
|
fn schema_plan_json_reports_unsupported_type_change() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("breaking.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let breaking_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("age: I32?", "age: I64?");
|
|
|
|
|
fs::write(&schema_path, breaking_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("plan")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(payload["supported"], false);
|
|
|
|
|
assert!(payload["steps"].as_array().unwrap().iter().any(|step| {
|
|
|
|
|
step["kind"] == "unsupported_change"
|
|
|
|
|
&& step["entity"]
|
|
|
|
|
.as_str()
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.contains("Person.age")
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_json_applies_supported_migration() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("next.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
|
|
|
" age: I32?\n}",
|
|
|
|
|
" age: I32?\n nickname: String?\n}",
|
|
|
|
|
);
|
|
|
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(payload["supported"], true);
|
|
|
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
|
assert_eq!(payload["step_count"], 1);
|
|
|
|
|
|
|
|
|
|
let db = tokio::runtime::Runtime::new()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.block_on(Omnigraph::open(graph.to_string_lossy().as_ref()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(
|
|
|
|
|
db.catalog().node_types["Person"]
|
|
|
|
|
.properties
|
|
|
|
|
.contains_key("nickname")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_human_reports_noop() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = fixture("test.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let stdout = stdout_string(&output);
|
|
|
|
|
|
|
|
|
|
assert!(stdout.contains("applied: no"));
|
|
|
|
|
assert!(stdout.contains("no schema changes"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_json_renames_type_and_updates_snapshot() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("rename.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let renamed_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("node Person {\n", "node Human @rename_from(\"Person\") {\n")
|
|
|
|
|
.replace("edge Knows: Person -> Person", "edge Knows: Human -> Human")
|
|
|
|
|
.replace(
|
|
|
|
|
"edge WorksAt: Person -> Company",
|
|
|
|
|
"edge WorksAt: Human -> Company",
|
|
|
|
|
);
|
|
|
|
|
fs::write(&schema_path, renamed_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
|
|
|
|
|
|
let db = tokio::runtime::Runtime::new()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.block_on(Omnigraph::open(graph.to_string_lossy().as_ref()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let snapshot = tokio::runtime::Runtime::new()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.block_on(db.snapshot_of(ReadTarget::branch("main")))
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(snapshot.entry("node:Human").is_some());
|
|
|
|
|
assert!(snapshot.entry("node:Person").is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_json_renames_property_and_updates_catalog() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("rename-property.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let renamed_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("age: I32?", "years: I32? @rename_from(\"age\")");
|
|
|
|
|
fs::write(&schema_path, renamed_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
|
|
|
|
|
|
let db = tokio::runtime::Runtime::new()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.block_on(Omnigraph::open(graph.to_string_lossy().as_ref()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let person = &db.catalog().node_types["Person"];
|
|
|
|
|
assert!(person.properties.contains_key("years"));
|
|
|
|
|
assert!(!person.properties.contains_key("age"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_json_adds_index_for_existing_property() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("index.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let before_index_count = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
|
|
|
let db = Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
let snapshot = db.snapshot_of(ReadTarget::branch("main")).await.unwrap();
|
|
|
|
|
let dataset = snapshot.open("node:Person").await.unwrap();
|
|
|
|
|
dataset.load_indices().await.unwrap().len()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let indexed_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("name: String @key", "name: String @key @index");
|
|
|
|
|
fs::write(&schema_path, indexed_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
|
|
|
|
|
|
let after_index_count = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
|
|
|
let db = Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
let snapshot = db.snapshot_of(ReadTarget::branch("main")).await.unwrap();
|
|
|
|
|
let dataset = snapshot.open("node:Person").await.unwrap();
|
|
|
|
|
dataset.load_indices().await.unwrap().len()
|
|
|
|
|
});
|
|
|
|
|
assert!(after_index_count > before_index_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_rejects_unsupported_plan() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("breaking.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let breaking_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace("age: I32?", "age: I64?");
|
|
|
|
|
fs::write(&schema_path, breaking_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_failure(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
assert!(stderr.contains("changing property type"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_rejects_when_non_main_branch_exists() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("next.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("branch")
|
|
|
|
|
.arg("create")
|
|
|
|
|
.arg("--from")
|
|
|
|
|
.arg("main")
|
|
|
|
|
.arg("--uri")
|
|
|
|
|
.arg(&graph)
|
|
|
|
|
.arg("feature"),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
|
|
|
" age: I32?\n}",
|
|
|
|
|
" age: I32?\n nickname: String?\n}",
|
|
|
|
|
);
|
|
|
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_failure(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
assert!(stderr.contains("schema apply requires a graph with only main"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_allow_data_loss_flag_promotes_drops_to_hard() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("drop-age.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
// Drop the nullable `age` column.
|
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace(" age: I32?\n", "");
|
|
|
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--allow-data-loss")
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
|
|
|
|
|
|
let drop_step = payload["steps"]
|
|
|
|
|
.as_array()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|s| s["kind"] == "drop_property")
|
|
|
|
|
.expect("plan should include a drop_property step");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
drop_step["mode"], "hard",
|
|
|
|
|
"--allow-data-loss should promote Soft → Hard; full step: {drop_step}",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_apply_without_allow_data_loss_keeps_soft_drops() {
|
|
|
|
|
// Symmetric to the above: same schema change without the flag →
|
|
|
|
|
// drops stay Soft. Pins default semantics against accidental Hard
|
|
|
|
|
// promotion if a future refactor changes the option threading.
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
let schema_path = temp.path().join("drop-age-soft.pg");
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
|
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg"))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.replace(" age: I32?\n", "");
|
|
|
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
let output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("apply")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
|
|
|
|
|
|
let drop_step = payload["steps"]
|
|
|
|
|
.as_array()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|s| s["kind"] == "drop_property")
|
|
|
|
|
.expect("plan should include a drop_property step");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
drop_step["mode"], "soft",
|
|
|
|
|
"no flag should leave drops Soft; full step: {drop_step}",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn schema_plan_parity_cli_and_sdk() {
|
|
|
|
|
// Same .pg through `Omnigraph::plan_schema_with_options` (SDK) and
|
|
|
|
|
// `omnigraph schema plan --json` (CLI). Asserts the steps array is
|
|
|
|
|
// byte-identical after JSON round-trip. HTTP doesn't expose a
|
|
|
|
|
// separate /schema/plan route — that side of parity is covered by
|
|
|
|
|
// the HTTP soft/hard drop tests, which exercise apply with
|
|
|
|
|
// identical fixtures.
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
let graph = graph_path(temp.path());
|
|
|
|
|
init_graph(&graph);
|
|
|
|
|
let schema_path = temp.path().join("plan-parity.pg");
|
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
|
|
|
" age: I32?\n}",
|
|
|
|
|
" age: I32?\n nickname: String?\n}",
|
|
|
|
|
);
|
|
|
|
|
fs::write(&schema_path, &next_schema).unwrap();
|
|
|
|
|
|
|
|
|
|
// CLI side.
|
|
|
|
|
let cli_output = output_success(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("schema")
|
|
|
|
|
.arg("plan")
|
|
|
|
|
.arg("--schema")
|
|
|
|
|
.arg(&schema_path)
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.arg(&graph),
|
|
|
|
|
);
|
|
|
|
|
let cli_payload: Value = serde_json::from_slice(&cli_output.stdout).unwrap();
|
|
|
|
|
|
|
|
|
|
// SDK side: open graph, call plan_schema.
|
|
|
|
|
let plan = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
|
|
|
let db = Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
db.plan_schema(&next_schema).await.unwrap()
|
|
|
|
|
});
|
|
|
|
|
let sdk_steps = serde_json::to_value(&plan.steps).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
cli_payload["steps"], sdk_steps,
|
|
|
|
|
"CLI plan steps must match SDK plan steps for identical input",
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(cli_payload["supported"], plan.supported);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn graphs_subcommand_help_lists_list_only() {
|
|
|
|
|
let output = output_success(cli().arg("graphs").arg("--help"));
|
|
|
|
|
let stdout = stdout_string(&output);
|
|
|
|
|
assert!(
|
|
|
|
|
stdout.contains("list"),
|
|
|
|
|
"expected `list` subcommand in help output:\n{stdout}"
|
|
|
|
|
);
|
|
|
|
|
let lowered = stdout.to_lowercase();
|
|
|
|
|
assert!(
|
|
|
|
|
!lowered.contains("create a new graph"),
|
|
|
|
|
"graph create should not be in v0.6.0 help; got:\n{stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
!lowered.contains("delete a graph"),
|
|
|
|
|
"graph delete should not be in v0.6.0 help; got:\n{stdout}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn graphs_list_against_local_uri_errors_with_remote_only_message() {
|
|
|
|
|
let output = output_failure(
|
|
|
|
|
cli()
|
|
|
|
|
.arg("graphs")
|
|
|
|
|
.arg("list")
|
|
|
|
|
.arg("--uri")
|
|
|
|
|
.arg("/tmp/local"),
|
|
|
|
|
);
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
|
|
|
|
assert!(
|
|
|
|
|
stderr.contains("remote multi-graph server URL"),
|
|
|
|
|
"expected 'remote multi-graph server URL' rejection in stderr; got:\n{stderr}"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-06-11 23:28:33 +03:00
|
|
|
|
|
|
|
|
/// RFC-008 stage 1: loading a legacy omnigraph.yaml emits the per-key
|
|
|
|
|
/// deprecation block (the migration map applied to THIS file), suppressible
|
|
|
|
|
/// via OMNIGRAPH_SUPPRESS_YAML_DEPRECATION.
|
|
|
|
|
#[test]
|
|
|
|
|
fn legacy_config_load_warns_per_key_and_suppression_silences() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
temp.path().join("omnigraph.yaml"),
|
|
|
|
|
"cli:\n actor: act-x\ngraphs:\n g:\n uri: /tmp/never-opened\n",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// `graphs list --json` loads the config and exits without touching the
|
|
|
|
|
// graph URI.
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(temp.path())
|
|
|
|
|
.arg("graphs")
|
|
|
|
|
.arg("list")
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
assert!(
|
|
|
|
|
stderr.contains("deprecated (RFC-008)") && stderr.contains("`cli.actor` -> `operator.actor`"),
|
|
|
|
|
"{stderr}"
|
|
|
|
|
);
|
|
|
|
|
assert!(stderr.contains("config migrate"), "{stderr}");
|
|
|
|
|
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(temp.path())
|
|
|
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
|
|
|
.arg("graphs")
|
|
|
|
|
.arg("list")
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
assert!(!stderr.contains("deprecated (RFC-008)"), "{stderr}");
|
|
|
|
|
}
|
2026-06-11 23:32:05 +03:00
|
|
|
|
|
|
|
|
/// RFC-008 stage 2: `config migrate` proposes the split read-only, applies
|
|
|
|
|
/// it with --write (operator merge never clobbers; cluster.yaml emitted),
|
|
|
|
|
/// and a second --write is idempotent.
|
|
|
|
|
#[test]
|
|
|
|
|
fn config_migrate_splits_legacy_config() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
temp.path().join("omnigraph.yaml"),
|
|
|
|
|
"graphs:\n prod:\n uri: https://graph.example.com\n bearer_token_env: PROD_TOKEN\ncli:\n actor: act-me\n output_format: json\npolicy:\n file: ./top.policy.yaml\n",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let operator_home = tempfile::tempdir().unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
operator_home.path().join("config.yaml"),
|
|
|
|
|
"operator:\n actor: act-existing\n",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// Read-only proposal: names both halves, writes nothing.
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(temp.path())
|
|
|
|
|
.env("OMNIGRAPH_HOME", operator_home.path())
|
|
|
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
|
|
|
.arg("config")
|
|
|
|
|
.arg("migrate")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(output.status.success(), "{output:?}");
|
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
assert!(stdout.contains("team half -> cluster.yaml"), "{stdout}");
|
|
|
|
|
assert!(stdout.contains("operator.actor: act-me"), "{stdout}");
|
|
|
|
|
assert!(stdout.contains("omnigraph login prod"), "{stdout}");
|
|
|
|
|
assert!(!temp.path().join("cluster.yaml").exists());
|
|
|
|
|
|
|
|
|
|
// --write: cluster.yaml lands; the existing operator actor is KEPT.
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(temp.path())
|
|
|
|
|
.env("OMNIGRAPH_HOME", operator_home.path())
|
|
|
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
|
|
|
.arg("config")
|
|
|
|
|
.arg("migrate")
|
|
|
|
|
.arg("--write")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(output.status.success(), "{output:?}");
|
|
|
|
|
let cluster = fs::read_to_string(temp.path().join("cluster.yaml")).unwrap();
|
|
|
|
|
assert!(cluster.contains("version: 1") && cluster.contains(" prod:"), "{cluster}");
|
|
|
|
|
let operator_text =
|
|
|
|
|
fs::read_to_string(operator_home.path().join("config.yaml")).unwrap();
|
|
|
|
|
assert!(operator_text.contains("act-existing"), "{operator_text}");
|
|
|
|
|
assert!(!operator_text.contains("act-me"), "existing keys win: {operator_text}");
|
|
|
|
|
assert!(operator_text.contains("output: json"), "{operator_text}");
|
|
|
|
|
assert!(
|
|
|
|
|
operator_text.contains("url: https://graph.example.com"),
|
|
|
|
|
"{operator_text}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Second --write: cluster.yaml exists -> proposal file, no clobber.
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(temp.path())
|
|
|
|
|
.env("OMNIGRAPH_HOME", operator_home.path())
|
|
|
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
|
|
|
.arg("config")
|
|
|
|
|
.arg("migrate")
|
|
|
|
|
.arg("--write")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(output.status.success(), "{output:?}");
|
|
|
|
|
assert!(temp.path().join("cluster.yaml.proposed").exists());
|
|
|
|
|
}
|
2026-06-12 00:03:10 +03:00
|
|
|
|
|
|
|
|
/// RFC-008 stage 4: OMNIGRAPH_NO_LEGACY_CONFIG refuses a present legacy
|
|
|
|
|
/// file (pointing at config migrate) but changes nothing on migrated
|
|
|
|
|
/// setups with no file.
|
|
|
|
|
#[test]
|
|
|
|
|
fn strict_mode_refuses_legacy_file_but_not_its_absence() {
|
|
|
|
|
let temp = tempdir().unwrap();
|
|
|
|
|
fs::write(temp.path().join("omnigraph.yaml"), "cli:\n actor: a\n").unwrap();
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(temp.path())
|
|
|
|
|
.env("OMNIGRAPH_NO_LEGACY_CONFIG", "1")
|
|
|
|
|
.arg("graphs")
|
|
|
|
|
.arg("list")
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(!output.status.success());
|
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
assert!(
|
|
|
|
|
stderr.contains("OMNIGRAPH_NO_LEGACY_CONFIG") && stderr.contains("config migrate"),
|
|
|
|
|
"{stderr}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Migrated setup (no file): strict mode is a no-op — a config-loading
|
|
|
|
|
// command that tolerates empty defaults succeeds.
|
|
|
|
|
let clean = tempdir().unwrap();
|
|
|
|
|
let output = cli()
|
|
|
|
|
.current_dir(clean.path())
|
|
|
|
|
.env("OMNIGRAPH_NO_LEGACY_CONFIG", "1")
|
|
|
|
|
.arg("queries")
|
|
|
|
|
.arg("list")
|
|
|
|
|
.arg("--json")
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(output.status.success(), "{output:?}");
|
|
|
|
|
}
|