Skip to content

refactor: simplify architecture with focused modules and test harness#236

Merged
erskingardner merged 10 commits into
masterfrom
refactor/simplify-architecture
Jul 5, 2026
Merged

refactor: simplify architecture with focused modules and test harness#236
erskingardner merged 10 commits into
masterfrom
refactor/simplify-architecture

Conversation

@erskingardner

@erskingardner erskingardner commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

Structural refactor to reduce complexity without changing runtime behavior. Split into seven bisect-friendly commits:

  • Config unificationdefaults.rs is the single source of tuning constants; TokenRateLimitConfig / ReplayProtectionConfig convert from ServerConfig directly (no more inverted imports from nostr::events / rate_limiter in main).
  • Split nostr/eventsdedup, admission, and processor submodules replace the 3k-line monolith; public API unchanged.
  • Extract lib.rs + app.rs — server wiring and event loop move out of main.rs; GlitchTip guard stays in main.
  • Test harness — shared test_support::default_server_config() / server_config_with() fixtures.
  • Always-present MetricsOption<Metrics> removed; enabled flag no-ops recording when metrics are off.
  • Split push/dispatcherhealth and inflight extracted from the dispatcher module.
  • EventProcessorBuilder — test-only fluent builder replaces new / with_cache_size / with_full_config; production uses only with_replay_config.

Test plan

  • just ci passes on each commit (fmt, clippy -D warnings, tests, audit)
  • Smoke-run with local config (cargo run -- --config config/local.toml) — relays connect, /health and /ready respond
  • Confirm metrics endpoint still gated by metrics.enabled in config

Made with Cursor


Open in Stage

Summary by CodeRabbit

  • New Features

    • Added a more robust startup and shutdown flow for the server.
    • Added configurable logging formats and improved healthcheck support.
    • Added support for delivery health tracking and safer in-flight task handling.
  • Bug Fixes

    • Improved event processing reliability, including replay protection and deduplication behavior.
    • Fixed metrics handling so tracking works consistently across server, push, and relay components.
    • Strengthened private-key file permission checks for safer configuration loading.

erskingardner and others added 7 commits July 5, 2026 19:48
Add a single defaults module as the source of truth for tuning constants,
replacing scattered definitions and inverted config→events imports. Event
processor configs now convert directly from ServerConfig via from_server_config.

Co-authored-by: Cursor <cursoragent@cursor.com>
Break the 4k-line events module into focused submodules while keeping the
public API unchanged. Event processor tests move with the implementation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce a library crate with app::run as the composition root so main.rs
only handles CLI parsing, telemetry init, and logging setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce test_support with default_server_config and server_config_with
to replace repeated 15-field literals in app and processor tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pass Metrics through all components instead of Option, gating collection
with an enabled flag so call sites no longer branch on optional handles.

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract delivery health and in-flight task tracking from the monolithic
dispatcher so the queue/worker logic stays easier to navigate and bisect.

Co-authored-by: Cursor <cursoragent@cursor.com>
Collapse new, with_cache_size, and with_full_config into EventProcessorBuilder
so production code only exposes with_replay_config.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5028e2b0-b5e8-4f5f-b186-d461504aad79

📥 Commits

Reviewing files that changed from the base of the PR and between 4f52b08 and d41dc79.

📒 Files selected for processing (12)
  • src/app.rs
  • src/defaults.rs
  • src/metrics.rs
  • src/nostr/client.rs
  • src/nostr/events/admission.rs
  • src/nostr/events/processor.rs
  • src/push/auth.rs
  • src/push/dispatcher/inflight.rs
  • src/push/retry.rs
  • src/rate_limiter.rs
  • src/telemetry.rs
  • src/test_support.rs

Walkthrough

This PR introduces a new src/app.rs orchestration module (run, staged teardown, key/CLI utilities), centralizes default constants into src/defaults.rs, converts Metrics across relay/push/dispatcher/health components from optional to always-present, adds admission/dedup submodules for event replay protection, and adds dispatcher health/in-flight tracking.

Changes

Server refactor: defaults, metrics, admission/dedup, orchestration

Layer / File(s) Summary
Centralized default constants
src/defaults.rs, src/config.rs, src/crypto/nip59.rs, src/rate_limiter.rs
Introduces shared default constants and updates dependent modules to import/re-export from them instead of defining locally.
Metrics enabled/disabled capability
src/metrics.rs
Adds an enabled flag with with_enabled/is_enabled/disabled constructors, gating all recording methods when disabled.
Non-optional Metrics propagation
src/nostr/client.rs, src/push/apns.rs, src/push/fcm.rs, src/push/retry.rs, src/push/dispatcher/mod.rs, src/server/health.rs
Converts stored/passed Option<Metrics> to required Metrics across relay, push clients, retry, dispatcher, and health server, removing conditional guards and updating tests.
Delivery health and in-flight tracking
src/push/dispatcher/health.rs, src/push/dispatcher/inflight.rs, src/push/dispatcher/mod.rs
Adds per-provider hard-failure streak tracking and an in-flight task lifetime tracker wired into the dispatcher.
Event admission and dedup submodules
src/nostr/events/admission.rs, src/nostr/events/dedup.rs, src/nostr/events/mod.rs, src/nostr/events/processor.rs
Adds transactional admission-charge guards and durable/in-memory dedup stores, wiring them into event processing with unconditional metrics recording.
App orchestration entrypoint and CLI wiring
src/app.rs, src/main.rs, src/lib.rs
Adds run, staged teardown, event loop supervision, key generation/resolution, healthcheck, and logging init; refactors main.rs to delegate to app.
Shared test config helpers
src/test_support.rs
Adds default_server_config() and server_config_with() fixture helpers.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the refactor focus and mentions the new modular architecture and test harness.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/simplify-architecture

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

@stage-review

stage-review Bot commented Jul 5, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 7 individual chapters for you:

Title
1 Unify configuration defaults and constants
2 Ensure metrics are always present
3 Refactor push dispatcher modules
4 Update push clients and retry logic
5 Split nostr event processor into submodules
6 Update relay client and health server
7 Extract server wiring and test harness
Open in Stage

Chapters generated by Stage for commit d41dc79 on Jul 5, 2026 8:05pm UTC.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

✅ Code Coverage Report

Metric Value
Line Coverage 96.93%
Lines Covered 17056 / 17597
Change from master +0.02%
View detailed coverage

Download the coverage-html artifact from this workflow run to view the detailed coverage report.

Or run locally:

cargo llvm-cov --html
open target/llvm-cov/html/index.html

Remove stale test imports, add RateLimiter::is_empty for clippy, and
replace intra-doc links to private items that break once the crate is a library.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

Caution

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

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

770-781: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the enabled guard to both histogram observers. observe_notification_parse_duration and observe_push_dispatch_admission_duration bypass self.enabled, so Metrics::disabled() still records these histograms while the other recording methods stay no-op. Add the same early return here.

🤖 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/metrics.rs` around lines 770 - 781, The notification parse and push
dispatch admission histogram observers currently bypass the Metrics::enabled
guard, so Metrics::disabled() still records them while the other metrics methods
remain no-op. Update observe_notification_parse_duration and
observe_push_dispatch_admission_duration in Metrics to match the existing
enabled check pattern used by the other observe_* methods, adding an early
return before calling observe_label_value.
🧹 Nitpick comments (1)
src/test_support.rs (1)

33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent import style for DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING.

All other defaults are imported via the use crate::defaults::{...} block at the top; this one is referenced with a fully-qualified path instead, without an obvious reason.

♻️ Suggested fix
 use crate::defaults::{
     DEFAULT_DEDUP_RETENTION_SECS, DEFAULT_GLOBAL_UNWRAP_LIMIT_PER_HOUR,
     DEFAULT_GLOBAL_UNWRAP_LIMIT_PER_MINUTE, DEFAULT_MAX_DEDUP_CACHE_SIZE,
     DEFAULT_MAX_NOTIFICATION_AGE_SECS, DEFAULT_MAX_NOTIFICATION_FUTURE_SKEW_SECS, DEFAULT_MAX_SIZE,
     DEFAULT_MAX_TOKENS_PER_EVENT, DEFAULT_RATE_LIMIT_PER_HOUR, DEFAULT_RATE_LIMIT_PER_MINUTE,
+    DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING,
 };
 ...
-        max_concurrent_event_processing: crate::defaults::DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING,
+        max_concurrent_event_processing: DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING,
🤖 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/test_support.rs` at line 33, The
`DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING` reference in `test_support` is
inconsistent with the other defaults because it uses a fully qualified path
instead of the top-level `use crate::defaults::{...}` import block. Update the
`test_support` setup to import `DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING`
alongside the existing defaults, and then reference that imported symbol
directly where the config is built.
🤖 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/app.rs`:
- Line 858: The test module has an unused std::path::PathBuf import that
triggers warnings-as-errors in CI. Remove the PathBuf import from the top of the
test module in src/app.rs, since the tests already rely on the existing Path
import and file.path()/display() usage and do not reference PathBuf anywhere.

In `@src/nostr/events/admission.rs`:
- Line 39: The intra-doc link in the `AdmissionOutcome` documentation is
unresolved because `EventProcessor` is not in scope, causing the docs build to
fail. Update the doc comment on `AdmissionOutcome` to either fully qualify
`EventProcessor::process_inner` with the correct path or remove the link if it
is not needed, keeping the reference valid for rustdoc.

In `@src/push/dispatcher/inflight.rs`:
- Line 11: The intra-doc link in the comment on inflight.rs is unresolved,
causing the docs build to fail under warnings-as-errors. Update the
documentation on the Semaphore reference to use a fully-qualified path or
replace the link with a plain code span so rustdoc no longer looks for an
in-scope item named Semaphore.

In `@src/test_support.rs`:
- Around line 1-47: Add tests in src/test_support.rs for default_server_config
and server_config_with. Verify that default_server_config() returns a
ServerConfig that satisfies ServerConfig::validate and that its zero-checked
limits stay non-zero, using the existing fields populated in
default_server_config. Also add a test that server_config_with correctly applies
mutations to a ServerConfig by overriding one or two fields and asserting the
changes are present.

---

Outside diff comments:
In `@src/metrics.rs`:
- Around line 770-781: The notification parse and push dispatch admission
histogram observers currently bypass the Metrics::enabled guard, so
Metrics::disabled() still records them while the other metrics methods remain
no-op. Update observe_notification_parse_duration and
observe_push_dispatch_admission_duration in Metrics to match the existing
enabled check pattern used by the other observe_* methods, adding an early
return before calling observe_label_value.

---

Nitpick comments:
In `@src/test_support.rs`:
- Line 33: The `DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING` reference in
`test_support` is inconsistent with the other defaults because it uses a fully
qualified path instead of the top-level `use crate::defaults::{...}` import
block. Update the `test_support` setup to import
`DEFAULT_MAX_CONCURRENT_EVENT_PROCESSING` alongside the existing defaults, and
then reference that imported symbol directly where the config is built.
🪄 Autofix (Beta)

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

Run ID: a5ef446f-036b-4a9e-92f8-5997d2cd3d66

📥 Commits

Reviewing files that changed from the base of the PR and between 6428fef and 4f52b08.

📒 Files selected for processing (21)
  • src/app.rs
  • src/config.rs
  • src/crypto/nip59.rs
  • src/defaults.rs
  • src/lib.rs
  • src/main.rs
  • src/metrics.rs
  • src/nostr/client.rs
  • src/nostr/events/admission.rs
  • src/nostr/events/dedup.rs
  • src/nostr/events/mod.rs
  • src/nostr/events/processor.rs
  • src/push/apns.rs
  • src/push/dispatcher/health.rs
  • src/push/dispatcher/inflight.rs
  • src/push/dispatcher/mod.rs
  • src/push/fcm.rs
  • src/push/retry.rs
  • src/rate_limiter.rs
  • src/server/health.rs
  • src/test_support.rs

Comment thread src/app.rs Outdated
Comment thread src/nostr/events/admission.rs Outdated
Comment thread src/push/dispatcher/inflight.rs Outdated
Comment thread src/test_support.rs
Add focused tests for defaults, admission refunds, RateLimiter::is_empty,
and clearnet startup validation so CI coverage does not drop below master.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@erskingardner erskingardner left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Adversarial pass found a couple of metrics-refactor issues. I verified the PR head locally with just ci, cargo doc --no-deps, and cargo test --features tor.

Comment thread src/metrics.rs
/// Return a metrics handle that never records (for tests and fallback init).
#[must_use]
pub fn disabled() -> Self {
Self::new().expect("metrics registry").with_enabled(false)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Metrics::disabled() is now used as the fallback after Metrics::new() fails during startup, but it immediately calls Metrics::new().expect(...) again. If the original initialization error is deterministic (duplicate collector name, invalid descriptor, etc.), the fallback path panics instead of preserving the old behavior of logging the metrics init failure and continuing with metrics disabled. Either make this return a Result, make the startup fallback not call the fallible constructor again, or remove the fallback branch if metrics init should be fatal.

Comment thread src/metrics.rs

/// Observe gift-wrap unwrap duration.
pub fn observe_gift_wrap_unwrap_duration(&self, outcome: OperationOutcome, duration_secs: f64) {
if !self.enabled {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This guard pattern is missing from observe_notification_parse_duration() immediately below, and the same omission appears in observe_push_dispatch_admission_duration(). Those two methods are called on the normal event path, so metrics.enabled = false still mutates histogram collectors for notification parsing and dispatcher admission while the surrounding metric methods return early. Please add the same if !self.enabled { return; } guard in both observers, ideally with a regression test for Metrics::disabled().

Propagate metrics registry init failures instead of retrying via
Metrics::disabled(), gate notification-parse and dispatch-admission
observers when disabled, and add tests for those paths and test_support.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@erskingardner
erskingardner merged commit d58dfa7 into master Jul 5, 2026
10 checks passed
@erskingardner
erskingardner deleted the refactor/simplify-architecture branch July 5, 2026 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant