refactor(cli): collapse export + graphs-list onto GraphClient (RFC-009 Phase 3c) - #213
Conversation
…9 Phase 3c) The last two embedded-vs-remote forks move onto the enum, so every such `if` in the CLI now lives in client.rs — the point of the refactor. - `export<W: Write>`: the streaming verb 3b deferred (writes to a writer, chunks the HTTP response body, rather than returning a DTO). Embedded calls db.export_jsonl_to_writer; Remote streams the chunked body through. Opens WITHOUT policy (like reads), so it routes via resolve(). - `list_graphs`: remote-only by design (no local enumeration endpoint), so the Embedded arm keeps the loud "requires a remote multi-graph server" bail verbatim. Routing it through the enum still buys the shared resolve() addressing/token preamble the arm hand-rolled. Retire the now-orphaned execute_export_to_writer / execute_export_remote_to_writer pair, and sweep two pre-existing dead fns while in the files: inferred_config_path (helpers.rs) and yaml_string (output.rs, shadowed by test-local copies). parity_matrix gains one row, parity_export — the single intended matrix change in this phase. Export is a JSONL stream, not a single --json doc, so it compares the two arms' output line-wise (sorted; twin graphs are byte-copies so rows need no scrubbing). graphs-list gets no row: its remote-only behavior is a documented exclusion, not an equality case. Full workspace tests pass; all 12 parity rows green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
aaltshuler has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
| assert!( | ||
| !local_lines.is_empty(), | ||
| "export produced no rows — the parity check would be vacuous" | ||
| ); | ||
| local_lines.sort_unstable(); | ||
| remote_lines.sort_unstable(); | ||
| assert_eq!( | ||
| local_lines, remote_lines, |
There was a problem hiding this comment.
Missing non-empty guard on the remote output
assert!(!local_lines.is_empty()) guards against the fixture producing no rows on the local arm, but the remote arm has no equivalent check. If the remote arm silently returns a 200 with an empty body (e.g., a chunked-transfer bug where the first chunk() is None), remote_lines will be empty while local_lines is non-empty, and the test will still fail — but with the generic "JSONL streams diverge" message rather than a diagnostic one that points directly at the remote arm being empty. Adding a parallel assert for remote_lines mirrors the stated intent and makes the failure mode self-documenting.
The last two embedded-vs-remote forks move onto the
GraphClientenum, so every suchifin the CLI now lives inclient.rs— the point of RFC-009 Phase 3. Builds on 3a (#210) and 3b (#211).What moved
export<W: Write>— the streaming verb 3b deferred (writes to a writer, chunks the HTTP response body, rather than returning a DTO). Embedded callsdb.export_jsonl_to_writer; Remote streams the chunked body through. Opens WITHOUT policy (like reads), so it routes viaresolve().list_graphs— remote-only by design (no local enumeration endpoint), so the Embedded arm keeps the loud "requires a remote multi-graph server" bail verbatim. Routing it through the enum still buys the sharedresolve()addressing/token preamble the arm hand-rolled.Cleanup
Retire the now-orphaned
execute_export_to_writer/execute_export_remote_to_writerpair, and sweep two pre-existing dead fns:inferred_config_path(helpers.rs) andyaml_string(output.rs, shadowed by test-local copies).Parity
parity_matrixgains one row,parity_export— the single intended matrix change in this phase. Export is a JSONL stream, not a single--jsondoc, so it compares the two arms line-wise (sorted; twin graphs are byte-copies, so rows need no scrubbing).graphs listgets no row — its remote-only behavior is a documented exclusion, not an equality case.Verification
parity_export).cli_datagreen (export e2e), warning-clean build.cargo test --workspace --locked→ exit 0, zero failures (run locally —Test Workspaceno longer runs on PRs per ci: run Test Workspace only on main, not on pull requests #212).Out of scope: Phase 4 (plane capability advertisement) and Phase 5 (route alignment).
🤖 Generated with Claude Code
Greptile Summary
This PR completes RFC-009 Phase 3 by moving the last two embedded-vs-remote forks —
exportandgraphs list— onto theGraphClientenum, so every CLIis_remotebranch now lives inclient.rs. It also retires four orphaned/dead helpers (execute_export_to_writer,execute_export_remote_to_writer,inferred_config_path,yaml_string) and adds theparity_exportrow to the parity matrix.exportmethod (client.rs): streams JSONL into aW: Write; Remote arm chunks the HTTP response body without buffering; Embedded arm opens without policy (consistent with reads), callsOmnigraph::opendirectly — both arms are direct transliterations of the removed helpers with no behavioral change.list_graphsmethod (client.rs): Remote-only by design; Embedded arm preserves the verbatim error message from the oldmain.rsguard, now buying the sharedresolve()addressing/token preamble for free.parity_exporttest: Uses a sorted line-wise comparison instead of the usual single-doc scrub, with a non-empty guard to prevent the check from being vacuous; only a minor diagnostic gap when the remote arm returns an empty body (caught by the finalassert_eq!, but with a less specific message).Confidence Score: 4/5
Safe to merge; the refactor is a mechanical transliteration with no behavioral changes to either the export stream or the graphs-list error path.
The core logic changes are direct port-overs of removed helpers into
GraphClientmatch arms, with the old and new code paths identical line-for-line. The only observation is a minor diagnostic gap inparity_export: the non-empty guard only covers the local output, so a silent empty-body response from the remote arm would surface as a genericassert_eq!failure rather than a targeted message.crates/omnigraph-cli/tests/parity_matrix.rs — the
parity_exporttest would benefit from a symmetric non-empty guard onremote_lines.Important Files Changed
exportandlist_graphsmethods toGraphClient, completing RFC-009 Phase 3c; logic is a direct transliteration of the removed helpers, behavior preserved.execute_export_to_writer,execute_export_remote_to_writer,inferred_config_path) now that their logic lives inclient.rs.graphs listhandlers replaced withGraphClient::resolve()+ method call; unusedExportRequest/GraphListResponseimports removed;http_clientstill required for operator-alias path.yaml_stringhelper; no functional changes.parity_exportrow; uses line-wise sorted comparison instead of the usual single-doc scrub; only the local arm gets an explicit non-empty guard, leaving a gap in diagnostic clarity if the remote arm silently returns an empty body.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD CLI["main.rs: Command::Export / Command::Graphs::List"] resolve["GraphClient::resolve(config, server, graph, uri, target)"] isRemote{is_remote_uri?} remoteVariant["GraphClient::Remote { http, base_url, token }"] embeddedVariant["GraphClient::Embedded { uri, graph: None, actor: None }"] exportRemote["export() Remote arm\nPOST /export → stream chunks → writer"] exportEmbedded["export() Embedded arm\nOmnigraph::open(uri)\n→ export_jsonl_to_writer → writer"] listRemote["list_graphs() Remote arm\nGET /graphs → GraphListResponse"] listEmbedded["list_graphs() Embedded arm\nbail! (remote-only)"] CLI --> resolve resolve --> isRemote isRemote -- yes --> remoteVariant isRemote -- no --> embeddedVariant remoteVariant --> exportRemote embeddedVariant --> exportEmbedded remoteVariant --> listRemote embeddedVariant --> listEmbeddedReviews (1): Last reviewed commit: "refactor(cli): collapse export + graphs-..." | Re-trigger Greptile