feat: repoint Rust client and CLI project commands to core#1328
Conversation
geoffjay
left a comment
There was a problem hiding this comment.
Review: feat: repoint Rust client and CLI project commands to core
Stack position: This PR (issue-1311) sits on top of epic-1307 → feature/projects-to-core (#1325). #1325 is still open, so this branch cannot merge until that parent stack is landed. issue-1312 is stacked on top of this PR and will need a restack after this merges.
⚠️ Needs attention: delete_project docstring is now incorrect — and the missing guard is a correctness concern
The method comment still says:
/// Delete a project (fails if agents or workflows are still associated).
pub async fn delete_project(&self, id: &Uuid) -> Result<()> {But the core handler (DELETE /api/v1/projects/{id}) explicitly does not enforce this constraint:
// crates/core/src/api/projects.rs
/// Deletes the project. Core only checks its own constraints — there is no
/// cross-service delete-guard at this layer. Returns `204 No Content`.
After this PR, a project record in core can be deleted while the orchestrator still holds agent and workflow associations pointing at that project UUID — leaving orphaned references with no corresponding project.
At minimum, please fix the docstring so it accurately describes what the new endpoint does. Beyond that, the PR should either:
- Note explicitly (here and/or in the linked issue) that the association guard is intentionally deferred, and describe where/when it will be enforced, or
- Add a client-side pre-check in
delete_projectthat querieslist_project_agents/list_project_workflows(both still on the orchestrator) before sending the delete to core.
Non-blocking: *_core helpers duplicate ~60 lines of the existing * helpers
get_core, post_core, put_core, delete_core differ from get, post, put, delete in only one way — they call self.core_url(path) instead of format!("{}{}", self.base_url, path). Everything else (bearer-auth wiring, handle_response, error context strings) is copy-pasted.
A lightweight cleanup would be a private url_for helper:
fn url_for(&self, path: &str, use_core: bool) -> String {
let base = if use_core {
self.core_base_url.as_deref().unwrap_or(&self.base_url)
} else {
&self.base_url
};
format!("{base}{path}")
}Then the private helpers each take use_core: bool and the duplication collapses. Not required for this PR, but worth a follow-up if this pattern expands to more endpoints.
Non-blocking: no unit tests for with_core_url / core_url resolution
The existing tests in client.rs only exercise base_url string conversion. The new fallback logic — "use core_base_url when set, otherwise fall back to base_url" — has no test coverage. Consider adding:
#[test]
fn test_core_url_falls_back_to_base_when_unset() {
let c = OrchestratorClient::new("http://orch");
assert_eq!(c.core_url("/projects"), "http://orch/projects");
}
#[test]
fn test_core_url_uses_core_base_when_set() {
let c = OrchestratorClient::new("http://orch").with_core_url("http://core/api/v1");
assert_eq!(c.core_url("/projects"), "http://core/api/v1/projects");
}Looks good
AppState::new(storage)fix is correct.AppStatehas three fields (storage,pam_config,pam_verifier); the oldAppState { storage }struct literal was a compile error waiting to happen once the struct gained those extra fields. Using::new()with fail-closed defaults is right.- Type compatibility between core's
ProjectResponse(String ids/timestamps) and the client'sProject(Uuid/DateTime<Utc>) is fine — core stores UUIDs in RFC4122 format and timestamps as RFC3339 strings, both of which serde deserializes into the typed fields transparently. - Removing
agent_count/workflow_countfromshow_projectis consistent withget_projectnow returningProjectinstead ofProjectResponse. - URL wiring in
main.rsis correct:orch_urlroutes viagateway_url("orchestrator")→{core_url}/api/v1/orchestrator, whilecore_api_urldirectly targets{core_url}/api/v1. Token is correctly shared across both paths.
feat: repoint MCP tools and UI project calls to core
- Fix delete_project docstring: core does not enforce association constraints server-side, so add a client-side pre-check that queries list_project_agents and list_project_workflows before deleting - Replace core_url(path) helper with url_for(path, use_core: bool) to reduce duplication; extract URL-based implementations (get_url, post_url, put_url, delete_url, delete_with_response_url) that both *_core and regular wrappers delegate to - Add unit tests for url_for fallback logic using a struct-literal url_only_client helper to avoid reqwest TLS init in test threads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Route all project CRUD operations (list, create, get, update, delete) in the Rust
OrchestratorClientand CLIprojectcommands to the core service, leaving association operations (add/remove agent/workflow) on the orchestrator.Changes:
OrchestratorClient: addcore_base_urlfield andwith_core_url()builder; replacecore_url(path)helper withurl_for(path, use_core: bool); extract URL-based HTTP implementations (get_url,post_url,put_url,delete_url,delete_with_response_url) to eliminate duplication between*_coreand regular wrappers; add client-side association pre-check indelete_projectbefore deleting from core (since core doesn't enforce this constraint server-side); add unit tests forurl_forfallback logicmain.rs: wirewith_core_url()for the project command armproject.rs: updateshow_projectfor the newProjectreturn type (noagent_count/workflow_count)Closes #1311