Skip to content

fix: harden SDK validation#2

Merged
leszek3737 merged 3 commits into
mainfrom
0.1.2
May 25, 2026
Merged

fix: harden SDK validation#2
leszek3737 merged 3 commits into
mainfrom
0.1.2

Conversation

@leszek3737
Copy link
Copy Markdown
Owner

@leszek3737 leszek3737 commented May 25, 2026

Summary by Sourcery

Harden SDK validation and workspace handling, add preflight input checks, and tighten HTTP/base URL behavior while expanding tests and CI coverage.

New Features:

  • Support workspace get-or-create semantics by fetching metadata and configuration via POST to the workspaces collection using a WorkspaceCreate body.
  • Expose a retryability helper on HonchoError to classify which errors are safe to retry.

Bug Fixes:

  • Ensure invalid pagination parameters and empty observer IDs are rejected client-side without issuing HTTP requests.
  • Validate dialectic chat queries for emptiness and maximum length in one place and prevent overlong requests from being sent.
  • Strengthen workspace ID and base URL validation for Honcho construction and builder usage to reject malformed inputs early.
  • Normalize base URLs consistently, including stripping trailing slashes on subpaths, to avoid incorrect request paths.

Enhancements:

  • Add raw configuration fetching that tolerates unknown future fields in workspace configuration responses.
  • Document API behavior and constraints in session and upload APIs, including supported MIME types and peer existence requirements.
  • Refactor pagination and dialectic validation into shared helpers for consistent enforcement across call sites.

Build:

  • Bump crate version to 0.1.2 to reflect the new validation and behavioral changes.

CI:

  • Tighten CI configuration with explicit permissions, workflow concurrency controls, full test/doc test runs, and a new MSRV check job.

Documentation:

  • Clarify README and API docs around file upload MIME types, session peer configuration requirements, and other behavioral nuances.

Tests:

  • Broaden client, pagination, peer, and dialectic tests to cover new validation rules, workspace POST semantics, base URL normalization, and raw configuration retrieval.
  • Add constructor and builder tests for workspace ID constraints, base URL validation, and acceptance of valid identifiers.
  • Extend error mapping tests to assert non-retryable behavior for certain transport errors.

Chores:

  • Update GitNexus metadata documentation files to reflect the latest index relationship counts.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 25, 2026

Reviewer's Guide

Harden SDK-side validation and request shaping across workspace construction, pagination, dialectic chat, and file upload, while tightening base URL/workspace ID validation, switching workspace reads to POST get-or-create semantics, and expanding tests and CI coverage (including MSRV).

Sequence diagram for new workspace metadata/configuration POST flow

sequenceDiagram
    participant Honcho
    participant HttpClient
    participant Routes
    participant Server

    Honcho->>Routes: workspaces()
    Routes-->>Honcho: /workspaces
    Honcho->>Honcho: build WorkspaceCreate { id, metadata: None, configuration: None }
    Honcho->>HttpClient: post(/workspaces, WorkspaceCreate, headers)
    HttpClient->>Server: POST /workspaces { id, metadata, configuration }
    Server-->>HttpClient: 200 Workspace
    HttpClient-->>Honcho: Workspace
    Honcho->>Honcho: extract metadata/configuration
Loading

File-Level Changes

Change Details Files
Strengthen Honcho client construction by validating and normalizing base URLs and workspace IDs and reusing this logic across constructors and builders.
  • Introduce normalize_base_url to parse, validate http/https scheme, require host, and trim trailing slashes, and use it in HttpClient::from_params and Honcho constructors
  • Extract workspace ID validation into validate_workspace_id enforcing non-empty, max length 512, and [a-zA-Z0-9_-]+, and call it in Honcho::new and Honcho::from_env
  • Update Honcho builder/client tests to cover valid/invalid workspace IDs and base URLs, and trailing-slash normalization
src/http/client.rs
src/client.rs
tests/client_metadata.rs
tests/client_builder.rs
tests/blocking_smoke.rs
Change workspace metadata/configuration reads to POST a WorkspaceCreate body to the workspaces collection endpoint instead of GET by ID, and add coverage for raw configuration.
  • Update get_metadata, get_configuration, and get_configuration_raw to POST to routes::workspaces with WorkspaceCreate { id, metadata: None, configuration: None }
  • Adjust async and blocking tests to mock POST /v3/workspaces with id in JSON body instead of GET /v3/workspaces/{id}
  • Add a new test verifying get_configuration_raw passes through unknown configuration fields
src/client.rs
tests/client_metadata.rs
tests/blocking_smoke.rs
Add SDK-side validation for pagination arguments and dialectic chat queries, and wire this into peer chat APIs and pagination helpers so invalid inputs fail before any HTTP request is made.
  • Introduce validate_pagination enforcing page >= 1 and 1 <= size <= 100 and call it at the start of paginate_post, with tests ensuring invalid inputs yield HonchoError::Validation and no requests are sent
  • Add validate_dialectic_query enforcing non-empty query and maximum 10,000 Unicode scalar values, expose DialecticOptions::validate, and use this from Peer::chat, Peer::chat_with_options, and ChatStreamBuilder::send
  • Add tests for dialectic validation, including byte vs char counting and long query rejection without extra HTTP calls, plus positive tests for boundary sizes
src/types/pagination.rs
src/types/dialectic.rs
src/peer.rs
tests/pagination.rs
tests/dialectic_dream_types.rs
tests/peer_core.rs
Tighten validation for dream scheduling and clarify behavior and constraints around session peer configuration and file uploads.
  • Add an early check in Honcho::schedule_dream rejecting empty observer with HonchoError::Validation so no network request is made
  • Document that set_peer_configuration requires the peer to already exist in the session in both async and blocking Session APIs
  • Document that file uploads (Session and FileSource helpers) currently support text/plain, application/pdf, and application/json content types, updating README examples accordingly
  • Add a test verifying schedule_dream rejects empty observer and does not hit the server
src/client.rs
src/session.rs
src/blocking/session.rs
src/upload.rs
tests/client_operations.rs
README.md
Extend error utilities and CI hygiene by exposing retryability semantics on HonchoError, simplifying conclusion pagination validation, tightening CI runs, and enforcing MSRV.
  • Add HonchoError::is_retryable to report retryability based on error kind and status code (429, 5xx) and assert non-retryable transport errors in tests
  • Remove ad-hoc page size > 0 validation from ListConclusionsBuilder::send to rely on centralized pagination validation
  • Update CI workflow to set minimal permissions, enable concurrency cancellation, run cargo test --all-features and doc tests, and add an MSRV job using cargo check on Rust 1.88
  • Bump crate version to 0.1.2 and keep agent metadata in sync with updated index stats
src/error.rs
src/conclusion.rs
.github/workflows/ci.yml
tests/error_mapping.rs
AGENTS.md
CLAUDE.md
Cargo.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the honcho-ai SDK to version 0.1.2, introducing centralized validation for workspace IDs, base URLs, and pagination parameters. Key changes include switching workspace metadata and configuration retrieval from GET to POST to support upsert-like behavior, adding an is_retryable helper to the error type, and expanding documentation for file uploads and peer management. Feedback identifies a potential regression where the new POST requests might fail with a 409 Conflict error on existing workspaces, and notes that the new hard limit of 100 for pagination page sizes may be a breaking change for some users.

Comment thread src/client.rs
Comment on lines 243 to 253
pub async fn get_metadata(&self) -> Result<HashMap<String, Value>> {
let body = crate::types::workspace::WorkspaceCreate {
id: self.inner.workspace_id.clone(),
metadata: None,
configuration: None,
};
let ws: Workspace = self
.inner
.http
.get(&routes::workspace(self.workspace_id())?, &[])
.post(&routes::workspaces(), Some(&body), &[])
.await?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The change from GET to POST for fetching workspace metadata (and configuration in subsequent methods) introduces a potential regression. While ensure_workspace (line 194) explicitly handles 409 Conflict as a success case, these methods do not. If the workspace already exists and the server returns 409 on a POST request, these methods will fail with a Conflict error instead of returning the data.

If the server is guaranteed to return 200 OK with the workspace object on POST even if it exists, then the 409 handling in ensure_workspace is redundant. Otherwise, these methods should handle the 409 case (e.g., by falling back to a GET request) to ensure they work correctly for existing workspaces.

Comment thread src/types/pagination.rs
Comment on lines +435 to +439
if !(1..=100).contains(&size) {
return Err(HonchoError::Validation(
"size must be between 1 and 100".to_string(),
));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The validate_pagination function enforces a hard limit of 100 for the page size. This is a breaking change that restricts users from requesting larger pages even if the server supports them. If this limit is intended to match server-side constraints, it should be documented in the public API; otherwise, consider allowing larger values or making the limit configurable.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • In src/client.rs, normalize_base_url is now called directly but is defined in src/http/client.rs as pub(crate); consider either importing it explicitly from crate::http::client or calling it as crate::http::client::normalize_base_url to avoid a compilation error.
  • You removed the size == 0 validation in ListConclusionsBuilder::send but only added the new validate_pagination check in paginate_post; consider reusing validate_pagination (or equivalent bounds checking) for conclusions pagination as well so page/size are consistently validated across the SDK.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `src/client.rs`, `normalize_base_url` is now called directly but is defined in `src/http/client.rs` as `pub(crate)`; consider either importing it explicitly from `crate::http::client` or calling it as `crate::http::client::normalize_base_url` to avoid a compilation error.
- You removed the `size == 0` validation in `ListConclusionsBuilder::send` but only added the new `validate_pagination` check in `paginate_post`; consider reusing `validate_pagination` (or equivalent bounds checking) for conclusions pagination as well so page/size are consistently validated across the SDK.

## Individual Comments

### Comment 1
<location path="src/client.rs" line_range="96" />
<code_context>
-            ));
-        }
+        validate_workspace_id(workspace_id)?;
+        let url = normalize_base_url(base_url)?;
         let http =
-            HttpClient::from_params(HttpClient::builder().base_url(base_url.to_string()).build())?;
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid double-normalizing and reparsing the base URL when constructing `HttpClient`.

`normalize_base_url` already parses and validates `base_url` into a `Url`, but we then convert it to `String` and pass it into `HttpClient::from_params`, which calls `normalize_base_url` again (same in `from_env`). This does redundant parsing/validation on every client construction. It would be better if `HttpClient::builder`/`HttpClientParams` could accept a `Url` directly, or if there were a constructor that takes an already-normalized `Url` to avoid the extra work.

Suggested implementation:

```rust
    pub fn new(base_url: &str, workspace_id: &str) -> Result<Self> {
        validate_workspace_id(workspace_id)?;
        let url = normalize_base_url(base_url)?;
        let http = HttpClient::from_url(url)?;
        Ok(Self {
            inner: Arc::new(Inner {
                http,

```

To fully implement the optimization and avoid double-normalization/reparsing you will also need to:

1. **Add a constructor to `HttpClient` that accepts a pre-normalized `Url`:**
   - In the module where `HttpClient` is defined, implement something like:
     ```rust
     impl HttpClient {
         pub fn from_url(url: Url) -> Result<Self> {
             // construct HttpClient directly from a Url without calling `normalize_base_url` again
             let params = HttpClientParams::builder().base_url_url(url).build();
             HttpClient::from_params(params)
         }
     }
     ```
   - The exact implementation will depend on how `HttpClientParams`/`builder()` are structured.

2. **Extend `HttpClientParams` / `HttpClient::builder()` to accept a `Url` directly (if it doesnt already):**
   - For example, add a method like:
     ```rust
     impl HttpClientParamsBuilder {
         pub fn base_url_url(mut self, url: Url) -> Self {
             self.base_url = url;
             self
         }
     }
     ```
   - And change `HttpClientParams`’s `base_url` field to be a `Url` instead of a `String` if it isnt already, or otherwise ensure that `from_url` does not re-call `normalize_base_url`.

3. **Update other call sites (such as `from_env`) that currently do `normalize_base_url(...)?` and then `HttpClient::from_params(HttpClient::builder().base_url(url.to_string()).build())` to instead use `HttpClient::from_url(url)` so they also benefit from avoiding redundant normalization.**
</issue_to_address>

### Comment 2
<location path=".github/workflows/ci.yml" line_range="49-50" />
<code_context>
-      - run: cargo test --test '*_types'
-      - run: cargo test --test wire_format_peers
-      - run: cargo test --test compile_assertions
+      - run: cargo test --all-features
+      - run: cargo test --doc --all-features
+
+  msrv:
</code_context>
<issue_to_address>
**suggestion (testing):** Running both `cargo test --all-features` and `cargo test --doc --all-features` likely executes doc tests twice.

`cargo test` already includes doctests unless you filter them out (e.g. with `--lib`, `--test`, etc.), so this setup will run doctests in both commands, slowing CI without added coverage. To avoid duplication while still running everything else, you could either change the first command to `cargo test --all-features --lib --tests --bins --examples` or remove the separate `--doc` invocation.

Suggested implementation:

```
      - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
      - run: cargo test --all-features --lib --tests --bins --examples

```

None required, as long as there are no other workflow steps elsewhere that depend specifically on `cargo test --doc`. If there are, consider aligning those with this pattern as well.
</issue_to_address>

### Comment 3
<location path="tests/pagination.rs" line_range="671-680" />
<code_context>
 }

 #[tokio::test]
-async fn gets_workspace_metadata_by_id() {
+async fn gets_workspace_metadata_by_post_get_or_create() {
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a boundary test for the minimum allowed page size

Current tests cover rejection of invalid sizes and acceptance of `size = 100`, but not the lower bound. Since `validate_pagination` permits `size = 1`, please add a test that calls `paginate_post` with `page = 1, size = 1` and asserts the request is sent with `size=1` and succeeds, confirming the intended `1..=100` inclusive range.
</issue_to_address>

### Comment 4
<location path="README.md" line_range="144" />
<code_context>
 session.remove_peers(["bob"]).await?;
 let peers = session.peers().await?;
 let cfg = session.get_peer_configuration("alice").await?;
+// alice must already be present; add_peer/add_peers/set_peers above satisfies this.
 session.set_peer_configuration("alice", &cfg).await?;

</code_context>
<issue_to_address>
**suggestion (typo):** Consider adjusting verb agreement in this sentence ("satisfy" instead of "satisfies").

Because `add_peer/add_peers/set_peers` form a compound subject, the verb should be plural: `add_peer/add_peers/set_peers above satisfy this.`
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/client.rs
Comment thread .github/workflows/ci.yml Outdated
Comment thread tests/pagination.rs
Comment on lines +671 to +680
#[tokio::test]
async fn paginate_post_rejects_invalid_page_and_size_before_request() {
use honcho_ai::http::client::HttpClient;
use honcho_ai::types::pagination::paginate_post;
use wiremock::MockServer;

let server = MockServer::start().await;
let http = HttpClient::from_params(
HttpClient::builder()
.base_url(server.uri())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding a boundary test for the minimum allowed page size

Current tests cover rejection of invalid sizes and acceptance of size = 100, but not the lower bound. Since validate_pagination permits size = 1, please add a test that calls paginate_post with page = 1, size = 1 and asserts the request is sent with size=1 and succeeds, confirming the intended 1..=100 inclusive range.

Comment thread README.md Outdated
Implements robust client-side validation for various parameters, including workspace IDs, pagination values, and dialectic queries, ensuring invalid requests are caught early. Enhances URL normalization and validation during client construction for consistent and reliable base URL handling. Updates relevant documentation to clearly state these new constraints.
@leszek3737 leszek3737 merged commit 71b4659 into main May 25, 2026
7 checks passed
@leszek3737 leszek3737 deleted the 0.1.2 branch May 25, 2026 01:58
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