P2P support and ticketing system for Hero products over encrypted overlay network.
  • Rust 91%
  • HTML 4.2%
  • TypeScript 2.8%
  • Shell 1.4%
  • JavaScript 0.5%
  • Other 0.1%
Find a file
despiegk 3c7a42ae23
Some checks failed
lab publish / publish (push) Failing after 30s
chore: remove hard version pinning from hero_* dependencies
Remove explicit version constraints from hero_proc_sdk, hero_sdk_derive,
hero_rpc_openrpc, herolib_core, and hero_proxy_sdk dependencies where git
branch is set to development. This allows Cargo to use the version from the
git repository head instead of enforcing a specific pinned version.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-06 08:36:25 +02:00
.cargo feat(workspace): snapshot hero_collab → hero_assistance, rename, baseline build green 2026-04-28 15:45:55 -04:00
.forgejo/workflows ci: install lab onto PATH from ~/.local/bin in the publish workflow 2026-05-29 15:37:51 -04:00
crates chore: remove hard version pinning from hero_* dependencies 2026-06-06 08:36:25 +02:00
docs docs(e2e_checklist): s2-028 final walkthrough stamps (#18 row 9) (#25) 2026-05-23 18:29:24 +00:00
runs chore(sessions,docs)(session-32): record session-32 manifest + CLAUDE.md/prompt.md handoff + runs/phase16b_findings.md root-cause update 2026-05-02 00:56:49 -04:00
scripts chore(cleanup): Phase D — ADR-0001 cleanup + s2-016 leftovers + post-D-26 doc polish 2026-05-22 23:00:59 -04:00
tests feat(ui)(phase-25-part-2c): admin dashboard route-based deep linking + Projects tab + visual-baseline tooling 2026-05-07 15:12:45 -04:00
.gitignore chore(cleanup): Phase D — ADR-0001 cleanup + s2-016 leftovers + post-D-26 doc polish 2026-05-22 23:00:59 -04:00
buildenv.sh feat(_server,_sdk)(session-34): close L-06 — /events GET on TCP listener with per-recipient server-side filter (D-20) + SDK transport polymorphism 2026-05-02 12:36:03 -04:00
Cargo.toml chore: remove hard version pinning from hero_* dependencies 2026-06-06 08:36:25 +02:00
LICENSE feat(workspace): snapshot hero_collab → hero_assistance, rename, baseline build green 2026-04-28 15:45:55 -04:00
README.md docs(e2e_checklist + README): post-D-26 rewrite (#18) (#24) 2026-05-23 17:34:34 +00:00

hero_assistance

A peer-to-peer support / ticketing system for Hero-stack products. No central server, no SaaS, no firewall holes; just product teams running their own per-product ticket server, and customers connecting directly over an encrypted overlay network.

Master tracking issue: https://forge.ourworld.tf/lhumina_code/hero_assistance/issues/1 Alignment meta-issue (open): https://forge.ourworld.tf/lhumina_code/hero_assistance/issues/15


What it is, plainly

Three roles, three experiences

Customer (end user with a problem)

Opens the Support UI in a browser. Enters the product's mycelium address and their email. Gets a magic link in their inbox, clicks it, and their device is bound to their account. From then on:

  • Files tickets with title + body + pasted screenshots straight from the clipboard.
  • Sees the support team's responses arrive live over SSE (no polling, no refresh).
  • Sees "the support agent is looking at your ticket right now" presence.
  • If they use multiple Hero products, adds both projects in the UI and sees both inboxes consolidated.

Support agent (product team member triaging tickets)

Same UI, different config. Subscribes to all the projects their team owns at once. Sees:

  • A consolidated cross-project ticket inbox.
  • Per-ticket detail view with threaded comments.
  • Real-time presence ("the customer is reading my reply right now").
  • Image-rich tickets render inline.

Product team integrator (developer embedding hero_assistance into their own product)

Mounts the customer-facing hero_assistance_ui daemon behind their reverse proxy or in an iframe inside their Hero admin dashboard. The web_embed postMessage protocols (hero:theme + hero:route) sync theme and breadcrumbs into the host's outer chrome. The customer sees a Support tab inside the host app, scoped to that host's project, branded as part of the host.

Architecture summary

Topology: many-to-many. A customer's browser subscribes to multiple project servers (one server per supported product / customer engagement). A server hosts multiple users (customers + support agents). All inter-host transport runs on mycelium IPv6: addresses are device-authenticated by the kernel (D-04, D-05), users authenticate over mycelium with email + magic-link (D-06), and every server binds its TCP listener to a 400::/7 overlay address (D-08).

The UI is server-rendered (Axum + Askama + Unpoly + EventSource SSE) per D-26 (retire-Dioxus-deviation; the in-repo decisions/ directory was scrubbed in s2-021, so D-NN records now live in the workspace's shared decisions/ under hero_work, or as Forge-issue ADR comments). The customer SSE channel inherits per-recipient privacy from the same per-user broadcast::Sender the WebSocket uses, per D-27 (per-ticket SSE wire shape and D-20 inheritance; same survival path as D-26).

What's invisible to users

  • Mycelium IPv6 for all transport: encrypted by default, no TLS, no certificates, no firewall punch-through.
  • One server per product (hero_assistance_server); SQLite per server.
  • Many-to-many: a client subscribes to N projects, each project hosts N users. Cross-project isolation enforced (per D-04).
  • Auth = email + magic-link + device-binding. Three-layer identity (project / device / user); the customer never has to think about mycelium addresses.

How the UI is embedded

Anything that can render <iframe src="..."> can embed hero_assistance: hero_router, an Askama-rendered Hero admin dashboard, a non-Hero web app, a hand-rolled HTML page. Integration quality scales with how much of the web_embed skill the host implements, and degrades gracefully:

Host implements Result
Both hero:theme + hero:route Full integration: theme syncs, host's outer chrome shows breadcrumbs, deep-link URLs work
Only hero:theme Theme syncs, no host chrome integration, UI still fully functional
Only iframe (no postMessage) UI renders with default theme, works internally

The handshake is implemented in crates/hero_assistance_ui/templates/base.html as vanilla-JS receivers per the web_embed skill. X-Forwarded-Prefix base-path rewrite supports any reverse-proxy mount point.

Where hero_assistance fits in the Hero ecosystem

Hero piece Role w.r.t. hero_assistance
hero_router Single TCP entry point. Routes the web / app / admin socket types per hero_sockets to the right binary; can iframe-embed the customer UI.
hero_proc Lifecycle supervisor. Registers _server + _ui + _admin via lab service hero_assistance --install.
hero_proc ADMIN_SECRETS Backs the operator IP whitelist that gates _admin's TCP arm per hero_ui_whitelists.
mycelium All inter-host server transport. _server binds its TCP listener to a 400::/7 overlay address (D-08); device addresses are kernel-authenticated.
hero_admin_lib Provides the operator-admin shell (Bootstrap + Unpoly + <hero-*> web components) used by _admin.
web_embed postMessage protocols The wire contract that lets any web host drive theme + breadcrumbs + back/forward; implemented by _ui and _admin base.html templates.
hero_proxy / claims Out-of-scope for v1. hero_assistance is an isolated peer-to-peer service that does not consume the central proxy's claim grammar. May change post-v1 if a customer needs it.

What's in the workspace

crates/
├── hero_assistance/                   CLI / lifecycle owner. Started via
│                                       `lab service hero_assistance --install --start`.
├── hero_assistance_server/            JSON-RPC over rpc.sock + events.sock + a
│                                       TCP listener on a 400::/7 mycelium overlay
│                                       address; SQLite-backed
├── hero_assistance_ui/                Customer-facing Axum binary; binds app.sock
│                                       per hero_sockets §3.5; server-rendered
│                                       Askama + Unpoly + per-ticket SSE live tail
│                                       (D-27). Hosts the /rpc byte-passthrough
│                                       proxy (D-04) and the /tickets/{id}/_messages
│                                       SSE channel.
├── hero_assistance_admin/             Operator admin Axum binary; binds admin.sock
│                                       per §3.2 + an optional TCP arm gated by
│                                       an ADMIN_SECRETS IP whitelist; server-rendered
│                                       Askama + hero_admin_lib middleware/assets +
│                                       Bootstrap + Unpoly + `<hero-*>` web
│                                       components (8 tabs: Tickets / Users /
│                                       Projects / Comments + Logs / Stats /
│                                       Admin / Docs). Hosts the byte-passthrough
│                                       /openrpc.json + /rpc proxies (D-04, D-21).
├── hero_assistance_sdk/               Auto-generated Rust client via
│                                       openrpc_client! + MultiProjectClient
│                                       aggregator (D-11) + RpcTransport trait
│                                       for transport-polymorphism (D-16)
└── hero_assistance_examples/          Runnable examples

Locked architecture decisions: D-01 through D-27 (minus D-14, held-bias and never filed). D-26 retires D-09 + D-17 + D-22 + D-25 atomically (the Dioxus-era SPA and config-persistence-layer decisions); D-27 locks the per-ticket SSE wire shape and how it inherits the D-20 per-recipient privacy invariant. The in-repo decisions/D-*.md directory was scrubbed in s2-021 (Phase Z AI-pipeline cleanup); the canonical record now lives in the workspace's shared decisions/ directory (under the hero_work checkout that drives this repo) or as Forge-issue ADR comments. A backup of the D-01..D-27 content from before the scrub is preserved at /tmp/scrub-backup/hero_assistance-prefilter.git (local-only mirror) + archive/hero_assistance-pre-scrub-2026-05-23/ (local-only copy) for reconstruction if needed.

Intentional gaps: L-01, L-02, L-05, L-07 remain open; L-03, L-04, L-06, L-08, L-09 closed. The in-repo limitations/L-*.md directory was also scrubbed in s2-021; limitation framing for the open set is preserved inline in docs/dev/e2e_checklist.md Section K rows K-16, K-17, K-19 (L-02, L-05, L-07 respectively; L-01 is the cross-language test-substrate parity question carried in this repo's earlier audit logs). The full executable spec lives in docs/dev/e2e_checklist.md; see docs/vision/HERO_ASSISTANCE.md for the stakeholder-facing vision.


Build + test + run

Native Cargo workspace; no WASM target after D-26.

cargo build --release --workspace
cargo test --release --workspace --no-fail-fast
cargo fmt --check
cargo clippy --release --workspace --all-targets -- -D warnings

Service lifecycle goes through lab service:

lab service hero_assistance --install      # build all 4 binaries + register with hero_proc
lab service hero_assistance --start        # start _server + _ui + _admin under hero_proc
lab service hero_assistance --stop
lab service hero_assistance --status
lab infocheck                              # canonical-shape gate (expect 4 ok / 0 findings)

CI auto-publish via .forgejo/workflows/lab-publish.yaml on every push to development: builds linux-musl-x86_64, refreshes releases/tag/latest. A deploy host re-pulls with lab build hero_assistance --download --install.


Deployment posture

Mycelium binding

hero_assistance_server accepts --mycelium-bind=<spec>:

Spec Behavior
auto:<port> Broad-binds [::]:<port> with IPV6_V6ONLY=1. The dispatcher activates enforce_overlay_peer and drops any peer_addr outside 400::/7 to anonymous (D-08; kernel source-address validation prevents non-mycelium local processes from forging an overlay source).
[<ipv6>]:<port> Literal bind to a specific overlay address (use this when you've reserved the host bits and want strict locality). The 400::/7 filter is intentionally bypassed in literal mode; the operator's bind choice IS the constraint.

Set HERO_ASSISTANCE_MYCELIUM_BIND in the environment instead if you'd rather not pass it on the CLI; the flag wins when both are set.

Auth modes

Mode Behavior
proxy (production) Server reads identity from peer_addr (mycelium-authenticated) plus a users row located via the magic-link flow (auth.request_magic_linkauth.consume_magic_link). Per-RPC caller_id is gated against the binding.
dev Permission checks ALL pass; user existence checks where present (presence.set_status_text, comment.create per Phase 14) still fire. Loud startup banner. Never use in production.

PRODUCTION = --auth-mode=proxy ALWAYS. Dev mode bypasses every permission check and was designed for hermetic tests + local walkthroughs. A server accidentally launched in dev mode against real customer data will accept any caller as authorised. The runbook (docs/runbook.md) covers the hero_proc action that pins the flag for production deploys.

Operator runbook

Operator docs live in docs/runbook.md:

  • Live cross-host smoke procedure (L-07: sudo setcap cap_net_admin+ep $(which mycelium); cargo test -- --ignored phase12b).
  • Auth-mode pinning under hero_proc.
  • The open limitations (L-01, L-02, L-05, L-07) and their post-v1 plans.

D-10 conventions (RPC param-name shape)

The wire surface uses two layered vocabularies:

  • Stable user-facing nouns: ticket, comment, presence. The ticket.* and comment.* methods are the canonical surface; new D-10 methods (e.g. presence.set_viewing_ticket) use these nouns for both the method name AND the params they accept.
  • Inherited storage names: channel, message. The collab snapshot's tables, handler module symbols, and CSS classes still carry these names.

ticket.* / comment.* methods are dispatcher-level aliases of the underlying channel.* / message.* handlers (D-10 §"additive aliasing only"). Most aliased methods accept the storage-side param name on the wire (channel_id, message_id); only the new D-12-era methods use the user-facing names directly. Keep this in mind when reading the OpenRPC spec at crates/hero_assistance_server/openrpc.json:

Method Wire param Why
ticket.get / ticket.update / ticket.archive / ticket.delete id Bare alias; underlying channel.* shape preserved.
ticket.member.list / ticket.member.add / ticket.member.remove channel_id Same reason.
comment.create / comment.update / comment.delete channel_id (the parent ticket) message.send shape preserved.
presence.set_viewing_ticket ticket_id New D-12 method, not an alias. User-facing name throughout.

Testing

Test posture: cargo test --release --workspace --no-fail-fast reports 254 pass / 1 fail / 14 ignored. The single failure (phase24b_ui_add_access_fails_when_hero_proc_unreachable) is a documented pre-existing host-env flake that fires when hero_proc_server is reachable on the host running the test; it does not block CI on clean runners.

Workspace pre-merge gate:

cargo fmt --check
cargo clippy --release --workspace --all-targets -- -D warnings
cargo build --workspace --release
cargo test --release --workspace --no-fail-fast

lab infocheck reports 4 crate(s) clean, 0 crate(s) with issues, 0 finding(s) total.


Branching + commits

Hero conventions: development is the default branch; feature work goes on development_<name> (underscores, not slashes); merges back via squash-merge. Conventional-commit messages with an absolute issue URL. No AI co-author trailers.


License

See Cargo.toml.