Skip to content

Develop - #443

Merged
ucswift merged 3 commits into
masterfrom
develop
Jul 30, 2026
Merged

Develop#443
ucswift merged 3 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 29, 2026

Copy link
Copy Markdown
Member

This PR improves the reliability of voice call webhooks and TTS (text-to-speech) infrastructure, adds health check endpoints, and fixes several bugs in dispatch audio and text preprocessing.

TTS Reliability & Fallback:

  • Adds a configurable TwilioSayFallbackEnabled flag (default off) that degrades voice prompts to Twilio's native <Say> verb when TTS generation fails or times out, preventing 500 errors that Twilio reads to callers as "application error"
  • Introduces a 6-second shared time budget per webhook request so a hung TTS service can't stall past Twilio's 15-second webhook limit; remaining prompts fall back to <Say> once expired
  • Catches TTS generation failures gracefully at the service layer, preventing unhandled exceptions from escaping the webhook

Voice Call Flow Improvements:

  • Enables asynchronous Answering Machine Detection (AMD) on outbound calls, eliminating several seconds of silence the callee previously heard while detection completed
  • Changes retry-exhausted dispatch behavior to speak dispatch via native voice (if fallback enabled) or proceed to menu, rather than playing a misleading "call closed" prompt and hanging up
  • Adds timeouts to reverse-geocoding calls and fixes an un-awaited Task that was stringified into SMS replies
  • Null-guards personnel/unit status lookups to prevent null-reference 500 errors on users or units with missing state data
  • Consolidates list-style prompts (active calls, personnel, units, calendar items) into single joined prompts to leverage the TTS layer's parallel chunking, reducing sequential round-trips
  • Adds sentence-boundary periods to dispatch prompts for clearer TTS pronunciation

Health Check Endpoints:

  • Adds /health (shallow liveness, no external calls) and /health/full (deep dependency probes) endpoints to both the API and TTS microservice
  • Includes health checks for SQL database connectivity, Redis cache round-trips, TTS microservice reachability, and functional synthesis pipeline (Piper/ffmpeg) probes with result memoization

Text Preprocessor Fixes:

  • Makes abbreviation/shorthand expansion case-sensitive so ordinary English words ("so", "apt", "co") are no longer rewritten into dispatch jargon
  • Fixes address abbreviation expansion to preserve the house number and street name instead of replacing the entire address
  • Prevents the number-to-word converter from interfering with digit-by-digit runs produced by long-number expansion

Other Changes:

  • New departments now have modern notification sounds enabled by default
  • Adds a "Please wait while we prepare your dispatch information" voice prompt
  • Comprehensive test coverage added for health checks, TTS fallback behavior, and text preprocessing rules

Summary by CodeRabbit

  • New Features

    • Added shallow and secured full health-check endpoints covering application, database, cache, storage, and TTS availability.
    • Added optional Twilio voice fallback so prompts can use native speech when generated audio is unavailable or delayed.
    • New departments enable modern notifications by default.
    • Added a dispatch information waiting prompt.
  • Bug Fixes

    • Improved voice call responsiveness by removing voicemail detection delays.
    • Prevented malformed addresses, missing statuses, and slow geocoding from disrupting voice and SMS workflows.
    • Improved TTS text handling for abbreviations, addresses, and number formatting.

@Resgrid-Bot

This comment has been minimized.

@request-info

request-info Bot commented Jul 29, 2026

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cf4a50c2-01a1-4cab-a267-794f85ebb39a

📥 Commits

Reviewing files that changed from the base of the PR and between ceac706 and a192224.

⛔ Files ignored due to path filters (2)
  • Tests/Resgrid.Tests/Web/Services/HealthChecksTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Tts/TextPreprocessorTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (11)
  • Core/Resgrid.Config/SystemBehaviorConfig.cs
  • Providers/Resgrid.Providers.Number/OutboundVoiceProvider.cs
  • Web/Resgrid.Web.Services/Controllers/TwilioController.cs
  • Web/Resgrid.Web.Services/Health/FullHealthCheckAccess.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web.Services/Startup.cs
  • Web/Resgrid.Web.Services/Twilio/ITwilioVoiceResponseService.cs
  • Web/Resgrid.Web.Services/Twilio/TwilioVoiceResponseService.cs
  • Web/Resgrid.Web.Tts/Health/TtsHealthCheckAccess.cs
  • Web/Resgrid.Web.Tts/Program.cs
  • Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs

📝 Walkthrough

Walkthrough

The change adds configurable Twilio <Say> fallback and bounded voice-webhook TTS handling, shallow and authenticated full health endpoints with dependency probes, TTS preprocessing corrections, removal of outbound AMD settings, and a default modern-notifications setting for new departments.

Changes

Twilio voice and TTS flow

Layer / File(s) Summary
Twilio fallback contracts and configuration
Core/Resgrid.Config/TtsConfig.cs, Web/Resgrid.Web.Services/Twilio/*
Adds configurable native <Say> fallback and consolidates the fallback API around a generic TwiML parent.
TwiML prompt generation and fallback
Web/Resgrid.Web.Services/Twilio/TwilioVoiceResponseService.cs
Handles TTS cancellation and failures by returning chunked native <Say> verbs when enabled.
Webhook budget and dispatch playback
Web/Resgrid.Web.Services/Controllers/TwilioController.cs
Adds shared TTS timing, bounded geocoding, dispatch retries, and dispatch-text fallback.
Inbound voice prompt composition
Web/Resgrid.Web.Services/Controllers/TwilioController.cs
Joins menu prompts, adds punctuation, and handles missing status values safely.
Outbound call answering behavior
Providers/Resgrid.Providers.Number/OutboundVoiceProvider.cs
Removes machine detection and asynchronous AMD options from mobile and home calls.

Health endpoints and dependency probes

Layer / File(s) Summary
Web health response and dependency checks
Core/Resgrid.Config/SystemBehaviorConfig.cs, Web/Resgrid.Web.Services/Health/*, Web/Resgrid.Web.Services/Startup.cs, Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
Adds JSON health output, Redis/SQL/TTS checks, shared-key access control, and shallow/full endpoint routing.
TTS functional health probe
Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs
Probes Redis, object storage, and synthesis with serialized execution, caching, and timeouts.
TTS health endpoint wiring
Web/Resgrid.Web.Tts/Health/TtsHealthCheckAccess.cs, Web/Resgrid.Web.Tts/Program.cs
Registers tagged checks, protects and exposes both health endpoints, includes check data, and excludes health paths from Sentry sampling.

TTS text preprocessing

Layer / File(s) Summary
Preprocessing normalization rules
Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs
Makes abbreviation matching case-sensitive, preserves address prefixes, and tightens small-number normalization.

Department defaults

Layer / File(s) Summary
Department notification initialization
Core/Resgrid.Services/DepartmentsService.cs
Stores EnableModernNotifications as enabled when creating departments.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant TwilioController
  participant TwilioVoiceResponseService
  participant TtsService
  Caller->>TwilioController: Submit voice webhook
  TwilioController->>TwilioVoiceResponseService: Render prompt with time budget
  TwilioVoiceResponseService->>TtsService: Generate audio
  TtsService-->>TwilioVoiceResponseService: Audio or timeout/failure
  TwilioVoiceResponseService-->>TwilioController: Play or Say TwiML
  TwilioController-->>Caller: Twilio voice response
Loading
sequenceDiagram
  participant Monitor
  participant TtsHealthEndpoint
  participant TtsFullHealthCheck
  participant Redis
  participant Storage
  participant AudioProcessingService
  Monitor->>TtsHealthEndpoint: Request /health/full
  TtsHealthEndpoint->>TtsFullHealthCheck: Run functional probe
  TtsFullHealthCheck->>Redis: Write and read probe marker
  TtsFullHealthCheck->>Storage: Check probe object
  TtsFullHealthCheck->>AudioProcessingService: Generate normalized WAV
  TtsFullHealthCheck-->>TtsHealthEndpoint: HealthReport with component data
  TtsHealthEndpoint-->>Monitor: JSON health response
Loading

Possibly related PRs

  • Resgrid/Core#357: Overlaps with Twilio controller dispatch and prompt playback changes.
  • Resgrid/Core#373: Modifies the same outbound Twilio call options and AMD behavior.
  • Resgrid/Core#420: Overlaps with dispatch TTS prompt rendering and pre-warming changes.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.73% 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 to describe the main change and does not convey any meaningful information about the pull request. Replace it with a concise, specific summary of the primary change, such as adding TTS/health-check reliability improvements.
✅ 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 💡 1
📝 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: 5

🧹 Nitpick comments (5)
Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs (1)

20-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the required Service Locator dependency resolution.

  • Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs#L20-L23: resolve ICacheProvider through Bootstrapper.GetKernel().Resolve<T>().
  • Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs#L22-L25: resolve IHttpClientFactory through Bootstrapper.GetKernel().Resolve<T>().

As per coding guidelines, “Use Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection.”

🤖 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/Health/RedisHealthCheck.cs` around lines 20 - 23,
Replace constructor injection in RedisHealthCheck by resolving ICacheProvider
through Bootstrapper.GetKernel().Resolve<T>(); apply the same
constructor-resolution change in
Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs at lines 22-25 for
IHttpClientFactory. Remove the corresponding constructor parameters while
preserving each class’s existing dependency fields and behavior.

Source: Coding guidelines

Web/Resgrid.Web.Services/Twilio/TwilioVoiceResponseService.cs (1)

95-131: 🚀 Performance & Scalability | 🔵 Trivial

Consider bounding concurrency in the chunk fan-out.

Task.WhenAll fires one TTS request per chunk with no upper bound. This was previously self-limiting because callers appended one prompt at a time; TwilioController.cs now joins large lists (all active calls, all users, all units, all calendar events) into a single prompt before calling this method, so a big department can generate many chunks that all hit the TTS microservice simultaneously in one call. Worst case this self-heals via the caller's 6s budget (falls back to <Say>), but it needlessly hammers the TTS service for large lists.

Consider capping concurrent chunk generation (e.g., SemaphoreSlim) or capping the number of joined items per prompt.

🤖 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/Twilio/TwilioVoiceResponseService.cs` around lines
95 - 131, The chunk fan-out in CreatePlayVerbsAsync launches an unbounded TTS
request for every chunk. Add bounded concurrency around
GetOrCreatePromptUrlAsync, using a SemaphoreSlim or equivalent limit while
preserving cancellation and the existing fallback behavior, so large prompts do
not hammer the TTS service simultaneously.
Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs (2)

138-152: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No server-side logging of probe failures.

Failures/timeouts are only surfaced transiently in the HTTP response; nothing is logged when a probe fails, making it hard to correlate historical outages if monitoring doesn't scrape frequently enough or a poll is missed. Consider injecting ILogger<TtsFullHealthCheck> and logging at warning/error level inside the catch blocks.

🤖 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.Tts/Health/TtsFullHealthCheck.cs` around lines 138 - 152,
Update TtsFullHealthCheck to inject and retain an ILogger<TtsFullHealthCheck>,
then log probe timeouts at warning level and unexpected probe failures at error
level within the existing catch blocks, including the probe name and exception
details where available. Preserve the current response data and errors.Add
behavior.

73-115: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential probes bound worst-case latency to 30s.

Each probe gets its own fresh 10s ComponentTimeout, but RunProbesAsync awaits redis, s3, and synthesis one after another, so a full timeout cascade on /health/full can take up to ~30 seconds. Since this endpoint is intended for active monitoring/diagnostics, consider running the probes concurrently to bound total latency to the single slowest probe's timeout instead of their sum.

♻️ Sketch of parallel execution (requires thread-safe collections)
-			var data = new Dictionary<string, object>();
-			var errors = new List<string>();
-
-			await ProbeAsync("redis", data, errors, async token => { ... }, cancellationToken);
-			await ProbeAsync("s3", data, errors, async token => { ... }, cancellationToken);
-			await ProbeAsync("synthesis", data, errors, async token => { ... }, cancellationToken);
+			var data = new ConcurrentDictionary<string, object>();
+			var errors = new ConcurrentBag<string>();
+
+			await Task.WhenAll(
+				ProbeAsync("redis", data, errors, async token => { ... }, cancellationToken),
+				ProbeAsync("s3", data, errors, async token => { ... }, cancellationToken),
+				ProbeAsync("synthesis", data, errors, async token => { ... }, cancellationToken));

Also applies to: 126-153

🤖 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.Tts/Health/TtsFullHealthCheck.cs` around lines 73 - 115,
Update RunProbesAsync so the redis, s3, and synthesis ProbeAsync calls start
concurrently and are awaited together, bounding total probe time to the slowest
ComponentTimeout rather than their sum. Preserve each probe’s existing behavior
and ensure shared data/error collection is safe for concurrent updates.
Web/Resgrid.Web.Tts/Program.cs (1)

133-146: 🩺 Stability & Availability | 🔵 Trivial

Consider restricting /health/full given it performs real work.

Unlike /health, this endpoint triggers a live Piper/ffmpeg synthesis call, an S3 reachability check, and a Redis round-trip. It's mapped here with no RequireRateLimiting(...)/auth, and the existing "tts" rate-limit policy isn't applied to health endpoints. Since the comments describe this as diagnostics-only ("do not wire probes to this endpoint"), consider enforcing that at the network/ingress layer or via an explicit auth/rate-limit requirement, so it can't be hammered externally to repeatedly trigger the underlying synthesis pipeline.

🤖 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.Tts/Program.cs` around lines 133 - 146, Restrict the deep
diagnostic endpoint mapped by the `/health/full` `MapHealthChecks` call so it
cannot be accessed or repeatedly invoked by unauthenticated external clients.
Apply the project’s established authentication or rate-limiting mechanism, or
enforce an equivalent network/ingress restriction, while leaving the shallow
`/health` probe publicly usable for orchestration.
🤖 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 `@Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs`:
- Around line 43-46: Log caught dependency-probe exceptions with
Resgrid.Framework.Logging.LogException(ex, ...) before returning the unhealthy
result in RedisHealthCheck.cs (lines 43-46), SqlDatabaseHealthCheck.cs (lines
38-41), and TtsServiceHealthCheck.cs (lines 51-54).
- Around line 25-36: Update RedisHealthCheck.CheckHealthAsync to create and use
a short local timeout cancellation token for both SetStringAsync and
GetStringAsync instead of the default token. Catch cancellation caused by that
timeout and return an unhealthy HealthCheckResult, while preserving the existing
failure handling and successful round-trip behavior.

In `@Web/Resgrid.Web.Services/Startup.cs`:
- Around line 751-756: Restrict the `/health/full` endpoint registration in
`Startup.Configure` to trusted monitoring by applying a dedicated monitoring
authorization policy or equivalent ingress-only requirement. Ensure
authentication and authorization are enforced before the deep SQL, Redis, and
TTS probes execute, while leaving only the shallow `/health` endpoint publicly
accessible.

In `@Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs`:
- Around line 148-152: Update the exception handler in the full health-check
probe flow to log the complete exception server-side, while adding only a
generic probe-failure message to the response errors collection. Preserve the
existing timing/status entry and OperationCanceledException filtering, and
ensure ex.Message is not included in HealthCheckResult.Description.

In `@Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs`:
- Around line 270-276: Update DispatchShorthandMap to support the intended
casing of the “etc” shorthand used by the case-sensitive matcher, ensuring
uppercase ETC and mixed-case Etc expand without changing matching behavior for
other entries.

---

Nitpick comments:
In `@Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs`:
- Around line 20-23: Replace constructor injection in RedisHealthCheck by
resolving ICacheProvider through Bootstrapper.GetKernel().Resolve<T>(); apply
the same constructor-resolution change in
Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs at lines 22-25 for
IHttpClientFactory. Remove the corresponding constructor parameters while
preserving each class’s existing dependency fields and behavior.

In `@Web/Resgrid.Web.Services/Twilio/TwilioVoiceResponseService.cs`:
- Around line 95-131: The chunk fan-out in CreatePlayVerbsAsync launches an
unbounded TTS request for every chunk. Add bounded concurrency around
GetOrCreatePromptUrlAsync, using a SemaphoreSlim or equivalent limit while
preserving cancellation and the existing fallback behavior, so large prompts do
not hammer the TTS service simultaneously.

In `@Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs`:
- Around line 138-152: Update TtsFullHealthCheck to inject and retain an
ILogger<TtsFullHealthCheck>, then log probe timeouts at warning level and
unexpected probe failures at error level within the existing catch blocks,
including the probe name and exception details where available. Preserve the
current response data and errors.Add behavior.
- Around line 73-115: Update RunProbesAsync so the redis, s3, and synthesis
ProbeAsync calls start concurrently and are awaited together, bounding total
probe time to the slowest ComponentTimeout rather than their sum. Preserve each
probe’s existing behavior and ensure shared data/error collection is safe for
concurrent updates.

In `@Web/Resgrid.Web.Tts/Program.cs`:
- Around line 133-146: Restrict the deep diagnostic endpoint mapped by the
`/health/full` `MapHealthChecks` call so it cannot be accessed or repeatedly
invoked by unauthenticated external clients. Apply the project’s established
authentication or rate-limiting mechanism, or enforce an equivalent
network/ingress restriction, while leaving the shallow `/health` probe publicly
usable for orchestration.
🪄 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 Plus

Run ID: 06fed034-f892-4f80-98cf-5fbc34fd7050

📥 Commits

Reviewing files that changed from the base of the PR and between 76df728 and ceac706.

⛔ Files ignored due to path filters (4)
  • Tests/Resgrid.Tests/Web/Services/HealthChecksTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/TwilioControllerVoiceVerificationTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/TwilioVoiceResponseServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Tts/TextPreprocessorTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (15)
  • Core/Resgrid.Config/TtsConfig.cs
  • Core/Resgrid.Services/DepartmentsService.cs
  • Providers/Resgrid.Providers.Number/OutboundVoiceProvider.cs
  • Web/Resgrid.Web.Services/Controllers/TwilioController.cs
  • Web/Resgrid.Web.Services/Health/HealthResponseWriter.cs
  • Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs
  • Web/Resgrid.Web.Services/Health/SqlDatabaseHealthCheck.cs
  • Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web.Services/Startup.cs
  • Web/Resgrid.Web.Services/Twilio/ITwilioVoiceResponseService.cs
  • Web/Resgrid.Web.Services/Twilio/TwilioVoiceResponseService.cs
  • Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs
  • Web/Resgrid.Web.Tts/Program.cs
  • Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs

Comment on lines +25 to +36
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
// The provider swallows connection failures and returns false/null, so a
// failed round-trip surfaces here as a value mismatch rather than a throw.
var stored = await _cacheProvider.SetStringAsync(ProbeKey, "ok", TimeSpan.FromMinutes(1));

if (!stored)
return HealthCheckResult.Unhealthy("Redis write failed; the cache provider reports it is unavailable.");

var value = await _cacheProvider.GetStringAsync(ProbeKey);

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -a 'ICacheProvider.cs' . -x sed -n '1,260p' {}
fd -a -i 'AzureRedisCacheProvider.cs' . -x sed -n '1,320p' {}
rg -n -C3 'SetStringAsync\(|GetStringAsync\(|AddCheck<.*RedisHealthCheck' .

Repository: Resgrid/Core

Length of output: 13259


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs
ast-grep outline Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs --view expanded
rg -n -C4 'class (.+SqlDatabaseHealthCheck|.*TtsServiceHealthCheck)|CancellationToken cancellationToken|WithTimeout|Timeout|SetCommand|Default|RedisHealthCheck' Web/Resgrid.Web.Services/Health Core/Resgrid.Model/Providers Web/Resgrid.Web.Services/Startup.cs
fd -a 'AzureRedisCacheProvider.cs' .
sed -n '1,360p' $(fd -a 'AzureRedisCacheProvider.cs' . | head -n1)

Repository: Resgrid/Core

Length of output: 22872


🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd -a 'AzureRedisCacheProvider.cs' .
for f in $(fd -a 'AzureRedisCacheProvider.cs' .); do
  echo '===== ' "$f" ' ====='
  sed -n '1,360p' "$f"
done

Repository: Resgrid/Core

Length of output: 150


🌐 Web query:

site:github.com/Resgrid/Core AzureRedisCacheProvider.cs ICacheProvider SetStringAsync GetStringAsync

💡 Result:

In the Resgrid Core architecture, AzureRedisCacheProvider.cs is the implementation of the ICacheProvider interface [1][2]. While specific signature details for SetStringAsync or GetStringAsync are internal to that repository's implementation, the project primarily utilizes a Retrieve pattern rather than direct low-level string set/get operations [1][2]. The ICacheProvider interface typically exposes a generic RetrieveAsync method to handle caching logic, including fallback mechanisms [1][2]. Example usage of the cache provider in the Resgrid codebase: async Task GetFooAsync(int departmentId, bool bypassCache = false) { async Task getFoo { //... actual logic... return foo; } if (!bypassCache && Config.SystemBehaviorConfig.CacheEnabled) return await _cacheProvider.RetrieveAsync(cacheKey, getFoo, cacheDuration); else return await getFoo; } For projects requiring direct interaction with Redis, the Resgrid codebase also uses the standard.NET AddStackExchangeRedisCache integration where appropriate [3]. If you are working directly with the Resgrid Core source, you can find the interface definition at Core/Resgrid.Model/Providers/ICacheProvider.cs and the implementation at Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs [1][2].

Citations:


Bound the Redis health probe.

The SQL and TTS health checks enforce a short probe deadline, but SetStringAsync()/GetStringAsync() receive the default cancellation token. Add a local timeout to CheckHealthAsync() and treat cancellation as an unhealthy probe failure.

🤖 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/Health/RedisHealthCheck.cs` around lines 25 - 36,
Update RedisHealthCheck.CheckHealthAsync to create and use a short local timeout
cancellation token for both SetStringAsync and GetStringAsync instead of the
default token. Catch cancellation caused by that timeout and return an unhealthy
HealthCheckResult, while preserving the existing failure handling and successful
round-trip behavior.

Comment on lines +43 to +46
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Redis probe failed.", ex);
}

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Log dependency-probe failures before returning unhealthy.

  • Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs#L43-L46: call Resgrid.Framework.Logging.LogException(ex, ...).
  • Web/Resgrid.Web.Services/Health/SqlDatabaseHealthCheck.cs#L38-L41: call Resgrid.Framework.Logging.LogException(ex, ...).
  • Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs#L51-L54: call Resgrid.Framework.Logging.LogException(ex, ...).

The response writer does not serialize exceptions, so these failures otherwise lack diagnostic detail. As per coding guidelines, “Use Resgrid.Framework.Logging.LogException(...) when catching exceptions.”

📍 Affects 3 files
  • Web/Resgrid.Web.Services/Health/RedisHealthCheck.cs#L43-L46 (this comment)
  • Web/Resgrid.Web.Services/Health/SqlDatabaseHealthCheck.cs#L38-L41
  • Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs#L51-L54
🤖 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/Health/RedisHealthCheck.cs` around lines 43 - 46,
Log caught dependency-probe exceptions with
Resgrid.Framework.Logging.LogException(ex, ...) before returning the unhealthy
result in RedisHealthCheck.cs (lines 43-46), SqlDatabaseHealthCheck.cs (lines
38-41), and TtsServiceHealthCheck.cs (lines 51-54).

Source: Coding guidelines

Comment thread Web/Resgrid.Web.Services/Startup.cs
Comment on lines +148 to +152
catch (Exception ex) when (ex is not OperationCanceledException)
{
data[name] = $"failed ({stopwatch.ElapsedMilliseconds}ms)";
errors.Add($"The {name} probe failed: {ex.Message}");
}

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find relevant files"
fd -a 'TtsFullHealthCheck.cs|Program.cs' . | sed 's#^\./##'

echo
echo "Locate health response serializer/call sites"
rg -n "WriteHealthResponseAsync|/health/full|Description|HealthCheckResult|errors|TtsFullHealthCheck" -S .

echo
echo "Inspect TtsFullHealthCheck around reported lines"
if [ -f Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs ]; then
  sed -n '1,230p' Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs | nl -ba | sed -n '1,230p'
fi

echo
echo "Inspect Program.cs relevant health JSON section"
if [ -f Web/Resgrid.Web.Tts/Program.cs ]; then
  rg -n -A 80 -B 20 "WriteHealthResponseAsync|HealthCheckResult|health" Web/Resgrid.Web.Tts/Program.cs || true
fi

Repository: Resgrid/Core

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked file candidates"
git ls-files 'Web/Resgrid.Web.Tts/**' | sed -n '1,120p'

echo
echo "TtsFullHealthCheck.cs lines 120-170"
sed -n '120,170p' Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs | nl -ba -v120

echo
echo "Program.cs health-related snippets"
rg -n -C 5 'WriteHealthResponseAsync|HealthCheckResult|HealthCheckService|endpoint|mapHealth|/health|full' Web/Resgrid.Web.Tts/Program.cs | sed -n '1,260p'

echo
echo "Restricted endpoints / internal-only references around TTS Web"
rg -n -i 'denyAnonymous|Authorize|AllowAnonymous|/health|WriteHealthResponseAsync|endpointFilter|IPNetwork|XForwardedFor|internal|monitoring' Web/Resgrid.Web.Tts | sed -n '1,260p'

Repository: Resgrid/Core

Length of output: 1969


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "TtsFullHealthCheck.cs lines 120-170"
awk 'NR>=120 && NR<=170 { printf "%d:%s\n", NR, $0 }' Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs

echo
echo "Program.cs health-related snippets"
python3 - <<'PY'
import re
from pathlib import Path
text = Path("Web/Resgrid.Web.Tts/Program.cs").read_text()
lines = text.splitlines()
for i,l in enumerate(lines,1):
    if "WriteHealthResponseAsync" in l or "HealthCheckResult" in l or "Health" in l or "/health" in l or "full" in l or "mapHealth" in l or "endpointFilter" in l:
        start=max(1,i-8); end=min(len(lines),i+8)
        print(f"-- around line {i} --")
        for j in range(start,end+1):
            print(f"{j}:{lines[j-1]}")
        print()
PY

echo
echo "Authorization / network / health restriction references"
rg -n -i 'denyanonymous|authorize|allowanonymous|health|/health|full|internal|monitoring|ipnetwork|x-forwarded|xforwarded|endpointfilter|maphealth|writ healthresponseasync' Web/Resgrid.Web.Tts | sed -n '1,260p'

Repository: Resgrid/Core

Length of output: 20602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "WriteHealthResponseAsync and auth middleware ordering"
python3 - <<'PY'
from pathlib import Path
text = Path("Web/Resgrid.Web.Tts/Program.cs").read_text().splitlines()
for i,l in enumerate(text,1):
    if "app.UseAuthentication();" in l or "app.UseAuthorization();" in l or l.strip() == "static async Task WriteHealthResponseAsync(HttpContext context, HealthReport report)":
        start=max(1,i-12); end=min(len(text),i+45)
        print(f"-- around line {i} --")
        for j in range(start,end+1):
            print(f"{j}:{text[j-1]}")
PY

echo
echo "TtsFullHealthCheck.cs lines 1-115"
awk 'NR>=1 && NR<=115 { printf "%d:%s\n", NR، $0 }' Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs

echo
echo "Authorization-related attribute/config in TTS app"
rg -n 'app\.UseAuthorization|app\.UseAuthentication|MapHealthChecks\([^)]*HealthCheckOptions|DenyAnonymousHostFilter|RequireAuthorization|AllowAnonymous|/health/full' -S .

Repository: Resgrid/Core

Length of output: 1771


Do not return probe exception messages from /health/full.

errors.Add($"The {name} probe failed: {ex.Message}") flows into HealthCheckResult.Description and serializes as the JSON description, exposing SDK/client error details to callers. Log the full exception server-side and return a generic probe failure message in the response, even if /health/full is restricted to monitoring access.

🤖 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.Tts/Health/TtsFullHealthCheck.cs` around lines 148 - 152,
Update the exception handler in the full health-check probe flow to log the
complete exception server-side, while adding only a generic probe-failure
message to the response errors collection. Preserve the existing timing/status
entry and OperationCanceledException filtering, and ensure ex.Message is not
included in HealthCheckResult.Description.

Comment thread Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs
Comment on lines 40 to 46
options.MachineDetection = "Enable";
// Async AMD: without it Twilio holds the webhook (and the callee hears
// silence for several seconds) until machine detection completes. Nothing
// branches on AnsweredBy, so run detection asynchronously and start the
// dispatch TwiML the moment the call is answered.
options.AsyncAmd = "true";
//options.IfMachine = "Continue";

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 Bug high

Missing AsyncAmdStatusCallback causes CallResource.CreateAsync to throw an ApiException (HTTP 400), resulting in silent failures for all outbound voice dispatch calls. Set options.AsyncAmdStatusCallback to a valid webhook URL, or remove both AsyncAmd and MachineDetection to start playback immediately.

// Nothing branches on AnsweredBy, so skip machine detection entirely —
// the webhook fires the moment the call is answered, starting dispatch
// playback immediately without the multi-second AMD hold.
// (If async AMD is still desired, AsyncAmdStatusCallback is required by
// Twilio: options.AsyncAmdStatusCallback = new Uri(...);)
Prompt for LLM

File Providers/Resgrid.Providers.Number/OutboundVoiceProvider.cs:

Line 40 to 46:

Missing `AsyncAmdStatusCallback` causes `CallResource.CreateAsync` to throw an ApiException (HTTP 400), resulting in silent failures for all outbound voice dispatch calls. Set `options.AsyncAmdStatusCallback` to a valid webhook URL, or remove both `AsyncAmd` and `MachineDetection` to start playback immediately.

Suggested Code:

// Nothing branches on AnsweredBy, so skip machine detection entirely —
// the webhook fires the moment the call is answered, starting dispatch
// playback immediately without the multi-second AMD hold.
// (If async AMD is still desired, AsyncAmdStatusCallback is required by
// Twilio: options.AsyncAmdStatusCallback = new Uri(...);)

Talk to Kody by mentioning @kody

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

// silence for several seconds) until machine detection completes. Nothing
// branches on AnsweredBy, so run detection asynchronously and start the
// dispatch TwiML the moment the call is answered.
options.AsyncAmd = "true";

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 low

Magic string vulnerability identified for the Twilio AsyncAmd option configuration. String literals representing finite sets hurt maintainability and are easily mistyped. Extract to a named constant like private const string TwilioAsyncAmdEnabled = "true"; or use the Twilio SDK's typed enum.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File Providers/Resgrid.Providers.Number/OutboundVoiceProvider.cs:

Line 45:

Magic string vulnerability identified for the Twilio `AsyncAmd` option configuration. String literals representing finite sets hurt maintainability and are easily mistyped. Extract to a named constant like `private const string TwilioAsyncAmdEnabled = "true";` or use the Twilio SDK's typed enum.

Talk to Kody by mentioning @kody

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

}

[Test]
public async Task check_health_should_report_healthy_when_all_probes_pass()

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 low

Naming convention violation identified in test methods. Rule 7 requires PascalCase for .NET methods instead of snake_case. Rename the method to CheckHealthShouldReportHealthyWhenAllProbesPass or use [TestCase] display names.

Kody rule violation: Use proper naming conventions

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/HealthChecksTests.cs:

Line 57:

Naming convention violation identified in test methods. Rule 7 requires PascalCase for .NET methods instead of snake_case. Rename the method to `CheckHealthShouldReportHealthyWhenAllProbesPass` or use `[TestCase]` display names.

Talk to Kody by mentioning @kody

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

.ContinueWith(t =>
{
if (t.IsFaulted && t.Exception != null)
Logging.LogException(t.Exception);

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

Missing structured logging context identified in the faulted background TTS pre-warm task. Logging only the exception object prevents operators from correlating failures to specific calls or departments. Pass structured context alongside the exception, such as Logging.LogException(t.Exception, new { op = "PreWarmPrompt", departmentId = call.DepartmentId, callId = call.CallId });.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/TwilioController.cs:

Line 764:

Missing structured logging context identified in the faulted background TTS pre-warm task. Logging only the exception object prevents operators from correlating failures to specific calls or departments. Pass structured context alongside the exception, such as `Logging.LogException(t.Exception, new { op = "PreWarmPrompt", departmentId = call.DepartmentId, callId = call.CallId });`.

Talk to Kody by mentioning @kody

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

if (points != null && points.Length == 2)
{
callText.Append(_geoLocationProvider.GetAproxAddressFromLatLong(double.Parse(points[0]), double.Parse(points[1])) + Environment.NewLine);
callText.Append(await _geoLocationProvider.GetAproxAddressFromLatLong(double.Parse(points[0]), double.Parse(points[1]))

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

Unvalidated external input parsing identified. Using double.Parse on GeoLocationData values throws a FormatException on malformed coordinates. Replace with double.TryParse(points[0], out var lat) && double.TryParse(points[1], out var lng) to avoid exception-driven control flow.

Kody rule violation: Use TryParse for string conversions

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/TwilioController.cs:

Line 598:

Unvalidated external input parsing identified. Using `double.Parse` on `GeoLocationData` values throws a `FormatException` on malformed coordinates. Replace with `double.TryParse(points[0], out var lat) && double.TryParse(points[1], out var lng)` to avoid exception-driven control flow.

Talk to Kody by mentioning @kody

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

{
await _twilioVoiceResponseService.AppendPromptAsync(response, text, GetTtsPromptBudgetToken(), ttsLanguage);
}
catch (OperationCanceledException) when (TtsBudgetExpired)

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 low

Unsafe type casting identified. Use the as operator or pattern matching for safe casts and guard null results before usage.

Kody rule violation: Use safe type casting with as operator

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/TwilioController.cs:

Line 1353:

Unsafe type casting identified. Use the `as` operator or pattern matching for safe casts and guard null results before usage.

Talk to Kody by mentioning @kody

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

{
callText.Append(_geoLocationProvider.GetAproxAddressFromLatLong(double.Parse(points[0]), double.Parse(points[1])) + Environment.NewLine);
callText.Append(await _geoLocationProvider.GetAproxAddressFromLatLong(double.Parse(points[0]), double.Parse(points[1]))
.WaitAsync(TimeSpan.FromSeconds(2), HttpContext?.RequestAborted ?? CancellationToken.None) + Environment.NewLine);

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 low

Magic number identified for the 2-second geocoder timeout. Inline literals scattered across call sites easily drift from extracted timeout budgets like TtsPromptBudget. Define a private static readonly TimeSpan GeocodeTimeout = TimeSpan.FromSeconds(2); and reference it in ProcessTextCommandsAsync and ResolveCallAddressAsync.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/TwilioController.cs:

Line 599:

Magic number identified for the 2-second geocoder timeout. Inline literals scattered across call sites easily drift from extracted timeout budgets like `TtsPromptBudget`. Define a `private static readonly TimeSpan GeocodeTimeout = TimeSpan.FromSeconds(2);` and reference it in `ProcessTextCommandsAsync` and `ResolveCallAddressAsync`.

Talk to Kody by mentioning @kody

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


try
{
var client = _httpClientFactory.CreateClient("ByPassSSLHttpClient");

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 critical

Transport Layer Security (TLS) bypass vulnerability identified by instantiating ByPassSSLHttpClient. Skipping certificate validation allows attackers to execute Man-in-the-Middle (MITM) attacks and intercept health traffic. Enforce TLS 1.2+ with valid cert validation, or install the internal CA in the trust store instead of bypassing validation.

Kody rule violation: Verify SSL/TLS Server Certificates

Prompt for LLM

File Web/Resgrid.Web.Services/Health/TtsServiceHealthCheck.cs:

Line 37:

Transport Layer Security (TLS) bypass vulnerability identified by instantiating `ByPassSSLHttpClient`. Skipping certificate validation allows attackers to execute Man-in-the-Middle (MITM) attacks and intercept health traffic. Enforce TLS 1.2+ with valid cert validation, or install the internal CA in the trust store instead of bypassing validation.

Talk to Kody by mentioning @kody

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

// Point k8s liveness probes here.
endpoints.MapHealthChecks("/health", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
{
Predicate = registration => !registration.Tags.Contains("full"),

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 low

Fragile compiler-invisible coupling identified via the duplicated string literal "full" in the liveness predicate and health-check registration. Centralize the tag in a constant such as internal static class HealthCheckTags { public const string Full = "full"; } and reference it in both locations.

Kody rule violation: Centralize string constants

Prompt for LLM

File Web/Resgrid.Web.Services/Startup.cs:

Line 747:

Fragile compiler-invisible coupling identified via the duplicated string literal "full" in the liveness predicate and health-check registration. Centralize the tag in a constant such as `internal static class HealthCheckTags { public const string Full = "full"; }` and reference it in both locations.

Talk to Kody by mentioning @kody

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

}
}

public void AppendSayFallback(Gather gather, string text)

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 low

Code duplication identified in the AppendSayFallback method body, which verbatim copies logic from line 59. Consolidate into a single overload accepting the common base type: public void AppendSayFallback(TwiML parent, string text).

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Web/Resgrid.Web.Services/Twilio/TwilioVoiceResponseService.cs:

Line 73:

Code duplication identified in the `AppendSayFallback` method body, which verbatim copies logic from line 59. Consolidate into a single overload accepting the common base type: `public void AppendSayFallback(TwiML parent, string text)`.

Talk to Kody by mentioning @kody

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

private readonly IStorageService _storageService;
private readonly IAudioProcessingService _audioProcessingService;
private readonly TtsOptions _options;
private readonly SemaphoreSlim _probeLock = new(1, 1);

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

Resource leak identified due to undisposed SemaphoreSlim instance _probeLock. TtsFullHealthCheck does not implement IDisposable, leaking the internal wait handle. Implement IDisposable on TtsFullHealthCheck to dispose _probeLock, or register the class as a singleton managed by the DI container.

Kody rule violation: Use using statements for disposable resources

Prompt for LLM

File Web/Resgrid.Web.Tts/Health/TtsFullHealthCheck.cs:

Line 27:

Resource leak identified due to undisposed `SemaphoreSlim` instance `_probeLock`. `TtsFullHealthCheck` does not implement `IDisposable`, leaking the internal wait handle. Implement `IDisposable` on `TtsFullHealthCheck` to dispose `_probeLock`, or register the class as a singleton managed by the DI container.

Talk to Kody by mentioning @kody

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

{
var pattern = $@"\b{Regex.Escape(kvp.Key)}\b";
text = Regex.Replace(text, pattern, kvp.Value, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
text = Regex.Replace(text, pattern, kvp.Value, RegexOptions.CultureInvariant);

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

Regular expression Denial-of-Service (DoS) vulnerability identified. Without a timeout, regex processing on untrusted input can be exploited, so always define a timeout when using regex.

Kody rule violation: Specify Timeout for Regular Expressions

Prompt for LLM

File Web/Resgrid.Web.Tts/Services/TextPreprocessor.cs:

Line 256:

Regular expression Denial-of-Service (DoS) vulnerability identified. Without a timeout, regex processing on untrusted input can be exploited, so always define a timeout when using regex.

Talk to Kody by mentioning @kody

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

@Resgrid-Bot

Resgrid-Bot commented Jul 30, 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.

Comment on lines +164 to +172
private static HttpRequest CreateRequest(string headerValue = null)
{
var context = new DefaultHttpContext();

if (headerValue != null)
context.Request.Headers[FullHealthCheckAccess.HeaderName] = headerValue;

return context.Request;
}

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 low

Duplicated logic in the CreateRequest helper method copies verbatim into TtsHealthCheckAccessTests, with only HeaderName differing. Move this logic to a shared base class or static helper that accepts the header name to eliminate the copy.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/HealthChecksTests.cs:

Line 164 to 172:

Duplicated logic in the `CreateRequest` helper method copies verbatim into `TtsHealthCheckAccessTests`, with only `HeaderName` differing. Move this logic to a shared base class or static helper that accepts the header name to eliminate the copy.

Talk to Kody by mentioning @kody

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

// (SystemBehaviorConfig.FullHealthCheckKey); an unconfigured key fails closed.
// The shallow /health endpoint stays public for the k8s probes.
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/health/full", StringComparison.OrdinalIgnoreCase),

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 low

Duplicate hardcoded string literal for the route path "/health/full" in the new UseWhen gate diverges from the literal used in app.MapHealthChecks("/health/full", ...) on line 162, risking a silently broken authorization gate if the route changes. Extract a constant such as private const string FullHealthRoute = "/health/full"; and reference it in both StartsWithSegments and MapHealthChecks.

Kody rule violation: Centralize string constants

Prompt for LLM

File Web/Resgrid.Web.Tts/Program.cs:

Line 139:

Duplicate hardcoded string literal for the route path "/health/full" in the new `UseWhen` gate diverges from the literal used in `app.MapHealthChecks("/health/full", ...)` on line 162, risking a silently broken authorization gate if the route changes. Extract a constant such as `private const string FullHealthRoute = "/health/full";` and reference it in both `StartsWithSegments` and `MapHealthChecks`.

Talk to Kody by mentioning @kody

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

@ucswift

ucswift commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions 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.

This PR is approved.

@ucswift
ucswift merged commit 63f697b into master Jul 30, 2026
18 of 19 checks passed
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