[hero_codescalers] Fix prefix routing through hero_router #19
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?
Problem
hero_codescalers is not correctly reachable through hero_router due to a
prefix routing issue. The service was tested directly (netcat) but not
through the router, so the prefix forwarding was never validated.
Steps to fix
injected by hero_router
Acceptance Criteria
Implementation Spec for Issue #19
Objective
Make
hero_codescalers_uicorrectly reachable viahero_routerat/hero_codescalers/ui(and any future webname alias such as/hero_codescalers/admin). The UI service already extractsX-Forwarded-Prefixand uses{{ base_path }}in templates, but the design has two latent problems that prevent the end-to-end browser → hero_router → hero_codescalers_ui path from working in every supported case, and this path was never validated. This spec fixes the prefix wiring, makes the in-page JSON-RPC calls always go through hero_router via the sibling-shortcut convention, and adds a real end-to-end test that exercises the path from the TCP entry point.Requirements
X-Forwarded-Prefix) and stamp it into every absolute link, asset URL, and JSON-RPC URL emitted to the browser. This already partially works — finish the job.RPC_BASE + "/rpc") must reachrpc.sockofhero_codescalersregardless of which webname the UI is mounted under (uitoday,adminis a future alias). Today therpc_basecomputation hard-codes the"/ui"suffix and silently produces an emptyRPC_BASEfor any other webname, which collides with hero_router's own TCP-level/rpcand breaks all RPC.hero_router's TCP listener (not netcat into the UI Unix socket). It must verify HTML loading, static assets,/health,/.well-known/heroservice.json, and a JSON-RPC round-trip.hero_web_prefixskill convention so future contributors do not regress this.Files to Modify/Create
Modify:
crates/hero_codescalers_ui/src/main.rscrates/hero_codescalers_ui/templates/base.htmlcrates/hero_codescalers_ui/templates/index.html(only if any new absolute URL is added; current contents already use{{ base_path }}consistently)Makefile(addmake test-routertarget)Create:
crates/hero_codescalers_ui/scripts/test-router-prefix.sh— curl-driven end-to-end smoke test through hero_router's TCP port.No new Rust modules are required — the existing
base_path_middlewareandIndexTemplatealready cover everything; the change is one struct field, one handler line, two template lines, one bash script.Implementation Plan
Step 1: Fix the
rpc_basecomputation so it works for any webnameFiles:
crates/hero_codescalers_ui/src/main.rsSubtasks:
Locate
index_handler(current logic computesrpc_baseasformat!("{}/rpc", &base_path[..base_path.len() - 3])only whenbase_path.ends_with("/ui")).Replace with the sibling-shortcut path that hero_router already supports for both
uiandadminwebnames (seehero_router/crates/hero_router/src/server/routes.rslines 1100-1144 —POST /<svc>/<webname>/rpcis rewritten internally torpc.sockPOST /rpc):Rationale: this collapses both webname cases (and any future alias) into one branch, removes the silent failure when accessed under
/admin, and uses the documented hero_router shortcut. The same-origin direct-access fallback is preserved (emptybase_path→ emptyrpc_base→ browser fetches/rpc→ localrpc_proxy_handler).Confirm the existing
IndexTemplatefieldrpc_base: Stringstill matches; no struct change needed.Add a 2-line doc comment above
index_handlerreferencing the hero_web_prefix skill and the hero_router sibling shortcut so the next reader does not re-introduce the bug.Dependencies: none.
Step 2: Audit templates for any remaining unprefixed absolute URLs
Files:
crates/hero_codescalers_ui/templates/base.htmlcrates/hero_codescalers_ui/templates/index.htmlSubtasks:
href="/,src="/,action="/, and any string literal starting with/. Confirm every one of them is preceded by{{ base_path }}.base.htmlthat definesBASE_PATHandRPC_BASEis reading the same Tera variables produced byIndexTemplate./css/,/js/,/favicon.svg, or hash-based but absolute link is found without{{ base_path }}, prefix it.Dependencies: none — can run in parallel with Step 1.
Step 3: Verify the JS layer respects
BASE_PATH/RPC_BASEeverywhereFiles:
crates/hero_codescalers_ui/static/js/dashboard.jscrates/hero_codescalers_ui/static/js/connection-status.jsSubtasks:
rpcCall()usesRPC_BASE + '/rpc'.initDashboard()builds the connection-statushealthUrlasBASE_PATH + '/health'.connection-status.jsonly fetchesopts.healthUrland never hard-codes a path.window.location.hash— never the path.BASE_PATHandRPC_BASE, change it.Dependencies: Step 1.
Step 4: Add an end-to-end smoke test through hero_router
Files (create):
crates/hero_codescalers_ui/scripts/test-router-prefix.shFiles (modify):
MakefileSubtasks:
ROUTER_HOST(default127.0.0.1) andROUTER_PORT(default9988) andSERVICE(defaulthero_codescalers).BASE_PATH = "/hero_codescalers/ui"andRPC_BASE = "/hero_codescalers/rpc"./healthis reachable through the prefix and returns JSON with"service":"hero_codescalers".crates/hero_codescalers_ui/scripts/test-users-ui.sh.test-routertarget to the top-level Makefile.Dependencies: Step 1, Step 2.
Step 5: Document the convention for future contributors
Files:
crates/hero_codescalers_ui/src/main.rs(extend module docstring)CLAUDE.md(one-paragraph note under Architecture)Subtasks:
hero_web_prefixClaude skill.CLAUDE.mdexplaining the prefix mount point andmake test-router.Dependencies: Step 1, Step 4.
Acceptance Criteria
make test-routerpasses against a running stack (service_codescalers startplus a runninghero_router).http://<router-host>:9988/hero_codescalers/ui/in a browser renders the dashboard, all CSS/JS/images load (no 404s), connection-status dot is green, and tab switches / RPC calls succeed.http://<router-host>:9988/hero_codescalers/ui(no trailing slash) also renders.grep -nE 'href="/|src="/|action="/' crates/hero_codescalers_ui/templates/*.htmlreturns zero hits without{{ base_path }}immediately after the opening quote.Notes
X-Forwarded-Prefix: /<service_name>/<webname>with no trailing slash. The middleware inmain.rsalready trims trailing slashes defensively.POST /<svc>/ui/rpcandPOST /<svc>/admin/rpcboth reachrpc.sock) is the linchpin of the Step 1 fix — no client-side changes are required, only emitting the correct URL from the template.hero_proc_uiis the canonical reference implementation. It does not need a separaterpc_basebecause it merges the generatedopenrpc_proxy!router and the JS usesBASE_PATH + '/rpc'which equals the sibling-shortcut path that hero_router rewrites. The fix in Step 1 brings hero_codescalers_ui to the same shape.admin_secretswhitelist gate runs only on TCP connections, not on UDS — when traffic arrives via hero_router's UDS proxy toui.sock, it is correctly bypassed. This is unchanged by this issue.Test Results
cargo check --workspacepass
cargo test --workspace --libpass
(Workspace libs
hero_codescalers_sdkandnu_execcompiled and ran with 0 tests defined;test result: okfor both.)Smoke script syntax (
bash -n scripts/test-router-prefix.sh)pass
Note
make test-routerwas not run here — it requires a livehero_router+ runninghero_codescalersservice. Run it manually after deploying:Implementation Summary
Changes
crates/hero_codescalers_ui/src/main.rs(+13 / -3)index_handler: replaced the/ui-suffix-specificrpc_basecomputation with a webname-agnostic sibling-shortcut form. Whenbase_pathis non-empty,rpc_base = "{base_path}/rpc"(e.g./hero_codescalers/ui/rpc); when empty (direct UDS access),rpc_base = ""so the browser falls through to same-origin/rpcserved by the localrpc_proxy_handler.//comment above the block explaining the sibling-shortcut convention and pointing at thehero_web_prefixskill.//!docstring with a "Reverse-proxy prefix" note (template prefix rule, RPC_BASE rule, X-Forwarded-Prefix source).crates/hero_codescalers_ui/scripts/test-router-prefix.sh(new, executable)BASE_PATH/RPC_BASEJS values, a static asset,/health,.well-known/heroservice.json, and a sibling-shortcut JSON-RPC round-trip.ROUTER_HOST(127.0.0.1),ROUTER_PORT(9988),SERVICE(hero_codescalers),WEBNAME(ui).scripts/test-users-ui.sh.Makefile(+4 / -1)test-routertarget invokingbash crates/hero_codescalers_ui/scripts/test-router-prefix.sh, placed adjacent totest-serverandtest-ui.test-routeradded to.PHONY.CLAUDE.md(+4 / -0)/hero_codescalers/uimount, theX-Forwarded-Prefixinjection, the sibling-shortcut RPC rewrite, and themake test-routervalidation step.crates/hero_codescalers_ui/templates/*.html— no changesAudit confirmed every absolute URL in
base.htmlandindex.htmlis already prefixed with{{ base_path }}and the inline<script>block stamps bothBASE_PATHandRPC_BASEfrom the Tera variables.crates/hero_codescalers_ui/static/js/*.js— no changesVerified
dashboard.jsusesRPC_BASE + '/rpc'andBASE_PATH + '/health'consistently;connection-status.jsonly fetchesopts.healthUrl; the hash router never touches the path.Tests
cargo check --workspace: passcargo test --workspace --lib: pass (2 lib crates, all green)bash -n scripts/test-router-prefix.sh: passManual validation (post-deploy)
Run this once
hero_routerandhero_codescalersare running on this machine:Then load
http://<router-host>:9988/hero_codescalers/ui/in a browser and confirm:session.list) succeeds with norpcCall failedconsole errors./rpc.Acceptance criteria mapping
make test-router.make test-router.Closing this. The earlier commit was reverted in
71512f6. Approach was wrong: it added a custom bash smoke script and Makefile target instead of relying on the existing hero_router alignment. No code change is required here beyond what hero_router already does.