OSIS island RPC client uses wrong URL pattern — cannot connect from hero_os #37

Open
opened 2026-04-27 14:41:32 +00:00 by rawan · 3 comments
Member

Symptom

When the OSIS native island is opened inside hero_os (built with FEATURES=web-native), the dashboard shows:

Cannot connect to OSIS backend.
Service unavailable — unexpected response
Make sure hero_osis_server is running with a root context.

The error message is misleading — hero_osis_server is running, contexts default to root (per ServerCli in hero_rpc). The actual problem is that the OSIS island's RPC client targets a URL that doesn't exist.

Root cause

crates/hero_osis_app/src/rpc.rs builds the URL as:

let url = if base.is_empty() {
    format!("/rpc/{context}")
} else {
    format!("{}/rpc/{context}", base.trim_end_matches('/'))
};

Combined with crates/hero_osis_app/src/app.rs:38:

crate::rpc::set_rpc_base("/hero_osis/ui");

…the island POSTs to /hero_osis/ui/rpc/root. Through hero_router on :9988, that path resolves to hero_osis_ui's HTTP server, which only serves admin HTML — there is no /rpc/{context} handler there. hero_router responds with the plain-text error Socket 'rpc.sock' not found for 'hero_osis', which then fails JSON parsing in the client → "Service unavailable — unexpected response".

This contradicts the documented architecture in hero_os/crates/hero_os_app/src/config.rs:11-14:

Per-domain OSIS services (e.g. hero_osis_files, hero_osis_ai) each own their own socket. The OSIS SDK targets /hero_osis_<domain>/rpc and carries the context in the X-Hero-Context HTTP header.

Reproduction

# What the island calls today (broken — non-JSON error):
curl -sS -X POST -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"base.context.list","params":{}}' \
  http://127.0.0.1:9988/hero_osis/ui/rpc/root
# → Socket 'rpc.sock' not found for 'hero_osis'

# What the architecture says it should call (works):
curl -sS -X POST -H 'Content-Type: application/json' \
  -H 'X-Hero-Context: root' \
  -d '{"jsonrpc":"2.0","id":1,"method":"base.context.list","params":{}}' \
  http://127.0.0.1:9988/hero_osis_base/rpc
# → {"jsonrpc":"2.0","result":"","id":1}

Proposed fix

In crates/hero_osis_app/src/rpc.rs:

  1. Derive the OSIS domain from the method prefix (e.g. base.context.listhero_osis_base, business.company.listhero_osis_business).
  2. Build the URL as {api_host}/hero_osis_{domain}/rpc instead of {base}/rpc/{context}.
  3. Send the context via the X-Hero-Context: {context} HTTP header (matches hero_context spec).
  4. Drop the /hero_osis/ui set_rpc_base call in app.rs:38 — origin should come from IslandContext::api_host (already passed to the component as a prop) for consistency with other islands.

Sketch:

pub async fn rpc(context: &str, method: &str, params: Value) -> Result<Value, String> {
    let domain = method.split('.').next().unwrap_or("base");
    let url = format!("{}/hero_osis_{domain}/rpc", api_host());
    let mut req = gloo_net::http::Request::post(&url)
        .header("Content-Type", "application/json")
        .header("X-Hero-Context", context);
    // …
}

Acceptance criteria

  • Opening the OSIS island in hero_os (built with FEATURES=web-native) successfully reaches hero_osis_base and lists contexts (or shows an empty list cleanly when none are seeded — that empty case is tracked separately).
  • No requests are sent to /hero_osis/ui/rpc/... paths.
  • All RPC calls carry X-Hero-Context, never put the context in the URL path.
  • Native admin run (make dev-minimal + open hero_osis_ui directly) still works.
## Symptom When the OSIS native island is opened inside hero_os (built with `FEATURES=web-native`), the dashboard shows: ``` Cannot connect to OSIS backend. Service unavailable — unexpected response Make sure hero_osis_server is running with a root context. ``` The error message is misleading — `hero_osis_server` is running, contexts default to `root` (per [`ServerCli` in hero_rpc](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/src/server/cli.rs#L31-L33)). The actual problem is that the OSIS island's RPC client targets a URL that doesn't exist. ## Root cause [`crates/hero_osis_app/src/rpc.rs`](crates/hero_osis_app/src/rpc.rs#L37-L42) builds the URL as: ```rust let url = if base.is_empty() { format!("/rpc/{context}") } else { format!("{}/rpc/{context}", base.trim_end_matches('/')) }; ``` Combined with [`crates/hero_osis_app/src/app.rs:38`](crates/hero_osis_app/src/app.rs#L38): ```rust crate::rpc::set_rpc_base("/hero_osis/ui"); ``` …the island POSTs to `/hero_osis/ui/rpc/root`. Through hero_router on `:9988`, that path resolves to `hero_osis_ui`'s HTTP server, which only serves admin HTML — there is no `/rpc/{context}` handler there. hero_router responds with the plain-text error `Socket 'rpc.sock' not found for 'hero_osis'`, which then fails JSON parsing in the client → "Service unavailable — unexpected response". This contradicts the documented architecture in [hero_os/crates/hero_os_app/src/config.rs:11-14](https://forge.ourworld.tf/lhumina_code/hero_os/src/branch/development/crates/hero_os_app/src/config.rs#L11-L14): > Per-domain OSIS services (e.g. `hero_osis_files`, `hero_osis_ai`) each own their own socket. The OSIS SDK targets `/hero_osis_<domain>/rpc` and carries the context in the `X-Hero-Context` HTTP header. ## Reproduction ```bash # What the island calls today (broken — non-JSON error): curl -sS -X POST -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"base.context.list","params":{}}' \ http://127.0.0.1:9988/hero_osis/ui/rpc/root # → Socket 'rpc.sock' not found for 'hero_osis' # What the architecture says it should call (works): curl -sS -X POST -H 'Content-Type: application/json' \ -H 'X-Hero-Context: root' \ -d '{"jsonrpc":"2.0","id":1,"method":"base.context.list","params":{}}' \ http://127.0.0.1:9988/hero_osis_base/rpc # → {"jsonrpc":"2.0","result":"","id":1} ``` ## Proposed fix In [`crates/hero_osis_app/src/rpc.rs`](crates/hero_osis_app/src/rpc.rs): 1. Derive the OSIS domain from the method prefix (e.g. `base.context.list` → `hero_osis_base`, `business.company.list` → `hero_osis_business`). 2. Build the URL as `{api_host}/hero_osis_{domain}/rpc` instead of `{base}/rpc/{context}`. 3. Send the context via the `X-Hero-Context: {context}` HTTP header (matches `hero_context` spec). 4. Drop the `/hero_osis/ui` `set_rpc_base` call in [`app.rs:38`](crates/hero_osis_app/src/app.rs#L38) — origin should come from `IslandContext::api_host` (already passed to the component as a prop) for consistency with other islands. Sketch: ```rust pub async fn rpc(context: &str, method: &str, params: Value) -> Result<Value, String> { let domain = method.split('.').next().unwrap_or("base"); let url = format!("{}/hero_osis_{domain}/rpc", api_host()); let mut req = gloo_net::http::Request::post(&url) .header("Content-Type", "application/json") .header("X-Hero-Context", context); // … } ``` ## Acceptance criteria - [ ] Opening the OSIS island in hero_os (built with `FEATURES=web-native`) successfully reaches `hero_osis_base` and lists contexts (or shows an empty list cleanly when none are seeded — that empty case is tracked separately). - [ ] No requests are sent to `/hero_osis/ui/rpc/...` paths. - [ ] All RPC calls carry `X-Hero-Context`, never put the context in the URL path. - [ ] Native admin run (`make dev-minimal` + open `hero_osis_ui` directly) still works.
Author
Member

Implementation Specification — Issue #37

Objective

Fix the OSIS Dioxus island (hero_osis_app) so its in-browser JSON-RPC client targets the correct hero_router URL pattern ({api_host}/hero_osis_<domain>/rpc) and carries the OSIS context via the X-Hero-Context HTTP header instead of in the URL path. The current path-based scheme (/hero_osis/ui/rpc/{context}) hits a non-existent route and breaks the OSIS island when opened from inside hero_os (built with FEATURES=web-native).

Requirements

  • The RPC client URL must follow the canonical Hero pattern: {api_host}/hero_osis_<domain>/rpc, where <domain> is derived from the JSON-RPC method's first dotted segment.
  • The OSIS context (e.g. root, geomind) must be sent via the X-Hero-Context HTTP header, never as a URL path segment.
  • The hard-coded set_rpc_base("/hero_osis/ui") call must be removed; URL origin must come from IslandContext::api_host (already a prop on HeroOsisApp).
  • All existing JSON-RPC callsites in hero_osis_app must continue to work after the fix:
    • base.context.list (must reach hero_osis_base)
    • base.context.get (must reach hero_osis_base)
    • rpc.discover (must reach a real OSIS domain service — see Notes)
  • Authorization: Bearer <hero_os_jwt> from localStorage must continue to be sent (used by hero_router auth gating; pre-existing behaviour, not in scope to remove).
  • Native admin (hero_osis_ui HTML+JS dashboard reached via make dev-minimal) must remain unaffected — it does not use hero_osis_app at all (it uses Askama templates and js/dashboard.js).
  • No requests should leave the browser targeting /hero_osis/ui/rpc/....

Files to Modify

  1. crates/hero_osis_app/src/rpc.rs — primary change. Replace path-based context routing with domain-derived URL + X-Hero-Context header. Replace the set_rpc_base("/hero_osis/ui") model with an api_host-based one.

  2. crates/hero_osis_app/src/app.rs — drop the set_rpc_base("/hero_osis/ui") call (line 38). Pass props.context.api_host to rpc.rs (via a renamed setter, e.g. set_api_host).

No new files; no other files in the repo reference crate::rpc::* from hero_osis_app.

Step-by-Step Implementation Plan

Step 1 — Rewrite crates/hero_osis_app/src/rpc.rs

Why: This is the load-bearing change. The current URL builder produces {base}/rpc/{context}, which routes through hero_router to hero_osis_ui's ui.sock (HTML-only) and fails. Replace with {api_host}/hero_osis_<domain>/rpc + X-Hero-Context header.

Concrete edits:

  • Rename the OnceLock and module helpers from RPC_BASE / set_rpc_base / rpc_base to API_HOST / set_api_host / api_host (semantic clarity: this is the hero_router origin, not a path prefix).
  • Rewrite the rpc function:
    • Derive domain from the method: let domain = method.split('.').next().unwrap_or("base");
    • Map rpc (the special namespace used by rpc.discover) to a real domain service. When domain == "rpc", fall back to "base" (since hero_osis_base is always present).
    • Build URL: let url = format!("{}/hero_osis_{domain}/rpc", api_host()); — when api_host() returns "" the URL becomes a same-origin relative path (/hero_osis_base/rpc), which is what we want behind hero_router.
    • Send context as a header: .header("X-Hero-Context", context)
    • Keep the existing Authorization: Bearer … JWT injection from auth_header().
    • Keep the existing JSON-RPC body structure and error handling.
  • Update the file-level doc comment to describe the new URL/header pattern.

Dependencies: none — must run before Step 2.

Step 2 — Update crates/hero_osis_app/src/app.rs to drop set_rpc_base and pass api_host

Why: The current call crate::rpc::set_rpc_base("/hero_osis/ui") hard-codes a path that doesn't exist on hero_router. The right value is the proxy origin, available via props.context.api_host (empty string by default → relative URLs, which is correct behind hero_router).

Concrete edits:

  • Replace line 38 (crate::rpc::set_rpc_base("/hero_osis/ui");) with crate::rpc::set_api_host(&props.context.api_host);.
  • Leave all three rpc::rpc("root", …) and rpc::rpc(&name, …) callsites otherwise unchanged — they already pass the context as the first argument, which the new rpc() will put in the X-Hero-Context header.

Dependencies: Step 1.

Step 3 — rpc.discover strategy

Why: The naive method.split('.').next() produces domain "rpc" for rpc.discover, which would route to a non-existent hero_osis_rpc service.

Decision: Option A (minimum-fix). Special-case domain == "rpc" → fall back to "base" inside rpc.rs. This sends rpc.discover to hero_osis_base/rpc. The "Domains in context" panel will then only show base methods, not all installed domains — a small UX regression compared to the design intent, but the original code never worked at all (failed entirely with "Service unavailable"), so functionally this is a strict improvement. A follow-up issue will track restoring full domain aggregation via fan-out (Option B).

Dependencies: Step 1 (this fix is part of the rpc.rs rewrite).

Step 4 — Verify no other callers of set_rpc_base

grep -rn "set_rpc_base\|RPC_BASE\|rpc_base" /home/rawan/codescalers/hero_osis/ — confirmed during exploration that the only references are inside hero_osis_app/src/rpc.rs (definitions) and one call in hero_osis_app/src/app.rs:38. No external crate depends on these symbols.

Dependencies: Steps 1 and 2.

Step 5 — Build verification

  • Build: cargo build -p hero_osis_app --target wasm32-unknown-unknown from the workspace root or crates/hero_osis_app/.
  • Manual verification per the issue's reproduction steps:
    1. curl http://127.0.0.1:9988/hero_osis_base/rpc -H 'X-Hero-Context: root' -d '{"jsonrpc":"2.0","id":1,"method":"base.context.list","params":{}}' should return a JSON-RPC result.
    2. Open the OSIS island in hero_os (built with FEATURES=web-native); browser DevTools Network tab should show POSTs to /hero_osis_base/rpc with X-Hero-Context: root, never to /hero_osis/ui/rpc/....
    3. Run native admin (make dev-minimal, open http://127.0.0.1:9988/hero_osis/ui/) → unchanged HTML dashboard renders (it uses js/dashboard.js, not hero_osis_app).

Dependencies: Steps 1–3.

Acceptance Criteria

  • Opening the OSIS island in hero_os (FEATURES=web-native) successfully reaches hero_osis_base and lists contexts.
  • No requests sent to /hero_osis/ui/rpc/... paths.
  • All RPC calls carry X-Hero-Context: <name>; the context never appears in the URL path.
  • Native admin (make dev-minimalhttp://127.0.0.1:9988/hero_osis/ui/) still works (unchanged because it doesn't use hero_osis_app).

Notes / Open Questions

  • Native admin is untouched. Exploration confirmed crates/hero_osis_ui/ is an Askama-templated HTML+JS dashboard whose RPC base is set via window.RPC_BASE = "{{ rpc_prefix }}" in templates/base.html, driven by hero_router's X-Forwarded-Prefix header. This code path does not import hero_osis_app. The only consumer of hero_osis_app::HeroOsisApp is hero_os/crates/hero_os_app/src/island_content.rs. So removing set_rpc_base("/hero_osis/ui") from the Dioxus island has zero effect on the native admin.

  • api_host fallback when running standalone. The IslandContext::default() sets api_host to "", which results in same-origin relative URLs. The standalone build path (the standalone feature in hero_osis_app/src/lib.rs) defaults api_host to "localhost" — suboptimal (would produce localhost/hero_osis_base/rpc). Out of scope for this PR; will track as a follow-up if needed.

  • rpc.discover aggregation regression. Sending rpc.discover to hero_osis_base returns only base's methods. The user-facing impact: the "Domains in context" panel will list only base.* methods after a context is selected. This is technically a UX regression from the design intent, but the original code never worked anyway, so functionally this is a strict improvement. Will be tracked as a follow-up issue.

  • JWT auth pattern preserved. The auth_header() helper reading hero_os_jwt from localStorage is unchanged.

  • OnceLock semantics. The renamed API_HOST keeps the same OnceLock set-once semantics — fine for the single mount in HeroOsisApp.

# Implementation Specification — Issue #37 ## Objective Fix the OSIS Dioxus island (`hero_osis_app`) so its in-browser JSON-RPC client targets the correct hero_router URL pattern (`{api_host}/hero_osis_<domain>/rpc`) and carries the OSIS context via the `X-Hero-Context` HTTP header instead of in the URL path. The current path-based scheme (`/hero_osis/ui/rpc/{context}`) hits a non-existent route and breaks the OSIS island when opened from inside hero_os (built with `FEATURES=web-native`). ## Requirements - The RPC client URL must follow the canonical Hero pattern: `{api_host}/hero_osis_<domain>/rpc`, where `<domain>` is derived from the JSON-RPC method's first dotted segment. - The OSIS context (e.g. `root`, `geomind`) must be sent via the `X-Hero-Context` HTTP header, never as a URL path segment. - The hard-coded `set_rpc_base("/hero_osis/ui")` call must be removed; URL origin must come from `IslandContext::api_host` (already a prop on `HeroOsisApp`). - All existing JSON-RPC callsites in `hero_osis_app` must continue to work after the fix: - `base.context.list` (must reach `hero_osis_base`) - `base.context.get` (must reach `hero_osis_base`) - `rpc.discover` (must reach a real OSIS domain service — see Notes) - `Authorization: Bearer <hero_os_jwt>` from `localStorage` must continue to be sent (used by hero_router auth gating; pre-existing behaviour, not in scope to remove). - Native admin (`hero_osis_ui` HTML+JS dashboard reached via `make dev-minimal`) must remain unaffected — it does not use `hero_osis_app` at all (it uses Askama templates and `js/dashboard.js`). - No requests should leave the browser targeting `/hero_osis/ui/rpc/...`. ## Files to Modify 1. **`crates/hero_osis_app/src/rpc.rs`** — primary change. Replace path-based context routing with domain-derived URL + `X-Hero-Context` header. Replace the `set_rpc_base("/hero_osis/ui")` model with an `api_host`-based one. 2. **`crates/hero_osis_app/src/app.rs`** — drop the `set_rpc_base("/hero_osis/ui")` call (line 38). Pass `props.context.api_host` to `rpc.rs` (via a renamed setter, e.g. `set_api_host`). No new files; no other files in the repo reference `crate::rpc::*` from `hero_osis_app`. ## Step-by-Step Implementation Plan ### Step 1 — Rewrite `crates/hero_osis_app/src/rpc.rs` **Why:** This is the load-bearing change. The current URL builder produces `{base}/rpc/{context}`, which routes through hero_router to `hero_osis_ui`'s ui.sock (HTML-only) and fails. Replace with `{api_host}/hero_osis_<domain>/rpc` + `X-Hero-Context` header. Concrete edits: - Rename the `OnceLock` and module helpers from `RPC_BASE` / `set_rpc_base` / `rpc_base` to `API_HOST` / `set_api_host` / `api_host` (semantic clarity: this is the hero_router origin, not a path prefix). - Rewrite the `rpc` function: - Derive `domain` from the method: `let domain = method.split('.').next().unwrap_or("base");` - Map `rpc` (the special namespace used by `rpc.discover`) to a real domain service. When `domain == "rpc"`, fall back to `"base"` (since `hero_osis_base` is always present). - Build URL: `let url = format!("{}/hero_osis_{domain}/rpc", api_host());` — when `api_host()` returns `""` the URL becomes a same-origin relative path (`/hero_osis_base/rpc`), which is what we want behind hero_router. - Send context as a header: `.header("X-Hero-Context", context)` - Keep the existing `Authorization: Bearer …` JWT injection from `auth_header()`. - Keep the existing JSON-RPC body structure and error handling. - Update the file-level doc comment to describe the new URL/header pattern. **Dependencies:** none — must run before Step 2. ### Step 2 — Update `crates/hero_osis_app/src/app.rs` to drop `set_rpc_base` and pass `api_host` **Why:** The current call `crate::rpc::set_rpc_base("/hero_osis/ui")` hard-codes a path that doesn't exist on hero_router. The right value is the proxy origin, available via `props.context.api_host` (empty string by default → relative URLs, which is correct behind hero_router). Concrete edits: - Replace line 38 (`crate::rpc::set_rpc_base("/hero_osis/ui");`) with `crate::rpc::set_api_host(&props.context.api_host);`. - Leave all three `rpc::rpc("root", …)` and `rpc::rpc(&name, …)` callsites otherwise unchanged — they already pass the context as the first argument, which the new `rpc()` will put in the `X-Hero-Context` header. **Dependencies:** Step 1. ### Step 3 — `rpc.discover` strategy **Why:** The naive `method.split('.').next()` produces domain `"rpc"` for `rpc.discover`, which would route to a non-existent `hero_osis_rpc` service. **Decision: Option A (minimum-fix).** Special-case `domain == "rpc"` → fall back to `"base"` inside `rpc.rs`. This sends `rpc.discover` to `hero_osis_base/rpc`. The "Domains in context" panel will then only show base methods, not all installed domains — a small UX regression compared to the design intent, but the original code never worked at all (failed entirely with "Service unavailable"), so functionally this is a strict improvement. A follow-up issue will track restoring full domain aggregation via fan-out (Option B). **Dependencies:** Step 1 (this fix is part of the rpc.rs rewrite). ### Step 4 — Verify no other callers of `set_rpc_base` `grep -rn "set_rpc_base\|RPC_BASE\|rpc_base" /home/rawan/codescalers/hero_osis/` — confirmed during exploration that the only references are inside `hero_osis_app/src/rpc.rs` (definitions) and one call in `hero_osis_app/src/app.rs:38`. No external crate depends on these symbols. **Dependencies:** Steps 1 and 2. ### Step 5 — Build verification - Build: `cargo build -p hero_osis_app --target wasm32-unknown-unknown` from the workspace root or `crates/hero_osis_app/`. - Manual verification per the issue's reproduction steps: 1. `curl http://127.0.0.1:9988/hero_osis_base/rpc -H 'X-Hero-Context: root' -d '{"jsonrpc":"2.0","id":1,"method":"base.context.list","params":{}}'` should return a JSON-RPC result. 2. Open the OSIS island in hero_os (built with `FEATURES=web-native`); browser DevTools Network tab should show POSTs to `/hero_osis_base/rpc` with `X-Hero-Context: root`, never to `/hero_osis/ui/rpc/...`. 3. Run native admin (`make dev-minimal`, open `http://127.0.0.1:9988/hero_osis/ui/`) → unchanged HTML dashboard renders (it uses `js/dashboard.js`, not `hero_osis_app`). **Dependencies:** Steps 1–3. ## Acceptance Criteria - [ ] Opening the OSIS island in hero_os (`FEATURES=web-native`) successfully reaches `hero_osis_base` and lists contexts. - [ ] No requests sent to `/hero_osis/ui/rpc/...` paths. - [ ] All RPC calls carry `X-Hero-Context: <name>`; the context never appears in the URL path. - [ ] Native admin (`make dev-minimal` → `http://127.0.0.1:9988/hero_osis/ui/`) still works (unchanged because it doesn't use `hero_osis_app`). ## Notes / Open Questions - **Native admin is untouched.** Exploration confirmed `crates/hero_osis_ui/` is an Askama-templated HTML+JS dashboard whose RPC base is set via `window.RPC_BASE = "{{ rpc_prefix }}"` in `templates/base.html`, driven by hero_router's `X-Forwarded-Prefix` header. This code path does not import `hero_osis_app`. The only consumer of `hero_osis_app::HeroOsisApp` is `hero_os/crates/hero_os_app/src/island_content.rs`. So removing `set_rpc_base("/hero_osis/ui")` from the Dioxus island has zero effect on the native admin. - **`api_host` fallback when running standalone.** The `IslandContext::default()` sets `api_host` to `""`, which results in same-origin relative URLs. The standalone build path (the `standalone` feature in `hero_osis_app/src/lib.rs`) defaults `api_host` to `"localhost"` — suboptimal (would produce `localhost/hero_osis_base/rpc`). Out of scope for this PR; will track as a follow-up if needed. - **`rpc.discover` aggregation regression.** Sending `rpc.discover` to `hero_osis_base` returns only base's methods. The user-facing impact: the "Domains in context" panel will list only `base.*` methods after a context is selected. This is technically a UX regression from the design intent, but the original code never worked anyway, so functionally this is a strict improvement. Will be tracked as a follow-up issue. - **JWT auth pattern preserved.** The `auth_header()` helper reading `hero_os_jwt` from `localStorage` is unchanged. - **`OnceLock` semantics.** The renamed `API_HOST` keeps the same `OnceLock` set-once semantics — fine for the single mount in `HeroOsisApp`.
Author
Member

Test Results

  • Total: 113
  • Passed: 113
  • Failed: 0
  • Ignored: 0

Build verification: cargo check --target wasm32-unknown-unknown on crates/hero_osis_app passed clean.

Note: hero_osis_app is excluded from the parent Cargo workspace (it is a Dioxus WASM island consumed via git from hero_os), so cargo test --workspace does not include it. The wasm-only crate has no Rust unit tests today; verification is via the build check above and manual reproduction of the issue's curl steps.

Invocation used: make test (which runs cargo test --lib --no-default-features --features all-domains, matching CI). Result: 113 passed across hero_osis_sdk (0), hero_osis_server (113), hero_osis_ui (0); 0 failed; 0 ignored.

Workspace build note (pre-existing, unrelated)

cargo test --workspace --no-fail-fast from the repo root does not reach the test-running phase because hero_osis_examples fails to compile on a pre-existing issue in crates/hero_osis_examples/examples/multi_domain.rs:

error[E0599]: no variant or associated item named `Active` found for enum `ProjectStatus` in the current scope
  --> crates/hero_osis_examples/examples/multi_domain.rs:48:37
   |
48 |     project.status = ProjectStatus::Active;
   |                                     ^^^^^^ variant or associated item not found in `ProjectStatus`

This is unrelated to the rpc.rs / app.rs changes (both files live in hero_osis_app, which is outside the workspace) and exists on development independently.

## Test Results - Total: 113 - Passed: 113 - Failed: 0 - Ignored: 0 Build verification: `cargo check --target wasm32-unknown-unknown` on `crates/hero_osis_app` passed clean. Note: `hero_osis_app` is excluded from the parent Cargo workspace (it is a Dioxus WASM island consumed via git from hero_os), so `cargo test --workspace` does not include it. The wasm-only crate has no Rust unit tests today; verification is via the build check above and manual reproduction of the issue's curl steps. Invocation used: `make test` (which runs `cargo test --lib --no-default-features --features all-domains`, matching CI). Result: 113 passed across `hero_osis_sdk` (0), `hero_osis_server` (113), `hero_osis_ui` (0); 0 failed; 0 ignored. ### Workspace build note (pre-existing, unrelated) `cargo test --workspace --no-fail-fast` from the repo root does not reach the test-running phase because `hero_osis_examples` fails to compile on a pre-existing issue in `crates/hero_osis_examples/examples/multi_domain.rs`: ``` error[E0599]: no variant or associated item named `Active` found for enum `ProjectStatus` in the current scope --> crates/hero_osis_examples/examples/multi_domain.rs:48:37 | 48 | project.status = ProjectStatus::Active; | ^^^^^^ variant or associated item not found in `ProjectStatus` ``` This is unrelated to the `rpc.rs` / `app.rs` changes (both files live in `hero_osis_app`, which is outside the workspace) and exists on `development` independently.
Author
Member

Implementation Summary

Changes Made

crates/hero_osis_app/src/rpc.rs — rewrote the JSON-RPC client to use the canonical Hero URL pattern.

  • Renamed module state and helpers: RPC_BASEAPI_HOST, set_rpc_baseset_api_host, rpc_baseapi_host. Semantically this is now the hero_router origin, not a path prefix.
  • Replaced the URL builder. Old: {base}/rpc/{context}. New: {api_host}/hero_osis_<domain>/rpc, where <domain> is the first dotted segment of the JSON-RPC method name.
  • Special-cased domain == "rpc" to fall back to "base" so rpc.discover reaches hero_osis_base/rpc (rather than the non-existent hero_osis_rpc).
  • Added X-Hero-Context: <context> header to every request. The context is no longer placed in the URL path.
  • When api_host() returns an empty string, the URL becomes a same-origin relative path (e.g. /hero_osis_base/rpc) — correct behaviour behind hero_router.
  • Preserved auth_header() Bearer-token injection from localStorage, the JSON-RPC body shape, and the response/error parsing.
  • Updated the file-level //! doc comment to describe the new URL pattern, domain derivation rule (including the rpcbase fallback), X-Hero-Context header, and same-origin behaviour.

crates/hero_osis_app/src/app.rs — switched from a hard-coded RPC base to the island-context-supplied API host.

  • Replaced crate::rpc::set_rpc_base("/hero_osis/ui"); (line 38) with crate::rpc::set_api_host(&props.context.api_host);.
  • All three existing rpc::rpc("root", …) and rpc::rpc(&name, …) callsites are unchanged — they already pass the context as the first argument, which the new rpc() puts in the X-Hero-Context header.

Verification

  • cargo check --target wasm32-unknown-unknown on crates/hero_osis_app passes clean.
  • make test (the CI-style invocation: cargo test --lib --no-default-features --features all-domains) passes: 113/113 in hero_osis_server, 0 tests in hero_osis_sdk and hero_osis_ui.
  • grep -rn "set_rpc_base\\|RPC_BASE\\|rpc_base" --include="*.rs" returns no matches — no orphan callers of the renamed symbols.

Notes

  • hero_osis_app is excluded from the parent Cargo workspace (it is a Dioxus WASM island consumed by hero_os via git dependency). It has no Rust unit tests today; verification is via the build check and the issue's curl reproduction steps.
  • The rpc.discover call now hits hero_osis_base only (instead of all installed domains). This is a UX regression from the design intent, but the prior behaviour was completely broken ("Service unavailable"), so functionally this is a strict improvement. A follow-up may restore aggregated discover by fanning out to every installed OSIS domain.
  • Native admin (make dev-minimalhttp://127.0.0.1:9988/hero_osis/ui/) is unaffected. It uses Askama templates + js/dashboard.js and does not import hero_osis_app.
## Implementation Summary ### Changes Made **`crates/hero_osis_app/src/rpc.rs`** — rewrote the JSON-RPC client to use the canonical Hero URL pattern. - Renamed module state and helpers: `RPC_BASE` → `API_HOST`, `set_rpc_base` → `set_api_host`, `rpc_base` → `api_host`. Semantically this is now the hero_router origin, not a path prefix. - Replaced the URL builder. Old: `{base}/rpc/{context}`. New: `{api_host}/hero_osis_<domain>/rpc`, where `<domain>` is the first dotted segment of the JSON-RPC method name. - Special-cased `domain == "rpc"` to fall back to `"base"` so `rpc.discover` reaches `hero_osis_base/rpc` (rather than the non-existent `hero_osis_rpc`). - Added `X-Hero-Context: <context>` header to every request. The context is no longer placed in the URL path. - When `api_host()` returns an empty string, the URL becomes a same-origin relative path (e.g. `/hero_osis_base/rpc`) — correct behaviour behind hero_router. - Preserved `auth_header()` Bearer-token injection from `localStorage`, the JSON-RPC body shape, and the response/error parsing. - Updated the file-level `//!` doc comment to describe the new URL pattern, domain derivation rule (including the `rpc` → `base` fallback), `X-Hero-Context` header, and same-origin behaviour. **`crates/hero_osis_app/src/app.rs`** — switched from a hard-coded RPC base to the island-context-supplied API host. - Replaced `crate::rpc::set_rpc_base("/hero_osis/ui");` (line 38) with `crate::rpc::set_api_host(&props.context.api_host);`. - All three existing `rpc::rpc("root", …)` and `rpc::rpc(&name, …)` callsites are unchanged — they already pass the context as the first argument, which the new `rpc()` puts in the `X-Hero-Context` header. ### Verification - `cargo check --target wasm32-unknown-unknown` on `crates/hero_osis_app` passes clean. - `make test` (the CI-style invocation: `cargo test --lib --no-default-features --features all-domains`) passes: 113/113 in `hero_osis_server`, 0 tests in `hero_osis_sdk` and `hero_osis_ui`. - `grep -rn "set_rpc_base\\|RPC_BASE\\|rpc_base" --include="*.rs"` returns no matches — no orphan callers of the renamed symbols. ### Notes - `hero_osis_app` is excluded from the parent Cargo workspace (it is a Dioxus WASM island consumed by `hero_os` via git dependency). It has no Rust unit tests today; verification is via the build check and the issue's curl reproduction steps. - The `rpc.discover` call now hits `hero_osis_base` only (instead of all installed domains). This is a UX regression from the design intent, but the prior behaviour was completely broken ("Service unavailable"), so functionally this is a strict improvement. A follow-up may restore aggregated discover by fanning out to every installed OSIS domain. - Native admin (`make dev-minimal` → `http://127.0.0.1:9988/hero_osis/ui/`) is unaffected. It uses Askama templates + `js/dashboard.js` and does not import `hero_osis_app`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_osis#37
No description provided.