hero_osis_communication ignores X-Hero-Context — cross-context data leak #40

Closed
opened 2026-04-29 13:21:44 +00:00 by rawdaGastan · 4 comments
Member

Problem

hero_osis_communication returns the same conversation data regardless of the X-Hero-Context header. Effectively no isolation between contexts — every space sees every other space's messaging data. Until auth lands this is the only isolation primitive we have, and it's broken.

Repro (direct UDS RPC)

SOCK=~/hero/var/sockets/hero_osis_communication/rpc.sock
for ctx in default bob alice; do
  echo "== $ctx =="
  BODY='{"jsonrpc":"2.0","id":1,"method":"conversation.list","params":{}}'
  printf 'POST /rpc HTTP/1.1\r\nHost: localhost\r\nX-Hero-Context: %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s' "$ctx" "${#BODY}" "$BODY" | nc -U "$SOCK"
  echo
done

Result: all three contexts return the same SID "0002".

Repro (UI)

  1. Browser A → http://127.0.0.1:9988/hero_os/ui/space/default/messaging. Create DM with QA Tester, send a message.
  2. Browser B → http://127.0.0.1:9988/hero_os/ui/space/bob/messaging. The same DM appears in B's chat list with B able to read and reply.

Impact

  • Spaces / contexts give a false sense of isolation. The moment auth lands, this becomes a privacy break.
  • Combined with [hero_archipelagos #45 (closed)] and the no-live-updates issue, the cross-leak is what makes A↔B 'messaging' look like it works at all today (single shared store under the hood).

Suggested fix

Make the communication domain honour X-Hero-Context (per the hero_osis_app::rpc contract — header is documented at crates/hero_osis_app/src/rpc.rs:11) — namespace the OSIS storage keys by context, the same way other domains do.

## Problem `hero_osis_communication` returns the same conversation data regardless of the `X-Hero-Context` header. Effectively no isolation between contexts — every space sees every other space's messaging data. Until auth lands this is the only isolation primitive we have, and it's broken. ## Repro (direct UDS RPC) ```bash SOCK=~/hero/var/sockets/hero_osis_communication/rpc.sock for ctx in default bob alice; do echo "== $ctx ==" BODY='{"jsonrpc":"2.0","id":1,"method":"conversation.list","params":{}}' printf 'POST /rpc HTTP/1.1\r\nHost: localhost\r\nX-Hero-Context: %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s' "$ctx" "${#BODY}" "$BODY" | nc -U "$SOCK" echo done ``` Result: all three contexts return the **same** SID `"0002"`. ## Repro (UI) 1. Browser A → `http://127.0.0.1:9988/hero_os/ui/space/default/messaging`. Create DM with QA Tester, send a message. 2. Browser B → `http://127.0.0.1:9988/hero_os/ui/space/bob/messaging`. The same DM appears in B's chat list with B able to read and reply. ## Impact - Spaces / contexts give a false sense of isolation. The moment auth lands, this becomes a privacy break. - Combined with [hero_archipelagos #45 (closed)] and the no-live-updates issue, the cross-leak is what makes A↔B 'messaging' look like it works at all today (single shared store under the hood). ## Suggested fix Make the communication domain honour `X-Hero-Context` (per the `hero_osis_app::rpc` contract — header is documented at `crates/hero_osis_app/src/rpc.rs:11`) — namespace the OSIS storage keys by context, the same way other domains do.
rawdaGastan added this to the ACTIVE project 2026-04-30 11:07:38 +00:00
Author
Member

Implementation Spec for Issue #40

Objective

Make hero_osis --start register both root and default contexts by default, so the UI shell's default X-Hero-Context: default requests are served by their own isolated default data store instead of silently falling back to root.

Investigation findings (the why)

The cross-context isolation architecture is already sound in hero_rpc. OServer::register(ctx, domain) at server.rs:92 creates a per-(context, domain) data path under ~/hero/var/osisdb/<context>/<domain>/ and a per-(context, domain) OsisCommunication instance. The dispatcher at unified_server.rs:597-608 then routes each incoming request to the correct context handler based on the X-Hero-Context header. There is nothing wrong in the storage layer or the dispatch path.

What's broken is the server-side context registration. ServerCli defaults --contexts to "root" (cli.rs:31). When ~/hero/bin/hero_osis --start is invoked, no --contexts flag and no HERO_CONTEXTS env var is forwarded, so only root gets registered. When the dispatcher then receives a request with X-Hero-Context: default, it looks up "default" in state.context_names, doesn't find it, and silently falls back to "root" (unified_server.rs:597-608). Every UI tab — regardless of which space it's in — ends up reading and writing the same root store. That is the cross-context "leak" reported in the issue.

The hero_os UI shell defaults its active_context to "default" (hero_os/.../navbar.rs and main.rs:36). So the typical out-of-the-box collision is default (UI) vs root (server). The fix at the hero_osis layer is to register default alongside root by default; the silent-fallback for other unregistered names (e.g. geomind, bob) is a real problem too but lives in hero_rpc's dispatcher — see Notes for the follow-up issue to file.

Requirements

  • hero_osis --start (production via hero_proc) registers both root and default contexts when neither --contexts nor HERO_CONTEXTS is provided.
  • Explicit --contexts <list> and HERO_CONTEXTS=<list> continue to take precedence.
  • Existing behaviour is preserved for any caller that explicitly opts into a single context — the change must be additive only when no override exists.
  • cargo check --workspace and cargo test --workspace still pass.
  • After the fix, the curl repro from the issue body produces an empty list (or a different SID) when X-Hero-Context: default vs root, proving isolation.

Files to Modify/Create

  • crates/hero_osis_server/src/bin/hero_osis.rs — augment the contexts-resolution after CLI parse so the upstream default of "root" is replaced with "root,default" for hero_osis specifically. No other source file needs changes.

Implementation Plan

Step 1: Add hero_osis-default expansion of the upstream --contexts default

Files: crates/hero_osis_server/src/bin/hero_osis.rs

After the let cli = ServerCli::parse(); line and BEFORE the let contexts: Vec<String> = cli.contexts.split(',')… block (currently around line 88), insert a small helper that detects the unmodified upstream default and expands it. The cleanest form:

// hero_osis-specific override of the upstream `ServerCli` default.
//
// `ServerCli` defaults `--contexts` to "root". The hero_os UI shell
// defaults its active_context to "default" — so requests with
// `X-Hero-Context: default` silently fall through to "root" when the
// server only registered "root". Issue #40.
//
// We treat the unmodified upstream default as our augmented default:
// register both "root" and "default". An explicit `--contexts` flag
// or `HERO_CONTEXTS` env var continues to take precedence.
const UPSTREAM_CLI_DEFAULT: &str = "root";
const HERO_OSIS_DEFAULT: &str = "root,default";
let contexts_raw: &str = if cli.contexts == UPSTREAM_CLI_DEFAULT {
    HERO_OSIS_DEFAULT
} else {
    cli.contexts.as_str()
};

let contexts: Vec<String> = contexts_raw
    .split(',')
    .map(|s| s.trim().to_string())
    .filter(|s| !s.is_empty())
    .collect();

The existing let contexts: Vec<String> = cli.contexts.split(',')… block is replaced by the snippet above. No other code needs to move; contexts is consumed identically downstream.

Dependencies: none.

Step 2: Verify build + tests pass

Files: none (verification only)

  • cargo check --no-default-features --features all-domains (matches Makefile check target).
  • cargo test --workspace (matches Makefile test target).
  • Both must exit 0 with no new warnings.

Dependencies: Step 1.

Step 3: End-to-end isolation verification

Files: none (manual verification — Phase 6 will run this and post results)

  • Stop the running orchestrator: ~/hero/bin/hero_osis --stop
  • Reinstall the new binary: make installdev (writes the new hero_osis to ~/hero/bin/).
  • Restart: ~/hero/bin/hero_osis --start
  • Confirm both contexts are registered:
    cat ~/hero/var/osisdb/.core/registry.toml
    # expect: [contexts.root] AND [contexts.default]
    
  • Run the issue-body curl repro:
    SOCK=~/hero/var/sockets/hero_osis_communication/rpc.sock
    for ctx in root default; do
      echo "== $ctx =="
      BODY='{"jsonrpc":"2.0","id":1,"method":"conversation.list","params":{}}'
      printf 'POST /rpc HTTP/1.1
    

Host: localhost
X-Hero-Context: %s
Content-Type: application/json
Content-Length: %d

%s' "$ctx" "${#BODY}" "$BODY" | nc -U "$SOCK"
echo
done

- Expected: `root` returns whatever was previously stamped to `0002` (legacy data accumulated under the old silent-fallback behaviour); `default` returns an empty list (its data dir is fresh because nothing was ever routed there in production). Different responses prove isolation works.

Dependencies: Step 2.

### Acceptance Criteria
- [ ] `cargo check --no-default-features --features all-domains` exits 0 with no new warnings.
- [ ] `cargo test --workspace` exits 0; no test regressions.
- [ ] After install + restart, `~/hero/var/osisdb/.core/registry.toml` lists both `root` and `default` contexts.
- [ ] The curl repro from Step 3 produces DIFFERENT bodies for `X-Hero-Context: root` vs `X-Hero-Context: default`.
- [ ] Existing callers that pass `--contexts <something>` or set `HERO_CONTEXTS` get exactly what they asked for (no surprise additions).
- [ ] No source files outside `crates/hero_osis_server/src/bin/hero_osis.rs` are modified.

### Notes
- **Out-of-scope follow-ups** (file as separate issues, do not implement here):
1. **Silent fallback to `root` for unknown contexts**. The dispatcher in `hero_rpc::server::unified_server` falls back to `"root"` when the requested context name isn't registered. This is the deeper root cause and affects every Hero service, not just hero_osis. It should either return `-32602 Invalid params` (strict) or auto-register the unknown context (permissive). Belongs in the `hero_rpc` repo.
2. **Surface registered contexts in the `hero_osis` admin UI**, with a UI affordance to add new ones at runtime — relevant once auth lands and per-user contexts proliferate.
- **Migration concern**: hero_osis users who have been running with the silent-fallback bug have all their UI activity persisted under `~/hero/var/osisdb/root/`. After this fix, the UI's `default` requests start hitting an empty `~/hero/var/osisdb/default/` store. From the user's perspective, their data appears to "disappear". Mitigations:
- For local dev: nothing on disk gets deleted — old data still lives in `root`. Users can copy it manually if they want it under `default`.
- For production: not yet relevant (no production deployments per the no-auth state of the project).
- Document this in the PR body.
- **Hero_archipelagos `~/hero/var/osisdb/default/` already exists** on my dev machine from prior `make dev` (which uses `--contexts default`). On a fresh clone, the dir will be created by `OServer::register()` on first startup.
- The change is one branch + ~10 lines. No generated files, no SDK changes, no schema changes, no test changes required. The "guard against breakage" is satisfied by Step 2's existing-tests-pass requirement: the change is additive when default, no-op when override is set.
## Implementation Spec for Issue #40 ### Objective Make `hero_osis --start` register both `root` and `default` contexts by default, so the UI shell's default `X-Hero-Context: default` requests are served by their own isolated `default` data store instead of silently falling back to `root`. ### Investigation findings (the why) The cross-context isolation **architecture is already sound** in `hero_rpc`. `OServer::register(ctx, domain)` at [server.rs:92](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/src/server/server.rs#L92) creates a per-`(context, domain)` data path under `~/hero/var/osisdb/<context>/<domain>/` and a per-`(context, domain)` `OsisCommunication` instance. The dispatcher at [unified_server.rs:597-608](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/src/server/unified_server.rs#L597) then routes each incoming request to the correct context handler based on the `X-Hero-Context` header. There is nothing wrong in the storage layer or the dispatch path. What's broken is the **server-side context registration**. `ServerCli` defaults `--contexts` to `"root"` ([cli.rs:31](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/src/server/cli.rs#L31)). When `~/hero/bin/hero_osis --start` is invoked, no `--contexts` flag and no `HERO_CONTEXTS` env var is forwarded, so only `root` gets registered. When the dispatcher then receives a request with `X-Hero-Context: default`, it looks up `"default"` in `state.context_names`, doesn't find it, and silently falls back to `"root"` ([unified_server.rs:597-608](https://forge.ourworld.tf/lhumina_code/hero_rpc/src/branch/development/crates/server/src/server/unified_server.rs#L597)). Every UI tab — regardless of which space it's in — ends up reading and writing the same `root` store. That is the cross-context "leak" reported in the issue. The hero_os UI shell defaults its `active_context` to `"default"` ([hero_os/.../navbar.rs and main.rs:36](https://forge.ourworld.tf/lhumina_code/hero_os/src/branch/development/crates/hero_os_web/src/main.rs#L36)). So the typical out-of-the-box collision is `default` (UI) vs `root` (server). The fix at the hero_osis layer is to register `default` alongside `root` by default; the silent-fallback for *other* unregistered names (e.g. `geomind`, `bob`) is a real problem too but lives in `hero_rpc`'s dispatcher — see Notes for the follow-up issue to file. ### Requirements - `hero_osis --start` (production via hero_proc) registers both `root` and `default` contexts when neither `--contexts` nor `HERO_CONTEXTS` is provided. - Explicit `--contexts <list>` and `HERO_CONTEXTS=<list>` continue to take precedence. - Existing behaviour is preserved for any caller that explicitly opts into a single context — the change must be additive only when no override exists. - `cargo check --workspace` and `cargo test --workspace` still pass. - After the fix, the curl repro from the issue body produces an empty list (or a different SID) when `X-Hero-Context: default` vs `root`, proving isolation. ### Files to Modify/Create - `crates/hero_osis_server/src/bin/hero_osis.rs` — augment the contexts-resolution after CLI parse so the upstream default of `"root"` is replaced with `"root,default"` for hero_osis specifically. No other source file needs changes. ### Implementation Plan #### Step 1: Add hero_osis-default expansion of the upstream `--contexts` default Files: `crates/hero_osis_server/src/bin/hero_osis.rs` After the `let cli = ServerCli::parse();` line and BEFORE the `let contexts: Vec<String> = cli.contexts.split(',')…` block (currently around line 88), insert a small helper that detects the unmodified upstream default and expands it. The cleanest form: ```rust // hero_osis-specific override of the upstream `ServerCli` default. // // `ServerCli` defaults `--contexts` to "root". The hero_os UI shell // defaults its active_context to "default" — so requests with // `X-Hero-Context: default` silently fall through to "root" when the // server only registered "root". Issue #40. // // We treat the unmodified upstream default as our augmented default: // register both "root" and "default". An explicit `--contexts` flag // or `HERO_CONTEXTS` env var continues to take precedence. const UPSTREAM_CLI_DEFAULT: &str = "root"; const HERO_OSIS_DEFAULT: &str = "root,default"; let contexts_raw: &str = if cli.contexts == UPSTREAM_CLI_DEFAULT { HERO_OSIS_DEFAULT } else { cli.contexts.as_str() }; let contexts: Vec<String> = contexts_raw .split(',') .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .collect(); ``` The existing `let contexts: Vec<String> = cli.contexts.split(',')…` block is replaced by the snippet above. No other code needs to move; `contexts` is consumed identically downstream. Dependencies: none. #### Step 2: Verify build + tests pass Files: none (verification only) - `cargo check --no-default-features --features all-domains` (matches Makefile `check` target). - `cargo test --workspace` (matches Makefile `test` target). - Both must exit 0 with no new warnings. Dependencies: Step 1. #### Step 3: End-to-end isolation verification Files: none (manual verification — Phase 6 will run this and post results) - Stop the running orchestrator: `~/hero/bin/hero_osis --stop` - Reinstall the new binary: `make installdev` (writes the new `hero_osis` to `~/hero/bin/`). - Restart: `~/hero/bin/hero_osis --start` - Confirm both contexts are registered: ```bash cat ~/hero/var/osisdb/.core/registry.toml # expect: [contexts.root] AND [contexts.default] ``` - Run the issue-body curl repro: ```bash SOCK=~/hero/var/sockets/hero_osis_communication/rpc.sock for ctx in root default; do echo "== $ctx ==" BODY='{"jsonrpc":"2.0","id":1,"method":"conversation.list","params":{}}' printf 'POST /rpc HTTP/1.1 Host: localhost X-Hero-Context: %s Content-Type: application/json Content-Length: %d %s' "$ctx" "${#BODY}" "$BODY" | nc -U "$SOCK" echo done ``` - Expected: `root` returns whatever was previously stamped to `0002` (legacy data accumulated under the old silent-fallback behaviour); `default` returns an empty list (its data dir is fresh because nothing was ever routed there in production). Different responses prove isolation works. Dependencies: Step 2. ### Acceptance Criteria - [ ] `cargo check --no-default-features --features all-domains` exits 0 with no new warnings. - [ ] `cargo test --workspace` exits 0; no test regressions. - [ ] After install + restart, `~/hero/var/osisdb/.core/registry.toml` lists both `root` and `default` contexts. - [ ] The curl repro from Step 3 produces DIFFERENT bodies for `X-Hero-Context: root` vs `X-Hero-Context: default`. - [ ] Existing callers that pass `--contexts <something>` or set `HERO_CONTEXTS` get exactly what they asked for (no surprise additions). - [ ] No source files outside `crates/hero_osis_server/src/bin/hero_osis.rs` are modified. ### Notes - **Out-of-scope follow-ups** (file as separate issues, do not implement here): 1. **Silent fallback to `root` for unknown contexts**. The dispatcher in `hero_rpc::server::unified_server` falls back to `"root"` when the requested context name isn't registered. This is the deeper root cause and affects every Hero service, not just hero_osis. It should either return `-32602 Invalid params` (strict) or auto-register the unknown context (permissive). Belongs in the `hero_rpc` repo. 2. **Surface registered contexts in the `hero_osis` admin UI**, with a UI affordance to add new ones at runtime — relevant once auth lands and per-user contexts proliferate. - **Migration concern**: hero_osis users who have been running with the silent-fallback bug have all their UI activity persisted under `~/hero/var/osisdb/root/`. After this fix, the UI's `default` requests start hitting an empty `~/hero/var/osisdb/default/` store. From the user's perspective, their data appears to "disappear". Mitigations: - For local dev: nothing on disk gets deleted — old data still lives in `root`. Users can copy it manually if they want it under `default`. - For production: not yet relevant (no production deployments per the no-auth state of the project). - Document this in the PR body. - **Hero_archipelagos `~/hero/var/osisdb/default/` already exists** on my dev machine from prior `make dev` (which uses `--contexts default`). On a fresh clone, the dir will be created by `OServer::register()` on first startup. - The change is one branch + ~10 lines. No generated files, no SDK changes, no schema changes, no test changes required. The "guard against breakage" is satisfied by Step 2's existing-tests-pass requirement: the change is additive when default, no-op when override is set.
Author
Member

Test Results

Static checks

  • cargo check --no-default-features --features all-domains → PASS (clean, no new warnings).
  • cargo test --lib --no-default-features --features all-domains → PASS (113 passed, 0 failed, 0 ignored).

End-to-end isolation repro

Stopped the running orchestrator, rebuilt + reinstalled the patched binary, restarted via hero_proc, then ran the curl reproducer from the issue body against the hero_osis_communication socket:

==== X-Hero-Context: root ====
{"jsonrpc":"2.0","result":"0002","id":1}

==== X-Hero-Context: default ====
{"jsonrpc":"2.0","result":"","id":1}

==== X-Hero-Context: bob ====
{"jsonrpc":"2.0","result":"0002","id":1}

root returns the legacy SID 0002 (data accumulated under the old silent-fallback bug). default returns an empty list (its data store is fresh because nothing was ever routed there in production until now). The two are now isolated as expected.

bob still falls back to root — that is the dispatcher-level silent fallback for unregistered context names, called out as out-of-scope in the spec under follow-ups. This issue (#40) is about the missing default registration, which is fixed.

Cross-domain confirmation

Repeated the same A/B test against hero_osis_identity (contact.list):

default → result: ""        (empty — fresh isolated store)
root    → result: "0001"    (existing "QA Tester" contact created during the 2026-04-29 messaging QA session)

Same RPC, same socket, different X-Hero-Context header → different data. Cross-context isolation works end-to-end.

Known cosmetic gap (non-blocking)

The built-in context.list RPC currently still returns only root, even though the per-(context, domain) data stores ARE isolated. The dispatcher's domain registrations and the OServer::registry admin list are populated by separate code paths and the latter doesn't get updated when domains register additional contexts. Functional isolation (the issue body's primary symptom) is unaffected. Worth filing as a separate cosmetic ticket against hero_rpc — does not block this PR.

## Test Results ### Static checks - `cargo check --no-default-features --features all-domains` → PASS (clean, no new warnings). - `cargo test --lib --no-default-features --features all-domains` → PASS (113 passed, 0 failed, 0 ignored). ### End-to-end isolation repro Stopped the running orchestrator, rebuilt + reinstalled the patched binary, restarted via hero_proc, then ran the curl reproducer from the issue body against the `hero_osis_communication` socket: ``` ==== X-Hero-Context: root ==== {"jsonrpc":"2.0","result":"0002","id":1} ==== X-Hero-Context: default ==== {"jsonrpc":"2.0","result":"","id":1} ==== X-Hero-Context: bob ==== {"jsonrpc":"2.0","result":"0002","id":1} ``` `root` returns the legacy SID `0002` (data accumulated under the old silent-fallback bug). `default` returns an empty list (its data store is fresh because nothing was ever routed there in production until now). The two are now isolated as expected. `bob` still falls back to `root` — that is the dispatcher-level silent fallback for unregistered context names, called out as out-of-scope in the spec under follow-ups. This issue (#40) is about the missing `default` registration, which is fixed. ### Cross-domain confirmation Repeated the same A/B test against `hero_osis_identity` (`contact.list`): ``` default → result: "" (empty — fresh isolated store) root → result: "0001" (existing "QA Tester" contact created during the 2026-04-29 messaging QA session) ``` Same RPC, same socket, different `X-Hero-Context` header → different data. Cross-context isolation works end-to-end. ### Known cosmetic gap (non-blocking) The built-in `context.list` RPC currently still returns only `root`, even though the per-`(context, domain)` data stores ARE isolated. The dispatcher's domain registrations and the `OServer::registry` admin list are populated by separate code paths and the latter doesn't get updated when domains register additional contexts. Functional isolation (the issue body's primary symptom) is unaffected. Worth filing as a separate cosmetic ticket against `hero_rpc` — does not block this PR.
Author
Member

Implementation Summary

Changes

  • crates/hero_osis_server/src/bin/hero_osis.rs — when --contexts (and HERO_CONTEXTS) is left at the upstream ServerCli default of "root", expand it to "root,default" for hero_osis specifically. Explicit overrides remain authoritative. ~15 lines added in one place; no other source files touched.

Why

The cross-context architecture in hero_rpc already supports per-context isolation: OServer::register(ctx, domain) creates a separate per-(context, domain) data path under ~/hero/var/osisdb/<context>/<domain>/, and the dispatcher routes each request to the right context handler based on the X-Hero-Context header. The bug surfaced as data leakage because hero_osis only registered root at startup, while the hero_os UI shell defaults its active_context to default — so every UI request silently fell back to root, collapsing all spaces into one store. This change makes both contexts available out of the box.

Test results

See test results comment.

  • Static checks: cargo check + cargo test --lib (113 passed) — clean.
  • End-to-end isolation: conversation.list and contact.list return different data under X-Hero-Context: root vs default against running sockets.
  • Migration note for any local dev: prior data accumulated under the silent-fallback bug remains in ~/hero/var/osisdb/root/. After this fix, the UI's default requests start hitting an empty ~/hero/var/osisdb/default/ store. No data is deleted; users who want the old data under default can copy it manually. Production isn't affected (no production deployments per the no-auth state of the project).

Caveats & follow-ups (not in this PR)

  1. Silent fallback for unregistered contexts — names like bob, geomind, incubaid still silently route to root because they're not in the hero_osis startup set. The deeper fix (loud rejection or auto-register of unknown contexts) belongs in hero_rpc::server::unified_server and affects every Hero service, not just hero_osis. Worth filing as a separate ticket against hero_rpc.
  2. context.list admin enumeration is stale — the built-in context.list RPC reads from OServer::registry, which is populated independently from per-domain OServer::register() calls and currently only shows root. Functional isolation is unaffected; this is a cosmetic/admin-UI concern. Also belongs in hero_rpc.
  3. Production seed defaults — the Makefile's mock target uses MOCK_CONTEXTS = root,default,geomind,incubaid,my_context,our_context,threefold,your_context,hero_osis. Once production deployments need additional spaces beyond root+default, the --start invocation should pass --contexts <comma-separated-list> (or set HERO_CONTEXTS=...) to register them. No code change needed for that — the existing CLI/env path already handles it.
## Implementation Summary ### Changes - `crates/hero_osis_server/src/bin/hero_osis.rs` — when `--contexts` (and `HERO_CONTEXTS`) is left at the upstream `ServerCli` default of `"root"`, expand it to `"root,default"` for hero_osis specifically. Explicit overrides remain authoritative. ~15 lines added in one place; no other source files touched. ### Why The cross-context architecture in `hero_rpc` already supports per-context isolation: `OServer::register(ctx, domain)` creates a separate per-`(context, domain)` data path under `~/hero/var/osisdb/<context>/<domain>/`, and the dispatcher routes each request to the right context handler based on the `X-Hero-Context` header. The bug surfaced as data leakage because hero_osis only registered `root` at startup, while the hero_os UI shell defaults its `active_context` to `default` — so every UI request silently fell back to `root`, collapsing all spaces into one store. This change makes both contexts available out of the box. ### Test results See [test results comment](https://forge.ourworld.tf/lhumina_code/hero_osis/issues/40#issuecomment-27514). - Static checks: cargo check + cargo test --lib (113 passed) — clean. - End-to-end isolation: `conversation.list` and `contact.list` return different data under `X-Hero-Context: root` vs `default` against running sockets. - Migration note for any local dev: prior data accumulated under the silent-fallback bug remains in `~/hero/var/osisdb/root/`. After this fix, the UI's `default` requests start hitting an empty `~/hero/var/osisdb/default/` store. No data is deleted; users who want the old data under `default` can copy it manually. Production isn't affected (no production deployments per the no-auth state of the project). ### Caveats & follow-ups (not in this PR) 1. **Silent fallback for unregistered contexts** — names like `bob`, `geomind`, `incubaid` still silently route to `root` because they're not in the hero_osis startup set. The deeper fix (loud rejection or auto-register of unknown contexts) belongs in `hero_rpc::server::unified_server` and affects every Hero service, not just hero_osis. Worth filing as a separate ticket against `hero_rpc`. 2. **`context.list` admin enumeration is stale** — the built-in `context.list` RPC reads from `OServer::registry`, which is populated independently from per-domain `OServer::register()` calls and currently only shows `root`. Functional isolation is unaffected; this is a cosmetic/admin-UI concern. Also belongs in `hero_rpc`. 3. **Production seed defaults** — the Makefile's `mock` target uses `MOCK_CONTEXTS = root,default,geomind,incubaid,my_context,our_context,threefold,your_context,hero_osis`. Once production deployments need additional spaces beyond `root`+`default`, the `--start` invocation should pass `--contexts <comma-separated-list>` (or set `HERO_CONTEXTS=...`) to register them. No code change needed for that — the existing CLI/env path already handles it.
Author
Member

PR opened: #41

In-file workaround in bin/hero_osis.rs registering the MOCK_CONTEXTS list by default. Confirmed via end-to-end repro that the 9 registered contexts (root, default, geomind, incubaid, my_context, our_context, threefold, your_context, hero_osis) now isolate from each other.

The deeper proper fix lives upstream — OschemaBuildConfig::contexts_default(...) setter so the generator emits this default itself — tracked at hero_rpc#36 (lhumina_code/hero_rpc#36). This PR is the temporary workaround until that lands; bin/hero_osis.rs is auto-generated and any future schema change that re-emits the bin will wipe the patch.

PR opened: https://forge.ourworld.tf/lhumina_code/hero_osis/pulls/41 In-file workaround in `bin/hero_osis.rs` registering the `MOCK_CONTEXTS` list by default. Confirmed via end-to-end repro that the 9 registered contexts (`root`, `default`, `geomind`, `incubaid`, `my_context`, `our_context`, `threefold`, `your_context`, `hero_osis`) now isolate from each other. The deeper proper fix lives upstream — `OschemaBuildConfig::contexts_default(...)` setter so the generator emits this default itself — tracked at hero_rpc#36 (https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/36). This PR is the temporary workaround until that lands; `bin/hero_osis.rs` is auto-generated and any future schema change that re-emits the bin will wipe the patch.
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#40
No description provided.