Skip to content

fix(core): retry domain subscriber registration after bus startup - #5400

Open
samrusani wants to merge 2 commits into
tinyhumansai:mainfrom
samrusani:agent/retry-domain-subscriber-registration
Open

fix(core): retry domain subscriber registration after bus startup#5400
samrusani wants to merge 2 commits into
tinyhumansai:mainfrom
samrusani:agent/retry-domain-subscriber-registration

Conversation

@samrusani

@samrusani samrusani commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Keep a domain's subscriber-registration token unconsumed while the global event bus is unavailable.
  • Preserve existing once-per-process registration after the bus is initialized.
  • Cover deferred registration, successful retry, production global-bus wiring, and post-success idempotency.

Problem

group_first_time inserted a DomainGroup into the process-wide completed set before its registration block ran. If the block ran before the global event bus was initialized, every subscribe_global call returned None, but the group stayed marked complete and could never retry during the process lifetime.

Solution

Gate insertion into the completed set on global event-bus readiness. subscribe_global has only one failure condition: the monotonic global bus OnceLock has not been initialized. Once the bus exists it cannot disappear, so the existing registration blocks can run unchanged and remain idempotent.

The state transition is extracted behind an injectable readiness flag for deterministic failure-path coverage. A separate wrapper test exercises the real global-bus lookup with a domain token that has no subscriber block.

On current main, bootstrap_core_runtime initializes the bus before it registers domain subscribers, which narrows the normal-startup window. start_channels can still reach registration before that path; retaining the readiness gate keeps a premature attempt retryable rather than permanently consuming its token.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage >= 80% - changed lines reached 100% with the repository's scoped Rust coverage lane and diff-cover on the current branch head.
  • Coverage matrix updated - N/A: behavior-only correction to existing core bootstrap registration.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related - N/A: no matrix feature ID applies.
  • No new external network dependencies introduced.
  • Manual smoke checklist updated - N/A: no release-cut or user-facing workflow changed.
  • Linked issue closed via Closes #NNN in the ## Related section.

Impact

Core runtime only. A premature registration attempt now defers cleanly and can succeed on a later bootstrap call. Current bootstrap ordering narrows the normal-startup window, but start_channels can still arrive before it; normal startup, domain widening, and once-per-process behavior are otherwise unchanged. No persistence, migration, API, or user-interface impact.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: agent/retry-domain-subscriber-registration
  • Commit SHA: 8e4eed191ff069843f40fdb938d4a095d03fbb3d

Validation Run

  • pnpm --filter openhuman-app format:check - N/A: no frontend files changed.
  • pnpm typecheck - N/A: no TypeScript files changed.
  • Focused tests: cargo test --lib domain_subscriber -- --nocapture (6 passed); repository scoped coverage lane (97 passed, 1 pre-existing ignored); diff-cover (100%).
  • Rust fmt/check (if changed): cargo fmt --manifest-path Cargo.toml --check; git diff --check; default and slim-feature focused test builds passed.
  • Tauri fmt/check (if changed): N/A: no Tauri files changed.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: bus-unavailable domain registration attempts remain retryable.
  • User-visible effect: none during normal startup; prevents silently missing domain subscribers after premature registration.

Parity Contract

  • Legacy behavior preserved: domain selection, later DomainSet widening, registration order, and once-per-process idempotency after successful bus initialization.
  • Guard/fallback/dispatch parity checks: default-feature and --no-default-features --features tokenjuice-treesitter registrar tests both pass.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None found in the final live search.
  • Canonical PR: This PR.
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • Bug Fixes
    • Improved domain event subscriber registration reliability when the event bus is temporarily unavailable.
    • Registrations now remain retryable until the event bus is ready, preventing missed subscriptions.
    • Successful registrations are safely handled as idempotent, avoiding duplicate setup.
    • Added coverage for deferred retries, repeated registrations, and event-bus readiness behavior.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Domain and learning subscriber registration now checks global event-bus readiness before consuming completion tokens. Unavailable-bus attempts remain retryable. Successful registration remains idempotent. Tests cover both flows and the domain wrapper.

Changes

Domain subscriber registration

Layer / File(s) Summary
Bus-aware registration tracking
src/core/jsonrpc.rs
Registration helpers consume per-process completion tokens only when the global event bus is available. The domain wrapper uses this retryable flow.
Registration validation
src/core/jsonrpc_tests.rs
Tests verify deferred retries, domain and learning idempotency, token preservation, and global event-bus usage.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Registration as register_domain_subscribers
  participant Helpers as registration helpers
  participant Bus as GlobalEventBus
  participant Completion as Per-process completion tracking

  Registration->>Helpers: Attempt subscriber registration
  Helpers->>Bus: Check event-bus readiness
  alt Bus unavailable
    Helpers-->>Registration: Defer without consuming token
  else Bus available
    Helpers->>Completion: Record group completion
    Helpers->>Bus: Register subscribers
  end
Loading

Possibly related PRs

Suggested labels: rust-core

Suggested reviewers: senamakel

Poem

A rabbit checked the event bus light,
And saved the token when it was not right.
When ready, subscribers joined the stream,
Repeated calls stayed clean and lean.
Retry kept the promise bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes defer completion until the event bus is ready, preserve retryability, and retain idempotency as required by issue #5269.
Out of Scope Changes check ✅ Passed The changes remain within core subscriber registration behavior and add focused tests without unrelated system changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: retrying domain subscriber registration after event-bus startup.

Comment @coderabbitai help to get the list of available commands.

@samrusani
samrusani marked this pull request as ready for review August 5, 2026 03:18
@samrusani
samrusani requested a review from a team August 5, 2026 03:18
@coderabbitai coderabbitai Bot added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label Aug 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/jsonrpc.rs`:
- Around line 1944-1972: Split src/core/jsonrpc.rs into focused Rust modules so
it is at most 500 lines, moving the bus-aware registration tracking around
group_first_time_when_bus_ready and group_first_time together with the
domain-subscriber registration logic into an appropriate module. Update imports,
visibility, and call sites to preserve existing retry behavior when the event
bus is unavailable and registration remains one-time after initialization.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cac5fb14-6b11-4a9b-9975-88abd5f27a86

📥 Commits

Reviewing files that changed from the base of the PR and between d75b0a4 and 3e008bc.

📒 Files selected for processing (2)
  • src/core/jsonrpc.rs
  • src/core/jsonrpc_tests.rs

Comment thread src/core/jsonrpc.rs
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a latent bootstrap bug where domain subscriber groups could be permanently skipped if register_domain_subscribers was called before the global event bus was initialized. The fix defers inserting a group into the DONE set until bus_ready is confirmed, making pre-bus calls fully retryable.

  • group_first_time and learning_first_time are refactored into outer wrappers that delegate to injectable _when_bus_ready variants, enabling deterministic unit tests without touching real process-global state.
  • Five new tests cover deferred registration, successful retry, idempotency after success, and the production global-bus wiring path.
  • The fix also applies the same bus-readiness gate to learning_first_time, which previously had the identical silent-skip problem described in the PR's "Known limitation" comment (now removed).

Confidence Score: 5/5

Safe to merge. The change is a targeted, well-tested correction to a bootstrap ordering hazard with no persistence, API, or UI impact.

The fix is narrowly scoped to the completion-set insertion logic in two functions. The new _when_bus_ready helpers are pure and fully covered by the five added tests. The monotonic OnceLock guarantee that underpins the correctness argument is sound: once the bus is initialized it cannot disappear, so a true return from group_first_time_when_bus_ready always corresponds to a usable bus. Normal startup order is unchanged and idempotency after success is explicitly tested.

Files Needing Attention: No files require special attention. The INFRA Once block inside register_domain_subscribers still calls subscribe_global without bus-readiness gating, but that block is not retryable by design and its failure branch already logs a warning — this is pre-existing and out of scope for this PR.

Important Files Changed

Filename Overview
src/core/jsonrpc.rs Refactors group_first_time and learning_first_time to gate completion-set insertion on bus readiness; removes the now-resolved known-limitation comment; logic is correct and well-documented.
src/core/jsonrpc_tests.rs Adds five focused unit tests covering deferred, retry, idempotency, and global-bus wiring paths; tests are clear and directly map to the new behaviour.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[register_domain_subscribers called] --> B{bus_ready?}
    B -- No --> C[log warn: deferred registration\nreturn false]
    C --> D[Group NOT inserted into DONE\nRemains retryable]
    B -- Yes --> E{Group already in DONE?}
    E -- Yes --> F[return false\nIdempotent skip]
    E -- No --> G[Insert group into DONE\nreturn true]
    G --> H[Run subscriber registration block]
    H --> I[SubscriptionHandle forgotten\nLive for process lifetime]
    D --> J[Next bootstrap call retries]
    J --> B
Loading

Reviews (2): Last reviewed commit: "fix(core): keep learning subscriber regi..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e008bc24f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/jsonrpc.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/jsonrpc_tests.rs (1)

184-192: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the production-wrapper test independent of global mutex state.

group_first_time uses process-global group_first_time_when_bus_ready(...completed...) state. This wrapper test asserts group_first_time(DomainGroup::Media) returns true, but any preceding test that enables the Media group can consume the token and make this test fail under parallel execution. Use the isolated group_first_time_when_bus_ready fixture with an explicit bucket, or add mutex reset/exposure in the test helper for this production wrapper path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/jsonrpc_tests.rs` around lines 184 - 192, Update
domain_subscriber_registration_wrapper_uses_the_global_bus to avoid relying on
shared group_first_time_when_bus_ready completed-state: use the isolated fixture
with an explicit bucket, or reset/expose the test mutex state before exercising
group_first_time(DomainGroup::Media). Preserve the assertions that the first
registration succeeds and the second returns false.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/core/jsonrpc_tests.rs`:
- Around line 184-192: Update
domain_subscriber_registration_wrapper_uses_the_global_bus to avoid relying on
shared group_first_time_when_bus_ready completed-state: use the isolated fixture
with an explicit bucket, or reset/expose the test mutex state before exercising
group_first_time(DomainGroup::Media). Preserve the assertions that the first
registration succeeds and the second returns false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 38528ce4-0d45-4ede-abe1-a2a0d5546db0

📥 Commits

Reviewing files that changed from the base of the PR and between 3e008bc and 8e4eed1.

📒 Files selected for processing (2)
  • src/core/jsonrpc.rs
  • src/core/jsonrpc_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/jsonrpc.rs

Copy link
Copy Markdown
Contributor Author

Reviewed the outside-diff suggestion about the DomainGroup::Media wrapper test. A repository-wide call-site check shows group_first_time(DomainGroup::Media) is used only by that test; the production registrar has no Media registration block, so enabling the Media domain cannot consume this token. The isolated helper tests already cover the pure state transitions, while this test intentionally covers the real global-bus wrapper. I am keeping it unchanged. CodeRabbit's follow-up review is approved and all review threads are resolved.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

Maintainer review (merge-readiness sweep) — review only, I have not touched this branch.

The change itself is sound: making the group token conditional on bus readiness is the right shape for #5265, the pure *_when_bus_ready helpers are the right seam to test, and the four unit tests exercise both the deferred and idempotent paths without needing a live bus. CodeRabbit's one thread is resolved and its final review is an approval.

However, it will not compile on current main, and GitHub will not tell you that — git reports the merge as clean because the conflict is semantic, not textual.

What broke

main renamed the bus module: src/core/event_bus/ (a directory module) is gone and the bus now lives at src/core/bus.rs (pub mod bus; in src/core/mod.rs). This PR references the old path in three places, all of which now fail to resolve:

file line current on main
src/core/jsonrpc.rs in group_first_time crate::core::event_bus::global().is_some() no such module
src/core/jsonrpc.rs in learning_first_time crate::core::event_bus::global().is_some() no such module
src/core/jsonrpc_tests.rs in domain_subscriber_registration_wrapper_uses_the_global_bus crate::core::event_bus::init_global(crate::core::event_bus::DEFAULT_CAPACITY) no such module or fn

What it should become

The readiness predicate is now BUS.get(), which is exactly the idiom jsonrpc.rs:1582 and socketio.rs already use (if let Some(bus) = crate::core::bus::BUS.get()):

-        crate::core::event_bus::global().is_some(),
+        crate::core::bus::BUS.get().is_some(),

in both group_first_time and learning_first_time.

The test needs more than a rename: init_global(capacity) was synchronous, whereas crate::core::bus::init() is async and idempotent. The smallest faithful port is to make the test async:

#[tokio::test]
async fn domain_subscriber_registration_wrapper_uses_the_global_bus() {
    use crate::core::all::DomainGroup;

    // The global bus has to be up for the wrapper to consume its token at all;
    // `init` is idempotent, so it does not matter whether an earlier test in
    // this binary already stood it up.
    let _ = crate::core::bus::init().await;
    assert!(group_first_time(DomainGroup::Media));
    assert!(!group_first_time(DomainGroup::Media));
}

let _ = rather than .expect(…) is deliberate — src/core/bus.rs documents that under cargo test the first init wins the OnceLock and later tests inherit a bus attached to a torn-down runtime, so the call can legitimately be a no-op here. The precedent for calling bus::init() from an in-crate test is already there (memory/sync_events_bridge_tests.rs, memory/ops/sync_tests.rs).

I applied exactly those three edits locally on top of a main merge and ran cargo check -p openhuman --testsexit 0. So that is the whole of it; nothing else in the PR has rotted.

One thing worth a second look

bootstrap_core_runtime on current main now awaits crate::core::bus::init() before it calls register_domain_subscribers (jsonrpc.rs:2330), which it may not have when you opened this in early August. That narrows the window your fix targets but does not close it — start_channels can still reach registration on another path, and the retryability you add is what makes a premature call recoverable rather than permanent. Worth a line in the PR description so a reviewer does not mistake it for a fix without a trigger.

To land

Merge or rebase onto main, apply the three edits above, push. CI here last ran on 2026-08-05 against a base ~4,600 commits old, so it needs a fresh run regardless.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

$0.0000 · 0 in / 0 out

@tinysweeper

tinysweeper Bot commented Sep 2, 2026

Copy link
Copy Markdown

How this change flows

0 changed behaviours across 8 relationships. 4 surrounding behaviours are shown (60 graph nodes walked). 41 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["vec"]:::impacted
  n1["set_many"]:::impacted
  n2["...est_payload_is_captured_at_warn_not_error"]:::impacted
  n3["..._envelope_passes_through_generic_dispatch"]:::impacted
  n2 -->|calls| n0
  n2 -->|tests| n0
  n2 -->|calls| n1
  n2 -->|tests| n1
  n3 -->|calls| n0
  n3 -->|tests| n0
  n3 -->|calls| n1
  n3 -->|tests| n1
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Sep 2, 2026
@samrusani

Copy link
Copy Markdown
Contributor Author

I rebased and rechecked the requested current-main path before publishing. On current main, bootstrap_core_runtime awaits core::bus::init() immediately before the sole production call to register_domain_subscribers; start_channels also awaits bus initialization and no longer calls that registrar. I therefore did not push the defensive rebase, since the remaining trigger described in the PR no longer reproduces. Is there another current path you want this PR to cover, or should I close it as obsolete?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core/jsonrpc: make DomainGroup subscriber registration retryable

2 participants