Skip to content

Releases: Resgrid/Core

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.

4.516.0

Choose a tag to compare

@github-actions github-actions released this 24 Jun 15:13
01beb00
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

4.513.0

Choose a tag to compare

@github-actions github-actions released this 23 Jun 14:50
848f34a
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

4.509.0

Choose a tag to compare

@github-actions github-actions released this 22 Jun 21:23
27a0c4f
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

4.507.0

Choose a tag to compare

@github-actions github-actions released this 22 Jun 18:43
e348678
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

4.504.0

Choose a tag to compare

@github-actions github-actions released this 21 Jun 15:34
0ea0d7e
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

4.501.0

Choose a tag to compare

@github-actions github-actions released this 20 Jun 15:41
aacc1c4
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->