Skip to content

feat: refactor CLI onto the hotdata Rust SDK#134

Merged
zfarrell merged 29 commits into
mainfrom
feat/sdk-refactor
Jun 5, 2026
Merged

feat: refactor CLI onto the hotdata Rust SDK#134
zfarrell merged 29 commits into
mainfrom
feat/sdk-refactor

Conversation

@zfarrell

@zfarrell zfarrell commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Replaces the CLI's hand-rolled reqwest::blocking HTTP layer with the hotdata Rust SDK (rev 8d4018f, hotdata-dev/sdk-rust#32), behind a synchronous block_on seam (src/sdk.rs) so the command modules stay sync. Auth keeps the CLI's PKCE login + session.json + client_id=hotdata-cli; a CliTokenProvider feeds the SDK the minted JWT.

Behavior changes (verified against runtimedb + the gateway):

  • Scope headers are sent only where the backend reads them. Dropped the dead X-Sandbox-Id (unused in runtimedb and monopoly post-migration) and the old blanket over-send of X-Database-Id/X-Session-Id to endpoints that ignore them (e.g. the query-runs poll). X-Workspace-Id (gateway ext-auth), X-Session-Id (datasets/indexes/query/results), and X-Database-Id (/query, dataset create) are preserved where consumed.
  • Traffic attributed as the CLIUser-Agent: hotdata-cli/<v> and client_id=hotdata-cli instead of the SDK defaults.
  • query submits via the SDK's submit_query; the ignored connection_id body field is no longer sent (/query is database-scoped), so query --connection is now a confirmed no-op (Remove/deprecate the dead 'query --connection' flag #132).

Verified with a request-differential gate (old main binary vs this branch): every API call is wire-identical except the intended deltas above.

Follow-ups: #129 scenario tests · #130 arrow→get_result_arrow · #131 raw→typed · #132 dead --connection · #133 upload→upload_stream (needs SDK Content-Length first).

@sentry

sentry Bot commented Jun 5, 2026

Copy link
Copy Markdown

Comment thread src/sdk.rs
query: &[(&str, String)],
) -> Result<T, ApiError> {
let cfg = self.client.configuration();
let url = format!("{}/v1{path}", cfg.base_path);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: (not blocking) get_json, post_raw, delete_raw, and get_bytes each duplicate the same ~30-line header-attachment block (user_agent, X-Workspace-Id, X-Session-Id, X-Database-Id, bearer). Adding or changing a scope header later means editing four call sites in lockstep, which is easy to get subtly out of sync. Consider extracting a helper that takes the RequestBuilder and applies the common seam headers, e.g.:

fn apply_seam_headers(&self, mut req: reqwest::RequestBuilder, cfg: &Configuration, bearer: Option<String>) -> reqwest::RequestBuilder { ... }

so each verb method just builds its method/body/accept and calls it. Purely a maintainability cleanup — the current code is correct and well-tested.

claude[bot]
claude Bot previously approved these changes Jun 5, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full SDK-refactor diff (sdk.rs seam, jwt CliTokenProvider, raw_http, and all 15 migrated command modules). The seam's auth-source precedence, scope-header behavior, and the documented intentional deltas (dropped X-Sandbox-Id, no-op --connection, no blanket scope over-send) all check out, and the test coverage is thorough (base-path double-/v1 regression, concurrent rayon block_on, per-module wire assertions). CI test job is green. One non-blocking maintainability nit left inline on the duplicated raw-helper header blocks.

@zfarrell

zfarrell commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the duplicated-header-block nit in d5aeaca: the four raw seam helpers (get_json/post_raw/delete_raw/get_bytes) now funnel through a single apply_seam_headers helper, so a scope-header change is a one-place edit. Verified no wire drift — per-verb header tests pass and the old-vs-new request-diff is unchanged.

Comment thread src/sdk.rs Outdated
Comment on lines +27 to +31
// The wrapper is wired into command modules incrementally (one commit per
// module). Until every call site is migrated, parts of this seam are unused;
// the allow keeps the build warning-free through the transition and is removed
// once api.rs is retired.
#![allow(dead_code)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

super nit: (not blocking) This PR deletes api.rs outright, so the "transition" these comments describe is now complete — the #![allow(dead_code)] is described as something to "[remove] once api.rs is retired," which happens right here. Worth a follow-up pass to drop the module-level allow (and the matching ones on AuthMode/CliTokenProvider in jwt.rs) and clean up anything that's genuinely unused now that the legacy client is gone. Leaving a blanket dead_code allow in place can mask real dead code later. Purely cleanup — no behavior impact.

claude[bot]
claude Bot previously approved these changes Jun 5, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the SDK refactor. Auth seam (CliTokenProvider/ensure_access_token), the sync block_on wrapper, scope-header handling, and the raw escape-hatch helpers all look correct and are backed by thorough, meaningful tests (mockito wire assertions, concurrent rayon block_on, double-/v1 regression, refresh/mint decision table). Prior header-duplication nit is resolved via apply_seam_headers. One non-blocking cleanup nit left inline.

@zfarrell

zfarrell commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the stale dead_code allows (latest commit): dropped the module-level #![allow(dead_code)] in sdk.rs and the AuthMode/CliTokenProvider allows in jwt.rs now that api.rs is gone. Good call — it immediately surfaced genuinely-dead code: Api::with_database (its only prod caller was removed when query submit moved to submit_query), now removed and its test switched to test_new_scoped. Build is warning-free with no allow.

claude[bot]
claude Bot previously approved these changes Jun 5, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full SDK-refactor diff across all 25 files. The seam (src/sdk.rs), the CliTokenProvider auth bridge, and each migrated command module are correct, consistent, and well-tested — scope-header narrowing, the double-/v1 guard, concurrent block_on from rayon, and the Option<Option<T>> model flattening all have dedicated coverage. The prior review's header-dedup nit is resolved via apply_seam_headers, and the dead_code allows are now either gone (api.rs deleted) or freshly justified (incremental raw_http migration).

Note: I could not run cargo check/tests in this environment, so build verification rests on manual review.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the SDK seam, auth provider, raw-HTTP helper, and command-module adaptations. Auth precedence, header scoping, Arrow decoding, and the rayon concurrency contract are all correct and thoroughly tested. The apply_seam_headers dedup from the prior review is already in place. No blocking issues.

@zfarrell

zfarrell commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Extended the request-differential parity gate to near-total coverage: 103 command/flag combinations driven through both the main binary and this branch against a per-endpoint capture server, in two passes — a no-scope baseline and a full active-scope sweep (HOTDATA_SANDBOX + HOTDATA_DATABASE forced on every command, so the scope-header over-send removal is exercised on every endpoint).

Result: GATE PASS on both passes — 0 unexpected divergences, 0 regressions. Every delta is one of the three documented intended changes (dead X-Sandbox-Id dropped, User-Agent: hotdata-cli added, connection_id dropped from /query + over-send of X-Session-Id/X-Database-Id removed). A positive oracle (from the sdk-rust per-op security spec) confirms the change is surgical: consumed endpoints retain session/database scope; only the dead header and genuine over-sends are dropped.

Harness + full report (secret gist): https://gist.github.com/zfarrell/4e231496dcd51e1afd00570f699096e0

@zfarrell zfarrell merged commit c4a70cc into main Jun 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant