Skip to content

Releases: Resgrid/Core

4.557.0

Choose a tag to compare

@github-actions github-actions released this 17 Jul 06:10
d6a062c

This pull request significantly enhances the Resgrid Chatbot's natural language understanding, help system, and SMS interaction model, making it more intuitive for first responders who interact primarily via text messages.

Key Changes

Smarter Intent Recognition (NLU)

  • Added responder shorthand for setting status (e.g., "omw", "on my way", "enroute" → responding) and for responding to calls (e.g., "omw to fire", "headed to Main St")
  • Added call number format support (e.g., "26-1" / "C26-1") alongside raw call IDs
  • Added aliases like "my staffing", "cal", and SMS-style "SWITCH" commands
  • Added trailing punctuation stripping so commands like "My status?" and "omw to 26-1." are recognized correctly
  • Restricted STOP to only "stop" and "unsubscribe" — removed "end", "quit", and "cancel" which carry other meanings in conversation flows

Flexible Call Reference Resolution

  • Introduced a centralized CallReferenceResolver that resolves references by raw ID, call number (year-sequence format), or shorthand term (e.g., "fire") matched against active calls' name, type, and nature. Department-level isolation (anti-IDOR) is enforced throughout.

Two-Level Help System

  • Restructured HelpActionHandler so bare "HELP" returns a concise topic menu (to minimize SMS segments/costs), and "HELP " returns detailed commands for that specific area. Status and staffing topics reflect each department's custom states.

STOP as Telecom Opt-Out

  • STOP now disables all outbound text messages at the profile level (matching the legacy text-command behavior) rather than ending a chatbot session. It works even for unauthorized/restricted users, since opt-out is a regulatory requirement on SMS platforms.

Interactive SMS Length Cap

  • Added a dedicated SmsReplyMaxLength (1500 chars) for chatbot replies, which are interactive content users act on over SMS — higher than notification defaults but within carrier/Twilio delivery limits.

Updated Tests & Localization

  • Adjusted test assertions for the new "No active call found matching" cross-tenant response and added localized Call_NoMatch resources across all supported languages.

4.553.0

Choose a tag to compare

@github-actions github-actions released this 17 Jul 02:36
3cb3086

Summary

This PR introduces several fixes and improvements to the chatbot/SMS text command system, primarily around department switching and unverified mobile number handling.

Key Changes

1. Department Switching via SMS ("SWITCH" command)

  • Added a new SwitchDepartment text command type that allows users to change their active department by texting SWITCH <number or name>
  • Implemented in both TwilioController and SignalWireController with parity: users can switch by list number, department ID, name, code, or partial name match
  • The text command parser now checks for SWITCH before custom staffing commands to prevent conflicts

2. Restricted Mode for Unsupported Departments

  • Previously, if a user's active department was on a plan that doesn't support chatbot/SMS features, they were hard-blocked with an error
  • Now, if the user belongs to other departments that DO support these features, they enter a "restricted mode" where only department list, switch, and active department queries are honored — guiding them to switch to a supported department
  • Only when no supported alternatives exist does the user receive the upgrade prompt

3. Unverified Mobile Number Handling

  • Changed the security model for unverified mobile numbers: they are now used for routing (department resolution, plan gating) but never for actions
  • Unverified senders receive a clear prompt to verify their number instead of being silently ignored or receiving "unknown command" responses
  • STOP/opt-out always works regardless of verification status

4. No More Silent Department Auto-Picking

  • GetActiveSmsDepartmentForUserAsync no longer silently auto-switches users to an SMS-capable department; it respects the user's actual active department and lets downstream logic handle unsupported plans

5. Centralized SMS-Supported Department List

  • Created a shared GetSmsSupportedMembershipsForUserAsync method to ensure consistent ordering and filtering across all surfaces (chatbot, Twilio, SignalWire) so numeric picks remain stable across stateless SMS requests

6. Subscription View Fix

  • Fixed indentation issues in the Subscription page Razor view and corrected a Stripe JavaScript initialization syntax (<text> to @:)

4.550.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 15:51
e11d6bb

Summary

RG-T117: Chatbot Security Hardening — Step-up PIN Authentication and SMS Identity Verification

This pull request introduces two major security improvements to the chatbot and SMS integration:

1. Security PIN (Step-up Authentication for Destructive Actions)

Adds a new 4-digit security PIN system that acts as a second factor for confirmed destructive or department-wide chatbot/text actions:

  • Per-user opt-in: Users can enable a security PIN on their profile page and set or regenerate their own PIN.
  • Department-level enforcement: A new department setting (ForceChatbotSecurityPin) forces all members to use a PIN. When enabled, every member without a PIN automatically receives a randomly generated one.
  • Chatbot flow: After a user replies YES to confirm an action, if a PIN is required, the session enters an AwaitingPin state. The pending intent only executes after the correct PIN is supplied. After 3 failed attempts, the action is cancelled.
  • PIN security rules: PINs must be exactly 4 digits and are rejected if weak (all-same-digit or consecutive sequences). PINs are generated using a crypto RNG and stored AES-encrypted on the user profile.
  • UI: Department settings page has a new checkbox; user profile page (own profile only) shows PIN input and opt-in toggle.

2. SMS Identity Verification Hardening

Tightens trust rules so an unverified mobile number can never identify or act as a Resgrid user:

  • Existing links re-validated: On every inbound SMS, existing chatbot identity links are only trusted if the linked profile's mobile number is verified AND still matches the sender's number. Stale or unverified SMS links are removed.
  • Removed phone-link confirmation flow: The previous "Reply YES to link" confirmation is replaced by a stricter rule — only a verified mobile number may be auto-linked. Unverified numbers receive a message directing the user to verify their number on their profile.
  • Number change invalidates links: When a user changes their mobile number, all SMS-based chatbot identity links are automatically removed.
  • Controller-level enforcement: Both the Twilio and SignalWire inbound SMS controllers now reject unverified matched profiles and respond with an appropriate verification prompt.
  • Profile save protection: The SecurityPin field is preserved when a caller doesn't supply it (null = unchanged), preventing accidental PIN deletion by unrelated profile saves.

Database

  • Migration M0089 (SQL Server and PostgreSQL) adds SecurityPin (nullable string, encrypted) and SecurityPinEnabled (boolean, default false) columns to UserProfiles.

Tests

  • Added unit tests for SecurityPinUtility (format validation, weakness detection, generation) and SecurityPinService (PIN requirement logic, validation, and generation/ensuring flows).

4.547.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 04:21
efd0bca

PR Description: RG-T125 Chatbot fix

This pull request fixes an issue with the chatbot messaging functionality by addressing a protobuf serialization defect and adding fail-fast validation for queue message types.

Changes

  1. Added missing [ProtoContract] attribute to ChatbotMessageQueueItem

    • The class was already decorated with [ProtoMember] attributes but was missing the required [ProtoContract] attribute at the class level. Without it, protobuf-net cannot properly serialize instances of this type, causing failures when chatbot messages are published to the RabbitMQ queue.
  2. Extended the Protobuf serializer warmup to cover all queue payload types

    • SerializerHelper.WarmUpProtobufSerializer() now prepares serializers for every payload type published to RabbitMQ (e.g., CallQueueItem, MessageQueueItem, ShiftQueueItem, CqrsEvent, AuditEvent, etc.).
    • This builds the protobuf contracts at application startup, ensuring that any type missing the [ProtoContract] attribute fails immediately on boot rather than surfacing as a runtime error the first time a message of that type is enqueued.

Functional Impact

Chatbot messages will now serialize correctly for queue transport, and the application will detect missing protobuf contract attributes early during startup, preventing similar serialization failures across other message types.

4.545.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 02:41
79a1656

Summary

This PR fixes the Docker build process for the Resgrid TTS (Text-to-Speech) web service by improving build reliability, error handling, and reproducibility.

Key Changes

  1. Pinned Piper voices repository revision: Added a new build argument PIPER_VOICES_REVISION that locks the Hugging Face Piper voices download to a specific git commit, ensuring reproducible builds instead of always pulling from the main branch (which could introduce breaking changes).

  2. Improved error handling: Split the single large RUN command into multiple separate RUN steps, each using set -eu to ensure the build fails immediately on any error or unset variable, rather than silently continuing.

  3. Enhanced download reliability: Increased curl retry attempts from 3 to 5 and added the --retry-all-errors flag so that all types of download errors (not just transient ones) are retried.

  4. Better build logging: Added echo statements to provide visibility into download progress (FFmpeg, Piper binary, and individual Piper voice files) during the Docker build.

4.539.0

Choose a tag to compare

@github-actions github-actions released this 14 Jul 23:13
540c023

Here is the analysis of the code changes and the generated Summary:

Summary

This pull request implements RG-T125 (IC bug fixes and weather alert fixes). The work is focused on improving the Incident Command (IC) module (specifically the Command Board and Command Definition Templates) and enhancing weather alert notifications.

Key Changes:

  1. Incident Command (IC) Improvements:

    • Command Definition Board (Templates): Fixed several issues related to the creation and management of command board templates (templates for the default incident command board layout). This includes ensuring that templates, roles, and lanes (assignments on the board) are properly persisted, retrieved, and linked.
    • Lane Requirements: Implemented advisory and forced lane requirements (via ForceRequirements). When enabled, the system can now block assigning a resource (personnel/unit) to a lane on the command board if they do not meet the template-defined lane requirements (e.g., required personnel roles or unit types). This ensures only qualified resources are placed in specific lanes on the default board templates.
    • Lane Assignment: Implemented the logic to correctly assign, move, and remove resources (personnel and units) to lanes on the command board. This includes ensuring proper relationships and preventing assignment when forced lane requirements are not met.
    • Lane Definition Persistence: The system now correctly saves and retrieves the lane requirements (roles, unit types) when a user creates or edits a command board template.
    • Command Board View: Fixed the view of the command board to correctly display the lanes, assignments, and requirements.
  2. Weather Alert Fixes:

    • Fixed the creation and sending of weather alert notifications. Weather alerts are now properly categorized with a new WeatherAlert message type and include a custom PushSubTitle (based on the alert headline) for better user visibility.
    • Added new exception handling (CommandRequirementsNotMetException) to ensure the system gracefully handles and reports errors when a user attempts to assign a resource that does not meet the forced lane requirements.
  3. Database and Infrastructure:

    • Added necessary database migrations to support the new lane requirements and exception handling.
    • Implemented unit tests and integration tests to cover the new command board templates and lane assignment functionality.

4.531.0

Choose a tag to compare

@github-actions github-actions released this 30 Jun 00:18
ec4c6bf

Description

This PR fixes bugs in the voice dispatch TTS (text-to-speech) path where long dispatch text exceeding the TTS chunk limit would cause ArgumentException failures, breaking voice dispatch playback (Sentry: RESGRID-API-78).

Changes

  • Pre-warm path (PreWarmPromptAsync): Previously threw ArgumentException for any input spanning more than one TTS chunk. It now iterates over all chunks and pre-warms each individually, so long dispatch text no longer faults the pre-warm/redirect flow.

  • Playback path (TryAppendDispatchPlaybackAsync in TwilioController): Switched from GetPromptUrlAsync (single-chunk only) to the multi-chunk-aware AppendPromptAsync, which emits one <Play> element per chunk. This prevents failures when dispatch text (e.g., long notes or addresses) exceeds the chunk limit.

  • Tests: Added two regression tests verifying that PreWarmPromptAsync no longer throws for multi-chunk text and that AppendPromptAsync produces a <Play> per chunk for multi-chunk input.

4.529.0

Choose a tag to compare

@github-actions github-actions released this 29 Jun 22:36
8f712f5

Pull Request Description

This PR fixes a SQL Server compatibility issue with the Resgrid Workers that occurs on .NET 8+ (the project now targets .NET 9.0).

Problem

The external Quidjibo.SqlServer NuGet package (v0.6.0) uses the legacy System.Data.SqlClient, which throws a SqlGuidCaster TypeLoadException on .NET 8 and above, breaking the worker's ability to use SQL Server as its background job store.

Changes

  1. Vendored the Quidjibo.SqlServer provider locally: Replaced the NuGet package reference with a new in-repository Quidjibo.SqlServer project that uses Microsoft.Data.SqlClient instead. This project includes:

    • Configuration, factory, and provider implementations for Work, Schedule, and Progress job storage
    • All required SQL scripts (schema setup, work receive/complete/fault/send, schedule create/receive/complete, progress tracking)
    • Utility classes for SQL execution and embedded script loading
  2. Fixed semaphore lock patterns in the Postgres provider factories: Moved SyncLock.WaitAsync() calls outside of their try blocks in the Progress, Schedule, and Work provider factories. This prevents a scenario where a cancellation or exception during WaitAsync would cause the finally block to incorrectly call Release(), leading to a SemaphoreFullException.

4.526.0

Choose a tag to compare

@github-actions github-actions released this 29 Jun 18:50
1447988

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).

4.521.0

Choose a tag to compare

@github-actions github-actions released this 26 Jun 03:25
cc0990c

Pull Request Description

This PR delivers the backend foundation for offline-first Incident Command (IC) plus several SMS deliverability, phone-number validation, and data-truncation fixes.

1. Offline-First Sync Foundation (RIC-T39)

Establishes the server-side primitives the mobile apps need to work fully offline and reconcile on reconnect (documented in the new docs/architecture/offline-first-architecture.md):

  • Change tracking: adds a ModifiedOn cursor (and a DeletedOn soft-delete tombstone for lanes) to all mutable incident-command entities via the new IChangeTracked interface. Every insert/update stamps it through the new Touch() / UpsertOwnedAsync() helpers.
  • Delta sync endpoint: new GET /api/v4/Sync/Changes?since= returns every changed row (including soft-deleted/closed/released) for the caller's department, with a ServerTimestampMs cursor for the next pull.
  • Idempotent creates: create paths now honor a client-supplied GUID PK and upsert by existence (not by id-presence), so an offline-created row replays without duplicating; foreign-department rows are rejected.
  • Idempotency keys for check-ins: CheckInRecord.IdempotencyKey (with a filtered unique index) dedups replayed offline check-ins; a post-violation recovery adopts the winning row instead of erroring.
  • Soft-delete for lanes: DeleteNodeAsync now sets a tombstone instead of hard-deleting, so removals propagate on delta sync.
  • DB migrations M0081–M0083 (SQL Server + PostgreSQL twins) add the columns/indexes.

2. SMS Deliverability & Cost

New SmsContentHelper applied at the single chokepoint (and the workflow SMS executor) before sending:

  • Strips non-allow-listed URLs (A2P 10DLC carrier filtering) while preserving Resgrid/map domains and bit.ly.
  • Normalizes smart punctuation to keep messages in cheaper GSM-7 encoding.
  • Truncates to a configurable max length (avoids Twilio error 21617).
  • Invalid/unreachable "To" numbers from Twilio are now logged quietly instead of surfacing as fatal errors.
  • Adds configurable SmsMaxLength and SmsAllowedUrlDomains settings.

3. Phone-Number Validation & E.164 Normalization

  • New PhoneRegionHelper maps country names to ISO region codes so national-format numbers can be recognized.
  • ContactVerificationService now validates/normalizes to E.164 before sending SMS or voice calls (prevents Twilio "Invalid 'To'" failures).
  • Profile editing validates and normalizes mobile/home numbers server-side, with a new AJAX ValidatePhoneNumber endpoint and a shared client-side resgrid.phonevalidate.js that offers a one-click canonical fix on the profile, contact, and personnel forms.

4. Data-Truncation Fixes

  • Addresses: widens Addresses columns (street to max, others generously) and tightens model-level StringLength validation across all address-entry view models so client + server reject oversized values before save. M0085 (both DBs).
  • Autofills.Data: widened from nvarchar(255) to max to prevent truncation of long call-note templates. M0084 (both DBs).

5. Other Fixes

  • HealthRepository: now uses the configured connection provider and CURRENT_TIMESTAMP so the health check works on PostgreSQL-backed datacenters (was hardcoded to SQL Server + GETDATE()).
  • Contacts Index view: corrected a model-indexer bug (Model.Contacts[i]Model.ContactCategories[i]).
  • Switched append-only timeline/transfer inserts from SaveOrUpdateAsync to explicit InsertAsync (pre-set GUIDs would otherwise cause silent 0-row updates).

Tests

Adds comprehensive coverage for idempotent check-ins (incl. concurrent-replay race), offline-aware IC service behavior (change tracking, idempotent creates, soft-delete, delta pull), ad-hoc resource idempotency/delta, SmsContentHelper, and PhoneRegionHelper.