OSIS island RPC client uses wrong URL pattern — cannot connect from hero_os #37
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_osis#37
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
When the OSIS native island is opened inside hero_os (built with
FEATURES=web-native), the dashboard shows:The error message is misleading —
hero_osis_serveris running, contexts default toroot(perServerCliin 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.rsbuilds the URL as:Combined with
crates/hero_osis_app/src/app.rs:38:…the island POSTs to
/hero_osis/ui/rpc/root. Through hero_router on:9988, that path resolves tohero_osis_ui's HTTP server, which only serves admin HTML — there is no/rpc/{context}handler there. hero_router responds with the plain-text errorSocket '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:
Reproduction
Proposed fix
In
crates/hero_osis_app/src/rpc.rs:base.context.list→hero_osis_base,business.company.list→hero_osis_business).{api_host}/hero_osis_{domain}/rpcinstead of{base}/rpc/{context}.X-Hero-Context: {context}HTTP header (matcheshero_contextspec)./hero_osis/uiset_rpc_basecall inapp.rs:38— origin should come fromIslandContext::api_host(already passed to the component as a prop) for consistency with other islands.Sketch:
Acceptance criteria
FEATURES=web-native) successfully reacheshero_osis_baseand lists contexts (or shows an empty list cleanly when none are seeded — that empty case is tracked separately)./hero_osis/ui/rpc/...paths.X-Hero-Context, never put the context in the URL path.make dev-minimal+ openhero_osis_uidirectly) still works.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 theX-Hero-ContextHTTP 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 withFEATURES=web-native).Requirements
{api_host}/hero_osis_<domain>/rpc, where<domain>is derived from the JSON-RPC method's first dotted segment.root,geomind) must be sent via theX-Hero-ContextHTTP header, never as a URL path segment.set_rpc_base("/hero_osis/ui")call must be removed; URL origin must come fromIslandContext::api_host(already a prop onHeroOsisApp).hero_osis_appmust continue to work after the fix:base.context.list(must reachhero_osis_base)base.context.get(must reachhero_osis_base)rpc.discover(must reach a real OSIS domain service — see Notes)Authorization: Bearer <hero_os_jwt>fromlocalStoragemust continue to be sent (used by hero_router auth gating; pre-existing behaviour, not in scope to remove).hero_osis_uiHTML+JS dashboard reached viamake dev-minimal) must remain unaffected — it does not usehero_osis_appat all (it uses Askama templates andjs/dashboard.js)./hero_osis/ui/rpc/....Files to Modify
crates/hero_osis_app/src/rpc.rs— primary change. Replace path-based context routing with domain-derived URL +X-Hero-Contextheader. Replace theset_rpc_base("/hero_osis/ui")model with anapi_host-based one.crates/hero_osis_app/src/app.rs— drop theset_rpc_base("/hero_osis/ui")call (line 38). Passprops.context.api_hosttorpc.rs(via a renamed setter, e.g.set_api_host).No new files; no other files in the repo reference
crate::rpc::*fromhero_osis_app.Step-by-Step Implementation Plan
Step 1 — Rewrite
crates/hero_osis_app/src/rpc.rsWhy: This is the load-bearing change. The current URL builder produces
{base}/rpc/{context}, which routes through hero_router tohero_osis_ui's ui.sock (HTML-only) and fails. Replace with{api_host}/hero_osis_<domain>/rpc+X-Hero-Contextheader.Concrete edits:
OnceLockand module helpers fromRPC_BASE/set_rpc_base/rpc_basetoAPI_HOST/set_api_host/api_host(semantic clarity: this is the hero_router origin, not a path prefix).rpcfunction:domainfrom the method:let domain = method.split('.').next().unwrap_or("base");rpc(the special namespace used byrpc.discover) to a real domain service. Whendomain == "rpc", fall back to"base"(sincehero_osis_baseis always present).let url = format!("{}/hero_osis_{domain}/rpc", api_host());— whenapi_host()returns""the URL becomes a same-origin relative path (/hero_osis_base/rpc), which is what we want behind hero_router..header("X-Hero-Context", context)Authorization: Bearer …JWT injection fromauth_header().Dependencies: none — must run before Step 2.
Step 2 — Update
crates/hero_osis_app/src/app.rsto dropset_rpc_baseand passapi_hostWhy: 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 viaprops.context.api_host(empty string by default → relative URLs, which is correct behind hero_router).Concrete edits:
crate::rpc::set_rpc_base("/hero_osis/ui");) withcrate::rpc::set_api_host(&props.context.api_host);.rpc::rpc("root", …)andrpc::rpc(&name, …)callsites otherwise unchanged — they already pass the context as the first argument, which the newrpc()will put in theX-Hero-Contextheader.Dependencies: Step 1.
Step 3 —
rpc.discoverstrategyWhy: The naive
method.split('.').next()produces domain"rpc"forrpc.discover, which would route to a non-existenthero_osis_rpcservice.Decision: Option A (minimum-fix). Special-case
domain == "rpc"→ fall back to"base"insiderpc.rs. This sendsrpc.discovertohero_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_basegrep -rn "set_rpc_base\|RPC_BASE\|rpc_base" /home/rawan/codescalers/hero_osis/— confirmed during exploration that the only references are insidehero_osis_app/src/rpc.rs(definitions) and one call inhero_osis_app/src/app.rs:38. No external crate depends on these symbols.Dependencies: Steps 1 and 2.
Step 5 — Build verification
cargo build -p hero_osis_app --target wasm32-unknown-unknownfrom the workspace root orcrates/hero_osis_app/.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.FEATURES=web-native); browser DevTools Network tab should show POSTs to/hero_osis_base/rpcwithX-Hero-Context: root, never to/hero_osis/ui/rpc/....make dev-minimal, openhttp://127.0.0.1:9988/hero_osis/ui/) → unchanged HTML dashboard renders (it usesjs/dashboard.js, nothero_osis_app).Dependencies: Steps 1–3.
Acceptance Criteria
FEATURES=web-native) successfully reacheshero_osis_baseand lists contexts./hero_osis/ui/rpc/...paths.X-Hero-Context: <name>; the context never appears in the URL path.make dev-minimal→http://127.0.0.1:9988/hero_osis/ui/) still works (unchanged because it doesn't usehero_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 viawindow.RPC_BASE = "{{ rpc_prefix }}"intemplates/base.html, driven by hero_router'sX-Forwarded-Prefixheader. This code path does not importhero_osis_app. The only consumer ofhero_osis_app::HeroOsisAppishero_os/crates/hero_os_app/src/island_content.rs. So removingset_rpc_base("/hero_osis/ui")from the Dioxus island has zero effect on the native admin.api_hostfallback when running standalone. TheIslandContext::default()setsapi_hostto"", which results in same-origin relative URLs. The standalone build path (thestandalonefeature inhero_osis_app/src/lib.rs) defaultsapi_hostto"localhost"— suboptimal (would producelocalhost/hero_osis_base/rpc). Out of scope for this PR; will track as a follow-up if needed.rpc.discoveraggregation regression. Sendingrpc.discovertohero_osis_basereturns only base's methods. The user-facing impact: the "Domains in context" panel will list onlybase.*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 readinghero_os_jwtfromlocalStorageis unchanged.OnceLocksemantics. The renamedAPI_HOSTkeeps the sameOnceLockset-once semantics — fine for the single mount inHeroOsisApp.Test Results
Build verification:
cargo check --target wasm32-unknown-unknownoncrates/hero_osis_apppassed clean.Note:
hero_osis_appis excluded from the parent Cargo workspace (it is a Dioxus WASM island consumed via git from hero_os), socargo test --workspacedoes 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 runscargo test --lib --no-default-features --features all-domains, matching CI). Result: 113 passed acrosshero_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-fastfrom the repo root does not reach the test-running phase becausehero_osis_examplesfails to compile on a pre-existing issue incrates/hero_osis_examples/examples/multi_domain.rs:This is unrelated to the
rpc.rs/app.rschanges (both files live inhero_osis_app, which is outside the workspace) and exists ondevelopmentindependently.Implementation Summary
Changes Made
crates/hero_osis_app/src/rpc.rs— rewrote the JSON-RPC client to use the canonical Hero URL pattern.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.{base}/rpc/{context}. New:{api_host}/hero_osis_<domain>/rpc, where<domain>is the first dotted segment of the JSON-RPC method name.domain == "rpc"to fall back to"base"sorpc.discoverreacheshero_osis_base/rpc(rather than the non-existenthero_osis_rpc).X-Hero-Context: <context>header to every request. The context is no longer placed in the URL path.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.auth_header()Bearer-token injection fromlocalStorage, the JSON-RPC body shape, and the response/error parsing.//!doc comment to describe the new URL pattern, domain derivation rule (including therpc→basefallback),X-Hero-Contextheader, 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.crate::rpc::set_rpc_base("/hero_osis/ui");(line 38) withcrate::rpc::set_api_host(&props.context.api_host);.rpc::rpc("root", …)andrpc::rpc(&name, …)callsites are unchanged — they already pass the context as the first argument, which the newrpc()puts in theX-Hero-Contextheader.Verification
cargo check --target wasm32-unknown-unknownoncrates/hero_osis_apppasses clean.make test(the CI-style invocation:cargo test --lib --no-default-features --features all-domains) passes: 113/113 inhero_osis_server, 0 tests inhero_osis_sdkandhero_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_appis excluded from the parent Cargo workspace (it is a Dioxus WASM island consumed byhero_osvia git dependency). It has no Rust unit tests today; verification is via the build check and the issue's curl reproduction steps.rpc.discovercall now hitshero_osis_baseonly (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.make dev-minimal→http://127.0.0.1:9988/hero_osis/ui/) is unaffected. It uses Askama templates +js/dashboard.jsand does not importhero_osis_app.