Skip to content

RIC-T39 More Backend IC work#418

Open
ucswift wants to merge 3 commits into
masterfrom
develop
Open

RIC-T39 More Backend IC work#418
ucswift wants to merge 3 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary

This PR implements the backend infrastructure for the Resgrid IC (Incident Command) app's offline-first shift-start workflow, comprising three major additions: (1) two new aggregate sync endpoints that let a field client pull everything it needs in minimal round-trips, (2) a per-endpoint incident-capability authorization layer, and (3) associated tests and documentation.

1. Shift-start aggregate sync endpoints

GET /api/v4/Sync/Bundle — Returns a render-ready IncidentCommandBoard (lanes, resources, objectives, timers, roles, annotations, and computed accountability/PAR) for every active incident in the caller's department, plus active ad-hoc units and personnel, in a single call. The client uses the returned ServerTimestampMs as the cursor for subsequent incremental /Sync/Changes pulls.

  • IncidentCommandService.GetBundleForDepartmentAsync scans each board table once for the whole department and groups by CallId in memory — O(tables) instead of the previous O(active incidents × department size) per-incident N+1 pattern.
  • The bundle is read-only: unlike the per-call GetCommandBoardAsync, it does not run the write-side PAR sweep (no marker writes or SignalR pushes).
  • ?includeAccountability=false lets very busy departments skip the per-incident PAR computation.

GET /api/v4/Sync/Reference — Returns the slowly-changing department configuration and a safe personnel roster needed to start and run an incident offline (call types, priorities, command templates, units, groups, POIs, protocols, custom statuses, feature flags).

  • A new SyncService aggregates data from 12 existing services into a single SyncReferenceData payload.
  • Personnel and groups are projected to safe DTOs (ReferencePersonnel, ReferenceGroup) that structurally exclude IdentityUser navs, password/security fields, and UserProfile contact-verification secrets.
  • Department-scoped cache-aside (5-minute TTL) via a protobuf-safe JSON envelope (ReferenceCacheEnvelope); ?bypassCache=true forces a fresh read.

Batched ad-hoc resources: IncidentResourcesService.GetActiveAdHocResourcesForDepartmentAsync returns all active (non-released) ad-hoc units and personnel scoped to the department's active incidents in one scan per table, replacing the per-incident N+1 lookups.

2. Incident-scoped capability authorization

A new RequiresIncidentCapabilityAttribute action filter enforces that the calling user holds the required IncidentCapabilities (e.g., ManageStructure, AssignResources, ManageAccountability) for the specific incident targeted by the request — layered on top of the existing broad [Authorize(Policy = Command_*)] claims.

  • Resolves the target Call from the request via three strategies: explicit callId route value, IncidentCommandId (resolved to its Call with department-ownership verification), or CallId on the bound body.
  • Fails open when the Call cannot be determined or belongs to another department — the broad Command_* claim and service-layer ownership guards still apply, so the filter only ever adds protection.
  • Applied to 14 endpoints across IncidentCommandController, IncidentResourcesController, IncidentRolesController, and IncidentVoiceController.
  • Intentionally not applied to EstablishCommand (it creates the command, so no capabilities exist yet) or to entity-id "second action" verbs where the target Call isn't on the request.

3. Tests & documentation

  • Added unit tests for the bundle assembly, tombstone filtering, read-only behavior, ad-hoc batching, the safe personnel projection, cache behavior, and the capability filter's allow/deny/fail-open paths.
  • Updated docs/architecture/offline-first-architecture.md with the authoritative shift-start manifest (§6.1) and marked the delta and aggregate endpoints as done (§9).

Summary by CodeRabbit

  • New Features

    • Added offline-ready sync endpoints for incident command boards and department reference data.
    • Introduced a richer sync bundle with active incidents, ad-hoc resources, and a sync cursor.
    • Added real-time incident command update notifications so boards refresh automatically.
  • Bug Fixes

    • Improved access checks so incident actions now require the appropriate capability for the specific task.
    • Tightened resource and role actions to better match user permissions.

@request-info

request-info Bot commented Jun 27, 2026

Copy link
Copy Markdown

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@Resgrid-Bot

Resgrid-Bot commented Jun 27, 2026

Copy link
Copy Markdown

Code Review Could Not Complete ⚠️

The review failed before suggestions could be generated.

Reason: Transient error reaching the provider. Try again.

After fixing the issue, comment @kody review on this PR to re-run the review.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds offline sync bundle/reference endpoints with new department-wide DTOs, service contracts, and aggregation logic. Also adds a per-incident capability filter and applies it to incident command, resource, role, and voice actions, and wires incident command updates through eventing and SignalR.

Changes

Offline Incident Sync Endpoints

Layer / File(s) Summary
Offline sync models and contracts
Core/Resgrid.Model/IncidentCommand/IncidentCommandBundle.cs, Core/Resgrid.Model/IncidentCommand/SyncReferenceData.cs, Core/Resgrid.Model/Services/IIncidentCommandService.cs, Core/Resgrid.Model/Services/IIncidentResourcesService.cs, Core/Resgrid.Model/Services/ISyncService.cs, Web/Resgrid.Web.Services/Models/v4/Sync/SyncModels.cs
Defines the bundle/reference DTOs, safe personnel/group projections, and the service/result contracts used by the sync endpoints.
Sync service implementations
Core/Resgrid.Services/IncidentCommandService.cs, Core/Resgrid.Services/IncidentResourcesService.cs, Core/Resgrid.Services/SyncService.cs, Core/Resgrid.Services/ServicesModule.cs
Implements department-scoped active command and ad-hoc resource lookup, bundle assembly, reference data aggregation, safe personnel projection, and service registration.
Sync endpoints and API responses
Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs, Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
Injects ISyncService, adds Bundle and Reference GET actions, and documents the new sync API surface.

Per-Incident Capability Authorization Filter

Layer / File(s) Summary
Capability filter implementation
Web/Resgrid.Web.Services/Filters/RequiresIncidentCapabilityAttribute.cs, Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
Implements an action filter that resolves a target call id from route/body data, checks per-incident capabilities, and returns HTTP 403 when the required capability is missing.
Capability filter usage in controllers
Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs, Web/Resgrid.Web.Services/Controllers/v4/IncidentResourcesController.cs, Web/Resgrid.Web.Services/Controllers/v4/IncidentRolesController.cs, Web/Resgrid.Web.Services/Controllers/v4/IncidentVoiceController.cs
Adds [RequiresIncidentCapability(...)] to incident command, resource, role, and voice endpoints and updates the related controller imports and comments.

Incident Command Update Event Flow

Layer / File(s) Summary
Incident command update contracts
Core/Resgrid.Model/EventingTypes.cs, Core/Resgrid.Model/Events/IncidentCommandEvents.cs, Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs, Core/Resgrid.Model/Services/ICoreEventService.cs
Defines the incident-command update event DTO, the new eventing type, and the inbound-event callback contract.
Event publish and routing
Core/Resgrid.Services/CoreEventService.cs, Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs, Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs
Publishes incident-command updates from the core event service, maps them into Rabbit topic messages, and wires the outbound event listener.
Inbound dispatch and SignalR broadcast
Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs, Web/Resgrid.Web.Eventing/Worker.cs
Registers the new Rabbit inbound handler, dispatches incident-command update messages by department, and broadcasts them to the matching SignalR group.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Resgrid/Core#312: Adds check-in timer domain support that is included in the new sync reference payload.
  • Resgrid/Core#416: Extends the incident-command update event flow that is also wired through this PR.
  • Resgrid/Core#417: Adds incident-command sync-related service methods that are used by the new bundle endpoint.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic and doesn’t clearly describe the main IC sync, authorization, and eventing changes. Rename it to a specific summary, e.g. "Add IC offline sync bundle/reference endpoints and incident capability gating".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 3

🤖 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 `@Core/Resgrid.Services/SyncService.cs`:
- Around line 56-88: GetReferenceDataAsync currently does all reference/service
reads serially and bypasses the required department cache-aside pattern. Update
SyncService.GetReferenceDataAsync to accept and pass through a bypassCache flag
where needed, then wrap the full payload build in
ICacheProvider.RetrieveAsync<T>() with a local async fallback that calls the
existing services. Keep the safe Group/Personnel projection logic, but ensure
the cache key is department-scoped so callers can reuse or refresh the reference
bootstrap consistently.

In `@Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs`:
- Line 39: `EstablishCommand` is being blocked by `RequiresIncidentCapability`,
which prevents the bootstrap command context from being created before a
command/role exists. Update `IncidentCommandController.EstablishCommand` to use
the existing `Command_Create` policy, or adjust the capability check in the
filter/service so bootstrap establishment is exempt from
`GetCapabilitiesForUserAsync` until the command is initialized.

In `@Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs`:
- Around line 81-87: The controller-level loop in
SyncController.GetBundleForDepartment is doing N+1 ad-hoc lookups by calling
IncidentResourcesService once per board. Move this aggregation into a single
batched call on IncidentResourcesService, or fold it into
GetBundleForDepartmentAsync so bundle.AdHocUnits and bundle.AdHocPersonnel are
populated in one pass. Keep the existing board/callId mapping logic, but replace
the per-board AddRange fetches with a single service method that returns all
ad-hoc data for the department’s active incidents.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 63fa3c79-b938-4413-8726-c082078e5379

📥 Commits

Reviewing files that changed from the base of the PR and between cc0990c and ac093d3.

⛔ Files ignored due to path filters (4)
  • Tests/Resgrid.Tests/Services/IncidentCommandServiceParTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/SyncServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/RequiresIncidentCapabilityFilterTests.cs is excluded by !**/Tests/**
  • docs/architecture/offline-first-architecture.md is excluded by !**/*.md
📒 Files selected for processing (15)
  • Core/Resgrid.Model/IncidentCommand/IncidentCommandBundle.cs
  • Core/Resgrid.Model/IncidentCommand/SyncReferenceData.cs
  • Core/Resgrid.Model/Services/IIncidentCommandService.cs
  • Core/Resgrid.Model/Services/ISyncService.cs
  • Core/Resgrid.Services/IncidentCommandService.cs
  • Core/Resgrid.Services/ServicesModule.cs
  • Core/Resgrid.Services/SyncService.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentResourcesController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentRolesController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentVoiceController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs
  • Web/Resgrid.Web.Services/Filters/RequiresIncidentCapabilityAttribute.cs
  • Web/Resgrid.Web.Services/Models/v4/Sync/SyncModels.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml

Comment thread Core/Resgrid.Services/SyncService.cs Outdated
Comment thread Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs Outdated
Comment thread Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs Outdated
@Resgrid-Bot

This comment has been minimized.

@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: 3

🤖 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 `@Core/Resgrid.Services/SyncService.cs`:
- Around line 33-34: The SyncService reference snapshot is using a hard-coded
5-minute cache lifetime instead of the standard department-data TTL. Update the
caching setup in SyncService by replacing the CacheLength constant with the
shared department cache duration used across the repository, and keep
SyncReferenceData aligned with the repository cache contract. If any part of the
payload needs fresher data, move that volatile subset out rather than shortening
the whole snapshot.

In `@Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs`:
- Around line 107-108: The PageSize calculation in SyncController’s response
only includes Units and Personnel, so the reported metadata is incomplete for
sync payloads. Update the SyncReferenceResult.PageSize assignment to count all
returned collections in the sync reference data, including call types,
priorities, templates, groups, POIs, protocols, timer configs, custom states,
and features, using the same SyncController result-building logic.
- Around line 103-105: The Reference endpoint currently exposes mobile numbers
via SyncService.GetReferenceDataAsync, but SyncController.Reference is only
gated by Command_View. Update the Reference flow to either omit
SyncReferenceData.MobilePhone from the returned payload or conditionally
populate it only when the caller passes the same CanViewPII check used
elsewhere, and make the change in the Reference action and/or the
GetReferenceDataAsync mapping so the PII gate is enforced consistently.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 611c138e-d54e-4f17-b1ae-b83058d4d4da

📥 Commits

Reviewing files that changed from the base of the PR and between ac093d3 and 2044c1b.

⛔ Files ignored due to path filters (3)
  • Tests/Resgrid.Tests/Services/IncidentResourcesServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/SyncServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/RequiresIncidentCapabilityFilterTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (8)
  • Core/Resgrid.Model/IncidentCommand/SyncReferenceData.cs
  • Core/Resgrid.Model/Services/IIncidentResourcesService.cs
  • Core/Resgrid.Model/Services/ISyncService.cs
  • Core/Resgrid.Services/IncidentResourcesService.cs
  • Core/Resgrid.Services/SyncService.cs
  • Web/Resgrid.Web.Services/Controllers/v4/IncidentCommandController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • Core/Resgrid.Model/Services/ISyncService.cs
  • Core/Resgrid.Model/IncidentCommand/SyncReferenceData.cs

Comment on lines +33 to +34
private static readonly string CacheKey = "SyncReferenceData_{0}";
private static readonly TimeSpan CacheLength = TimeSpan.FromMinutes(5);

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.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Use the standard department-data TTL here.

SyncReferenceData is a department-scoped reference snapshot, but this hard-codes a 5-minute lifetime. That will repopulate the full multi-service fan-out far more often than the repository cache contract expects. If part of this payload truly needs near-live freshness, split that volatile subset out instead of shortening the whole snapshot. As per coding guidelines, "most user/department data is cached for 1 day. When testing or requiring fresh data, use bypassCache: true parameter or call invalidation methods."

♻️ Proposed change
-		private static readonly TimeSpan CacheLength = TimeSpan.FromMinutes(5);
+		private static readonly TimeSpan CacheLength = TimeSpan.FromDays(1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static readonly string CacheKey = "SyncReferenceData_{0}";
private static readonly TimeSpan CacheLength = TimeSpan.FromMinutes(5);
private static readonly string CacheKey = "SyncReferenceData_{0}";
private static readonly TimeSpan CacheLength = TimeSpan.FromDays(1);
🤖 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 `@Core/Resgrid.Services/SyncService.cs` around lines 33 - 34, The SyncService
reference snapshot is using a hard-coded 5-minute cache lifetime instead of the
standard department-data TTL. Update the caching setup in SyncService by
replacing the CacheLength constant with the shared department cache duration
used across the repository, and keep SyncReferenceData aligned with the
repository cache contract. If any part of the payload needs fresher data, move
that volatile subset out rather than shortening the whole snapshot.

Source: Coding guidelines

Comment on lines +103 to +105
public async Task<ActionResult<SyncReferenceResult>> Reference(bool bypassCache = false)
{
var data = await _syncService.GetReferenceDataAsync(DepartmentId, bypassCache);

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Compare the Sync reference endpoint's authorization with personnel/PII-related endpoints
# and confirm where mobile numbers are projected into the offline reference payload.
rg -n -C2 'Authorize\(Policy = .*Command_View|Authorize\(Policy = .*Personnel_View|CanViewPII|MobilePhone' \
  Web/Resgrid.Web.Services/Controllers/v4 \
  Core/Resgrid.Services \
  Core/Resgrid.Model

Repository: Resgrid/Core

Length of output: 19108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether Command_View is used for other endpoints that expose roster-like data,
# and whether there is a distinct policy for personnel PII exposure.
rg -n -C2 'Authorize\(Policy = ResgridResources\.(Command_View|Personnel_View)|CanViewPII|MobileNumber|MobilePhone' \
  Web/Resgrid.Web.Services/Controllers/v4 \
  Core/Resgrid.Services \
  Core/Resgrid.Model \
  Core/Resgrid.Framework

Repository: Resgrid/Core

Length of output: 34377


Gate Reference mobile numbers behind a PII check. SyncService.GetReferenceDataAsync() copies UserProfile.MobileNumber into SyncReferenceData.MobilePhone, but SyncController.Reference() is only protected by Command_View. If command access shouldn’t include phone numbers, strip that field from the payload or apply the same CanViewPII gate used elsewhere.

🤖 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 `@Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs` around lines 103 -
105, The Reference endpoint currently exposes mobile numbers via
SyncService.GetReferenceDataAsync, but SyncController.Reference is only gated by
Command_View. Update the Reference flow to either omit
SyncReferenceData.MobilePhone from the returned payload or conditionally
populate it only when the caller passes the same CanViewPII check used
elsewhere, and make the change in the Reference action and/or the
GetReferenceDataAsync mapping so the PII gate is enforced consistently.

Comment on lines +107 to +108
var result = new SyncReferenceResult { Data = data };
result.PageSize = data.Units.Count + data.Personnel.Count;

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

PageSize only counts two collections.

The reference response also returns call types, priorities, templates, groups, POIs, protocols, timer configs, custom states, and features. Reporting only Units.Count + Personnel.Count makes the standard metadata wrong for every non-trivial payload.

🤖 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 `@Web/Resgrid.Web.Services/Controllers/v4/SyncController.cs` around lines 107 -
108, The PageSize calculation in SyncController’s response only includes Units
and Personnel, so the reported metadata is incomplete for sync payloads. Update
the SyncReferenceResult.PageSize assignment to count all returned collections in
the sync reference data, including call types, priorities, templates, groups,
POIs, protocols, timer configs, custom states, and features, using the same
SyncController result-building logic.

@Resgrid-Bot

Resgrid-Bot commented Jun 28, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@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

🧹 Nitpick comments (1)
Web/Resgrid.Web.Eventing/Worker.cs (1)

114-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the framework logger instead of Console.WriteLine.

This new handler is bypassing the standard logging pipeline, so these event traces won't be handled consistently with the rest of the service.

As per coding guidelines, use Resgrid.Framework.Logging static methods (LogException, LogError, LogInfo, LogDebug) for all logging throughout the codebase.

Suggested change
-			Console.WriteLine($"Processing RabbitMQ IncidentCommandUpdated Event For {departmentId}");
+			Resgrid.Framework.Logging.LogInfo($"Processing RabbitMQ IncidentCommandUpdated Event For {departmentId}");
🤖 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 `@Web/Resgrid.Web.Eventing/Worker.cs` around lines 114 - 121, The
IncidentCommandUpdated handler is writing trace output directly with
Console.WriteLine, bypassing the standard logging pipeline. Replace that call in
Worker.IncidentCommandUpdated with the appropriate Resgrid.Framework.Logging
static method (for example LogInfo or LogDebug) so event processing is logged
consistently with the rest of the service. Keep the existing context in the
message, and leave the hub send logic unchanged.

Source: Coding guidelines

🤖 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 `@Core/Resgrid.Services/CoreEventService.cs`:
- Around line 15-17: `IncidentCommandUpdatedAsync` now relies on an in-process
send and fire-and-forget publish, which can lose the only client sync trigger
before persistence. Update `CoreEventService` so the event path uses a durable,
awaited handoff before returning—either restore the previous CQRS enqueue or
switch `SendMessage`/the downstream publisher path to a persisted publish
mechanism and await completion in `IncidentCommandUpdatedAsync`.

---

Nitpick comments:
In `@Web/Resgrid.Web.Eventing/Worker.cs`:
- Around line 114-121: The IncidentCommandUpdated handler is writing trace
output directly with Console.WriteLine, bypassing the standard logging pipeline.
Replace that call in Worker.IncidentCommandUpdated with the appropriate
Resgrid.Framework.Logging static method (for example LogInfo or LogDebug) so
event processing is logged consistently with the rest of the service. Keep the
existing context in the message, and leave the hub send logic unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b70a498-0942-4bc3-bd6f-136a5f2488ad

📥 Commits

Reviewing files that changed from the base of the PR and between 2044c1b and 46c8bf3.

⛔ Files ignored due to path filters (1)
  • Tests/Resgrid.Tests/Services/CoreEventServiceTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (9)
  • Core/Resgrid.Model/EventingTypes.cs
  • Core/Resgrid.Model/Events/IncidentCommandEvents.cs
  • Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs
  • Core/Resgrid.Model/Services/ICoreEventService.cs
  • Core/Resgrid.Services/CoreEventService.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs
  • Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs
  • Web/Resgrid.Web.Eventing/Worker.cs
✅ Files skipped from review due to trivial changes (1)
  • Core/Resgrid.Model/Services/ICoreEventService.cs

Comment on lines +15 to 17
public CoreEventService(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Keep a durable handoff for IncidentCommandUpdatedAsync.

This now returns before the update is persisted anywhere. SendMessage(...) only dispatches in-process, and the new downstream publisher path is fire-and-forget, so a Rabbit publish failure or process recycle can drop the only sync trigger for connected IC clients.

Either keep the previous CQRS enqueue here or move this event rail to an awaitable/persisted publish path before returning.

Also applies to: 28-39

🤖 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 `@Core/Resgrid.Services/CoreEventService.cs` around lines 15 - 17,
`IncidentCommandUpdatedAsync` now relies on an in-process send and
fire-and-forget publish, which can lose the only client sync trigger before
persistence. Update `CoreEventService` so the event path uses a durable, awaited
handoff before returning—either restore the previous CQRS enqueue or switch
`SendMessage`/the downstream publisher path to a persisted publish mechanism and
await completion in `IncidentCommandUpdatedAsync`.

Comment on lines +118 to +121
case EventingTypes.IncidentCommandUpdated:
if (ProcessIncidentCommandUpdated != null)
await ProcessIncidentCommandUpdated.Invoke(eventingMessage.DepartmentId, eventingMessage.ItemId);
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception vulnerability in RabbitInboundEventProvider.cs and Web/Resgrid.Web.Eventing/Worker.cs:114-122 where await ProcessIncidentCommandUpdated.Invoke(...) executes without a try/catch. A thrown callback, such as a SignalR SendAsync failure, propagates unhandled through the async-void consumer.ReceivedAsync handler and risks consumer instability without logging; wrap the invocation in a try/catch block to log exceptions.

Kody rule violation: Handle async operations with proper error handling

case EventingTypes.IncidentCommandUpdated:
							if (ProcessIncidentCommandUpdated != null)
							{
								try
								{
									await ProcessIncidentCommandUpdated.Invoke(eventingMessage.DepartmentId, eventingMessage.ItemId);
								}
								catch (Exception ex)
								{
									Framework.Logging.LogException(ex);
								}
							}
							break;
Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs:

Line 118 to 121:

Violates rule 'Handle async operations with proper error handling': the newly added 'case EventingTypes.IncidentCommandUpdated' block calls 'await ProcessIncidentCommandUpdated.Invoke(...)' without try/catch. If the invoked callback throws (e.g., SignalR SendAsync failure in Worker.IncidentCommandUpdated), the exception propagates unhandled through the async-void consumer.ReceivedAsync event handler, risking consumer instability with no error logging.

Suggested Code:

case EventingTypes.IncidentCommandUpdated:
							if (ProcessIncidentCommandUpdated != null)
							{
								try
								{
									await ProcessIncidentCommandUpdated.Invoke(eventingMessage.DepartmentId, eventingMessage.ItemId);
								}
								catch (Exception ex)
								{
									Framework.Logging.LogException(ex);
								}
							}
							break;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

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.

2 participants