Releases: Resgrid/Core
Release list
4.531.0
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 threwArgumentExceptionfor 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 (
TryAppendDispatchPlaybackAsyncinTwilioController): Switched fromGetPromptUrlAsync(single-chunk only) to the multi-chunk-awareAppendPromptAsync, 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
PreWarmPromptAsyncno longer throws for multi-chunk text and thatAppendPromptAsyncproduces a<Play>per chunk for multi-chunk input.
4.529.0
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
-
Vendored the Quidjibo.SqlServer provider locally: Replaced the NuGet package reference with a new in-repository
Quidjibo.SqlServerproject that usesMicrosoft.Data.SqlClientinstead. 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
-
Fixed semaphore lock patterns in the Postgres provider factories: Moved
SyncLock.WaitAsync()calls outside of theirtryblocks in the Progress, Schedule, and Work provider factories. This prevents a scenario where a cancellation or exception duringWaitAsyncwould cause thefinallyblock to incorrectly callRelease(), leading to aSemaphoreFullException.
4.526.0
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.GetBundleForDepartmentAsyncscans each board table once for the whole department and groups byCallIdin 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=falselets 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
SyncServiceaggregates data from 12 existing services into a singleSyncReferenceDatapayload. - Personnel and groups are projected to safe DTOs (
ReferencePersonnel,ReferenceGroup) that structurally excludeIdentityUsernavs, password/security fields, andUserProfilecontact-verification secrets. - Department-scoped cache-aside (5-minute TTL) via a protobuf-safe JSON envelope (
ReferenceCacheEnvelope);?bypassCache=trueforces 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
callIdroute value,IncidentCommandId(resolved to its Call with department-ownership verification), orCallIdon 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, andIncidentVoiceController. - 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.mdwith the authoritative shift-start manifest (§6.1) and marked the delta and aggregate endpoints as done (§9).
4.521.0
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
ModifiedOncursor (and aDeletedOnsoft-delete tombstone for lanes) to all mutable incident-command entities via the newIChangeTrackedinterface. Every insert/update stamps it through the newTouch()/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 aServerTimestampMscursor 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:
DeleteNodeAsyncnow 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
SmsMaxLengthandSmsAllowedUrlDomainssettings.
3. Phone-Number Validation & E.164 Normalization
- New
PhoneRegionHelpermaps country names to ISO region codes so national-format numbers can be recognized. ContactVerificationServicenow 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
ValidatePhoneNumberendpoint and a shared client-sideresgrid.phonevalidate.jsthat offers a one-click canonical fix on the profile, contact, and personnel forms.
4. Data-Truncation Fixes
- Addresses: widens
Addressescolumns (street to max, others generously) and tightens model-levelStringLengthvalidation 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_TIMESTAMPso 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
SaveOrUpdateAsyncto explicitInsertAsync(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.
4.516.0
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
4.513.0
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
4.509.0
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
4.507.0
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
4.504.0
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
4.501.0
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->