Skip to content

RG-T55 RMS RMS hardening scope and prevention and investigations - #501

Merged
ucswift merged 1 commit into
masterfrom
develop
Sep 8, 2026
Merged

ucswift merged 1 commit into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

This pull request adds a major new RMS prevention and investigations foundation, expands Contacts with structured pre-plans and site files, hardens external records connectors, and improves release safety/telemetry for Records.

What changed

Contacts now support structured pre-plans, hazards, and site files

  • Added structured pre-incident plans for contacts, including occupancy, utilities, access, water supply, on-site contacts, hazards/tactics, and review cycle fields.
  • Added repeatable premise hazards with severity, alerting, and optional GPS coordinates.
  • Added contact file attachments for site documents such as pre-plans, floor plans, site photos, and drawings.
  • Added auditing for:
    • pre-plan create/update/delete
    • contact attachment add/remove
  • Added UI/API support to:
    • view, create, edit, and delete pre-plans
    • manage hazards
    • upload, list, download, and delete contact files

Call site information now surfaces linked contact/site context

  • Calls can now link a primary contact plus additional contacts.
  • Added call-level site info aggregation that returns:
    • linked contacts
    • pre-plans
    • hazards
    • live alert notes
    • site file metadata
  • Added lightweight call contact summaries for call lists, including indicators for:
    • whether a contact has a pre-plan
    • alert note count
    • hazard count
  • Exposed this in:
    • v4 Calls API
    • dispatch call view
    • active call/get call payloads

RMS prevention modules were introduced

Added new Records modules and service layers for prevention and related workflows, behind feature flags:

  • Occupancies

    • occupancy/property master
    • role-based contact links
    • occupancy hazards
    • provenance tracking
    • crosswalk/reconciliation from Contacts pre-plans, contacts, and POIs
    • write-ownership switch from Contacts to Records
  • Inspections

    • code sets and code sections
    • inspection programs and checklists
    • inspections and reinspections
    • violations
    • due inspection generation
    • overdue violation workflow trigger
  • Hydrants

    • hydrants/water sources
    • flow tests
    • maintenance logs
    • in/out-of-service state
    • CSV import
    • map layer and nearest-hydrant lookup
  • Permits

    • permit types
    • permits
    • plan reviews
    • fee recording
    • expiration sweep
    • expiring-permit workflow trigger
  • Community Risk Reduction

    • CRR activity tracking
    • summary reporting
  • Investigations

    • restricted case-based investigations
    • members and roles
    • linked incidents with pinned revisions
    • notes
    • evidence
    • append-only chain of custody
    • referrals
    • findings approval/return flow
    • export and access audit
  • Quality Review

    • post-finalization rubric-based review
    • deterministic sampling
    • scoring and trends

New permissions and feature flags

  • Added a new permission: Manage Prevention Data.
  • Added feature flags for:
    • occupancies
    • inspections
    • hydrants
    • permits
    • CRR
    • investigations
    • quality review

Protected data coverage was expanded

  • Added protected data catalog coverage for:
    • contact pre-plans
    • pre-plan hazards
    • contact attachments
    • RMS prevention records
    • investigations
    • quality review
    • prevention attachments
  • Added read/write protection handling for these new entities.

Records release telemetry and health were added

  • Added release telemetry for Records, including counts for:
    • legacy write attempts
    • authorization denials
    • outbox lag/failures
    • workflow runs
    • attachment scan state
    • created/finalized/overdue records
    • submissions
    • prevention counters
    • protection/search status
  • Added prevention summary reporting.
  • Added worker integration so prevention sweeps run alongside due-state processing.

Record definition cardinality is now configurable and enforced

  • Added per-definition cardinality rules for how many records can exist per call.
  • Added validation and UI/API exposure for cardinality.
  • Enforced uniqueness at create time and database level.
  • Released slots when records are voided or cancelled.
  • Applied specific template cardinality to incident support forms that should only exist once per subject/call.

Connector hardening and concurrency protections

  • Added outbound connector restrictions for feed destinations:
    • allowed host allowlist
    • blocking private/internal network destinations by default
    • DNS resolution re-check before connect
    • no automatic redirect following
  • Added concurrency claims to prevent multiple connector runs from processing the same connector simultaneously.
  • Added optimistic/concurrency protections for deployment fill transitions.
  • Sanitized external feed contract values shown in validation problems.

Field records resilience improvements

  • Added a new exclusion reason for temporary catalog unavailability.
  • Sync now treats transient catalog failures as retryable instead of forcing clients to reset cached data.

Functional impact

These changes introduce the core prevention/investigations RMS surface, let departments manage structured site pre-plan data directly on contacts, expose richer site context during dispatch, strengthen outbound connector safety, and improve operational safety through telemetry, concurrency guards, and retry-friendly failure handling.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 170 files, which is 70 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: eb99d9aa-c94a-4489-b80d-a873c3a91e70

📥 Commits

Reviewing files that changed from the base of the PR and between 58236c9 and 9e29241.

⛔ Files ignored due to path filters (60)
  • Core/Resgrid.Config/RecordsConnectorConfig.cs is excluded by !**/Core/Resgrid.Config/**
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Records/Records.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.uk.resx is excluded by !**/*.resx
  • Tests/Resgrid.Tests/Allocations/trigger-baseline.json is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Localization/TranslationCompletenessTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/BackOfficeExtensionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/ClaimsAuthorizationHelperParityTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/FakeRmsDefinitionStore.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/FieldRecordCatalogTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/PassthroughRecordsProtection.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordClaimsTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordDeploymentConnectorsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsCutoverServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsFieldRolloutServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsHydrantsAndPermitsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsInspectionsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsInvestigationsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsOccupancyServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsPermissionRowsTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsQualityAndTelemetryTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsRms5WorkflowTriggerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsContainerCompositionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsIdentifierPinTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsProtectedFieldsCatalogTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/AdpSizingServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/ContactsServicePreplanTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/ProtectedReadServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/RemainingCandidateProtectionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/CallsControllerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/User/ProtectedRevealAuthorizationTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (170)
  • Core/Resgrid.Model/AuditLogTypes.cs
  • Core/Resgrid.Model/CallSiteInfo.cs
  • Core/Resgrid.Model/ContactAttachment.cs
  • Core/Resgrid.Model/ContactAttachmentTypes.cs
  • Core/Resgrid.Model/ContactPreplan.cs
  • Core/Resgrid.Model/ContactPreplanEnums.cs
  • Core/Resgrid.Model/ContactPreplanHazard.cs
  • Core/Resgrid.Model/FeatureFlagKeys.cs
  • Core/Resgrid.Model/PermissionTypes.cs
  • Core/Resgrid.Model/Records/ExternalOrderFeed.cs
  • Core/Resgrid.Model/Records/FieldRecordsContracts.cs
  • Core/Resgrid.Model/Records/OccupancyDispatchProjectionV1.cs
  • Core/Resgrid.Model/Records/RecordCardinalityException.cs
  • Core/Resgrid.Model/Records/RecordPermissionCatalog.cs
  • Core/Resgrid.Model/Records/RecordsPreventionContracts.cs
  • Core/Resgrid.Model/Records/RecordsReleaseTelemetry.cs
  • Core/Resgrid.Model/Records/RmsInvestigations.cs
  • Core/Resgrid.Model/Records/RmsOccupancy.cs
  • Core/Resgrid.Model/Records/RmsOperationalRecord.cs
  • Core/Resgrid.Model/Records/RmsPrevention.cs
  • Core/Resgrid.Model/Records/RmsProtectedFields.cs
  • Core/Resgrid.Model/Records/RmsQualityReview.cs
  • Core/Resgrid.Model/Records/RmsRecordDefinitions.cs
  • Core/Resgrid.Model/Records/RmsTemplatePacks.cs
  • Core/Resgrid.Model/Repositories/IContactPreplanRepository.cs
  • Core/Resgrid.Model/Repositories/IRmsDefinitionRepositories.cs
  • Core/Resgrid.Model/Repositories/IRmsFieldRolloutRepository.cs
  • Core/Resgrid.Model/Repositories/IRmsPreventionRepositories.cs
  • Core/Resgrid.Model/Repositories/IRmsRepositories.cs
  • Core/Resgrid.Model/Services/ICallsService.cs
  • Core/Resgrid.Model/Services/IContactsService.cs
  • Core/Resgrid.Model/Services/IProtectedReadService.cs
  • Core/Resgrid.Model/Services/IProtectedWriteService.cs
  • Core/Resgrid.Model/Services/IRecordsInvestigationsService.cs
  • Core/Resgrid.Model/Services/IRecordsOccupancyService.cs
  • Core/Resgrid.Model/Services/IRecordsPreventionServices.cs
  • Core/Resgrid.Model/Services/IRecordsProtectionService.cs
  • Core/Resgrid.Model/WorkflowTemplateVariableCatalog.cs
  • Core/Resgrid.Model/WorkflowTriggerEventType.cs
  • Core/Resgrid.Services/AdpTableBindings.cs
  • Core/Resgrid.Services/CallsService.cs
  • Core/Resgrid.Services/ContactsService.cs
  • Core/Resgrid.Services/ProtectedFieldCatalog.cs
  • Core/Resgrid.Services/ProtectedReadService.cs
  • Core/Resgrid.Services/Records/Connectors/ExternalFeedDestination.cs
  • Core/Resgrid.Services/Records/Connectors/ExternalOrderFeedProviders.cs
  • Core/Resgrid.Services/Records/Connectors/RecordDeploymentConnectorsService.cs
  • Core/Resgrid.Services/Records/FieldRecordsService.cs
  • Core/Resgrid.Services/Records/RecordDefinitionsService.cs
  • Core/Resgrid.Services/Records/RecordDeploymentsService.cs
  • Core/Resgrid.Services/Records/RecordTemplateCatalog.IncidentSupport.cs
  • Core/Resgrid.Services/Records/RecordsCrrService.cs
  • Core/Resgrid.Services/Records/RecordsFieldRolloutService.cs
  • Core/Resgrid.Services/Records/RecordsHydrantsService.cs
  • Core/Resgrid.Services/Records/RecordsInspectionsService.cs
  • Core/Resgrid.Services/Records/RecordsInvestigationsService.cs
  • Core/Resgrid.Services/Records/RecordsOccupancyService.cs
  • Core/Resgrid.Services/Records/RecordsPermitsService.cs
  • Core/Resgrid.Services/Records/RecordsPreventionAttachmentsService.cs
  • Core/Resgrid.Services/Records/RecordsPreventionGate.cs
  • Core/Resgrid.Services/Records/RecordsProtectionService.cs
  • Core/Resgrid.Services/Records/RecordsQualityReviewService.cs
  • Core/Resgrid.Services/Records/RecordsReleaseTelemetryService.cs
  • Core/Resgrid.Services/Records/RecordsService.cs
  • Core/Resgrid.Services/ServicesModule.cs
  • Core/Resgrid.Services/WorkflowSampleDataGenerator.cs
  • Core/Resgrid.Services/WorkflowTemplateContextBuilder.cs
  • Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
  • Providers/Resgrid.Providers.Claims/ResgridClaimTypes.cs
  • Providers/Resgrid.Providers.Claims/ResgridResources.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0182_AddRmsRecordCardinality.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0183_AddContactPreplans.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0184_AddContactAttachments.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0185_RmsReleaseReadiness.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0186_AddRmsPreventionAndInvestigations.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0182_AddRmsRecordCardinalityPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0183_AddContactPreplansPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0184_AddContactAttachmentsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0185_RmsReleaseReadinessPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0186_AddRmsPreventionAndInvestigationsPg.cs
  • Repositories/Resgrid.Repositories.DataRepository/ContactPreplanRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsDefinitionRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsFieldRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsPreventionRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs
  • Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ContactFilesController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ContactsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/FieldRecordsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordDeploymentConnectorsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordHydrantsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordInspectionsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordInvestigationsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordOccupanciesController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordPermitsController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordQualityController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/RecordsPreventionApiControllerBase.cs
  • Web/Resgrid.Web.Services/Helpers/ClaimsAuthorizationHelper.cs
  • Web/Resgrid.Web.Services/Helpers/RecordsRms1bApiMapper.cs
  • Web/Resgrid.Web.Services/Models/v4/Calls/CallResult.cs
  • Web/Resgrid.Web.Services/Models/v4/Calls/CallSiteInfoResult.cs
  • Web/Resgrid.Web.Services/Models/v4/Calls/EditCallInput.cs
  • Web/Resgrid.Web.Services/Models/v4/Calls/NewCallInput.cs
  • Web/Resgrid.Web.Services/Models/v4/ContactFiles/ContactFileResult.cs
  • Web/Resgrid.Web.Services/Models/v4/Contacts/ContactHazardsResult.cs
  • Web/Resgrid.Web.Services/Models/v4/Contacts/ContactPreplanResult.cs
  • Web/Resgrid.Web.Services/Models/v4/Records/RecordsRms1bApiModels.cs
  • Web/Resgrid.Web.Services/Models/v4/Records/RecordsRms5ApiModels.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web.Services/Startup.cs
  • Web/Resgrid.Web/Areas/User/Controllers/ContactsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordHydrantsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordInvestigationsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordOccupanciesController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordPermitsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordsPreventionMvcControllerBase.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs
  • Web/Resgrid.Web/Areas/User/Models/Calls/ViewCallView.cs
  • Web/Resgrid.Web/Areas/User/Models/Contacts/ContactPreplanViews.cs
  • Web/Resgrid.Web/Areas/User/Models/Contacts/ContactsIndexView.cs
  • Web/Resgrid.Web/Areas/User/Models/Contacts/ViewContactView.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/RecordDefinitionsViewModels.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/RecordsRms5ViewModels.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/RecordsViewModels.cs
  • Web/Resgrid.Web/Areas/User/Views/Contacts/Attachments.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contacts/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contacts/Preplan.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Contacts/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordCrr/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordCrr/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordDefinitions/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordDeploymentConnectors/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Details.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInspections/CodeSets.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInspections/Details.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInspections/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInspections/Programs.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Custody.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Open.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordOccupancies/Crosswalk.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordOccupancies/Details.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordOccupancies/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordOccupancies/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordPermits/Details.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordPermits/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordPermits/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordPermits/Types.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/_DefinitionFields.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordsHealth/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordsQuality/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordsQuality/Review.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordsQuality/Rubric.cshtml
  • Web/Resgrid.Web/Areas/User/Views/RecordsQuality/Trends.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_ContactPreplanSummary.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_Navigation.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_PreventionAttachments.cshtml
  • Web/Resgrid.Web/Helpers/ClaimsAuthorizationHelper.cs
  • Web/Resgrid.Web/Startup.cs
  • Workers/Resgrid.Workers.Framework/Logic/RmsDueStateEvaluationLogic.cs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing

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

@request-info

request-info Bot commented Sep 8, 2026

Copy link
Copy Markdown

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

@Resgrid-Bot

Resgrid-Bot commented Sep 8, 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.

@github-advanced-security github-advanced-security AI 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.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

/// subdomains ("example.org, .ciffc.ca"). Empty allows any public host: the address checks below still
/// apply, so an empty list is not the same as no restriction.
/// </summary>
public static string AllowedFeedHosts = "";

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

Mutable configuration state in Core/Resgrid.Config/RecordsConnectorConfig.cs: AllowedFeedHosts appears to be configuration metadata initialized once but remains reassignable. Mark it readonly to communicate immutability and prevent accidental mutation.

Kody rule violation: Use `readonly` or `const` for Immutable Data

public static readonly string AllowedFeedHosts = string.Empty;
Prompt for LLM

File Core/Resgrid.Config/RecordsConnectorConfig.cs:

Line 37:

Mutable configuration state in Core/Resgrid.Config/RecordsConnectorConfig.cs: AllowedFeedHosts appears to be configuration metadata initialized once but remains reassignable. Mark it readonly to communicate immutability and prevent accidental mutation.

Suggested Code:

public static readonly string AllowedFeedHosts = string.Empty;

Talk to Kody by mentioning @kody

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

<data name="CandidateLinked" xml:space="preserve"><value>Candidato collegato.</value></data>
<data name="CandidateRejected" xml:space="preserve"><value>Candidato rifiutato.</value></data>
<data name="Candidates" xml:space="preserve"><value>Candidati</value></data>
<data name="CaseAccessNotice" xml:space="preserve"><value>L'accesso a questo caso è limitato ai suoi membri e ogni lettura viene registrata.</value></data>

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

Audit wording mismatch in Core/Resgrid.Localization/Areas/User/Records/Records.it.resx and the related locations listed: CaseAccessNotice promises that every read is recorded, but the stated requirement is an append-only immutable audit log for ePHI access. Tighten the text to immutable audit logging and verify the implementation enforces that stronger guarantee.

Kody rule violation: Write immutable audit logs for all ePHI access

<data name="CaseAccessNotice" xml:space="preserve"><value>L'accesso a questo caso è limitato ai suoi membri e ogni lettura genera un audit record immutabile.</value></data>
Prompt for LLM

File Core/Resgrid.Localization/Areas/User/Records/Records.it.resx:

Line 1461:

Audit wording mismatch in Core/Resgrid.Localization/Areas/User/Records/Records.it.resx and the related locations listed: CaseAccessNotice promises that every read is recorded, but the stated requirement is an append-only immutable audit log for ePHI access. Tighten the text to immutable audit logging and verify the implementation enforces that stronger guarantee.

Suggested Code:

  <data name="CaseAccessNotice" xml:space="preserve"><value>L'accesso a questo caso è limitato ai suoi membri e ogni lettura genera un audit record immutabile.</value></data>

Talk to Kody by mentioning @kody

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

<data name="AllPrograms" xml:space="preserve"><value>Tutti i programmi</value></data>
<data name="AmendmentsRecommended" xml:space="preserve"><value>Modifiche consigliate</value></data>
<data name="Applicant" xml:space="preserve"><value>Richiedente</value></data>
<data name="ApplicantEmail" xml:space="preserve"><value>E-mail del richiedente</value></data>

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

PII labeling risk in Core/Resgrid.Localization/Areas/User/Records/Records.it.resx and the related locations listed: ApplicantEmail names raw email directly, which conflicts with default redaction or hashing requirements for telemetry and diagnostics. Prefer terminology and downstream usage that indicate hashed or redacted representations.

Kody rule violation: Redact PII in logs and metrics by default

<data name="ApplicantEmail" xml:space="preserve"><value>Hash e-mail richiedente</value></data>
Prompt for LLM

File Core/Resgrid.Localization/Areas/User/Records/Records.it.resx:

Line 1440:

PII labeling risk in Core/Resgrid.Localization/Areas/User/Records/Records.it.resx and the related locations listed: ApplicantEmail names raw email directly, which conflicts with default redaction or hashing requirements for telemetry and diagnostics. Prefer terminology and downstream usage that indicate hashed or redacted representations.

Suggested Code:

  <data name="ApplicantEmail" xml:space="preserve"><value>Hash e-mail richiedente</value></data>

Talk to Kody by mentioning @kody

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

/// <summary>Address folding shared by the crosswalk inventory and the occupancy master so both sides match the same way.</summary>
public static class AddressNormalizer
{
private static readonly Regex NonAlphanumeric = new Regex("[^A-Z0-9 ]", RegexOptions.Compiled);

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

Regex DoS risk in Core/Resgrid.Model/Records/RecordsPreventionContracts.cs and the related locations listed: NonAlphanumeric uses new Regex("[^A-Z0-9 ]", RegexOptions.Compiled) without a timeout, allowing untrusted input to consume unbounded processing time. Define an explicit regex timeout.

Kody rule violation: Specify Timeout for Regular Expressions

Prompt for LLM

File Core/Resgrid.Model/Records/RecordsPreventionContracts.cs:

Line 60:

Regex DoS risk in Core/Resgrid.Model/Records/RecordsPreventionContracts.cs and the related locations listed: NonAlphanumeric uses new Regex("[^A-Z0-9 ]", RegexOptions.Compiled) without a timeout, allowing untrusted input to consume unbounded processing time. Define an explicit regex timeout.

Talk to Kody by mentioning @kody

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

auditEvent.UserAgent = userAgent;
var auditEvent = NewAuditEvent(departmentId, userId, AuditLogTypes.ContactRemoved, ipAddress, userAgent);

var contact = await _contactsRepository.GetByIdAsync(contactId);

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 service-level exception context in Core/Resgrid.Services/ContactsService.cs and the related locations listed: await _contactsRepository.GetByIdAsync(contactId) performs external data access without a surrounding try/catch, so repository failures bubble up without operation context. Catch exceptions around the repository call, annotate them with service context, and map or rethrow deliberately.

Kody rule violation: Add try-catch blocks for external calls

try
{
	var contact = await _contactsRepository.GetByIdAsync(contactId);
}
catch (Exception ex)
{
	// add context/logging and map to an application-level error
	throw;
}
Prompt for LLM

File Core/Resgrid.Services/ContactsService.cs:

Line 225:

Missing service-level exception context in Core/Resgrid.Services/ContactsService.cs and the related locations listed: await _contactsRepository.GetByIdAsync(contactId) performs external data access without a surrounding try/catch, so repository failures bubble up without operation context. Catch exceptions around the repository call, annotate them with service context, and map or rethrow deliberately.

Suggested Code:

			try
			{
				var contact = await _contactsRepository.GetByIdAsync(contactId);
			}
			catch (Exception ex)
			{
				// add context/logging and map to an application-level error
				throw;
			}

Talk to Kody by mentioning @kody

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

Comment on lines +590 to +598
foreach (var contactId in (contactIds ?? Enumerable.Empty<string>()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct())
{
var notes = await _contactNotesRepository.GetContactNotesByContactIdAsync(contactId);
result[contactId] = (notes ?? Enumerable.Empty<ContactNote>())
.Where(x => IsLiveAlertNote(x, departmentId, now))
.ToList();
}

return result;

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 Performance medium

N+1 query pattern in Core/Resgrid.Services/ContactsService.cs: GetAlertNotesByContactIdsAsync and LoadContactsAsync issue one repository call per distinct contact, adding O(contacts) round-trips to GetActiveCalls and GetCall on top of the existing batched reads. Batch contact and alert-note retrieval with repository methods such as GetContactNotesByContactIdsAsync and GetByIdsAsync so each request uses one query per dataset.

public async Task<Dictionary<string, List<ContactNote>>> GetAlertNotesByContactIdsAsync(int departmentId, IEnumerable<string> contactIds)
{
	var ids = (contactIds ?? Enumerable.Empty<string>()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
	if (!ids.Any())
		return new Dictionary<string, List<ContactNote>>();

	var now = DateTime.UtcNow;
	var notes = await _contactNotesRepository.GetContactNotesByContactIdsAsync(departmentId, ids);
	return (notes ?? Enumerable.Empty<ContactNote>())
		.Where(x => IsLiveAlertNote(x, departmentId, now))
		.GroupBy(x => x.ContactId)
		.ToDictionary(g => g.Key, g => g.ToList());
}

private async Task<Dictionary<string, Contact>> LoadContactsAsync(int departmentId, IEnumerable<string> contactIds)
{
	var ids = contactIds.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
	if (!ids.Any())
		return new Dictionary<string, Contact>();

	return ((await _contactsRepository.GetByIdsAsync(departmentId, ids)) ?? Enumerable.Empty<Contact>())
		.Where(c => !c.IsDeleted)
		.ToDictionary(c => c.ContactId, c => c);
}
Prompt for LLM

File Core/Resgrid.Services/ContactsService.cs:

Line 590 to 598:

N+1 query pattern in Core/Resgrid.Services/ContactsService.cs: GetAlertNotesByContactIdsAsync and LoadContactsAsync issue one repository call per distinct contact, adding O(contacts) round-trips to GetActiveCalls and GetCall on top of the existing batched reads. Batch contact and alert-note retrieval with repository methods such as GetContactNotesByContactIdsAsync and GetByIdsAsync so each request uses one query per dataset.

Suggested Code:

public async Task<Dictionary<string, List<ContactNote>>> GetAlertNotesByContactIdsAsync(int departmentId, IEnumerable<string> contactIds)
{
	var ids = (contactIds ?? Enumerable.Empty<string>()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
	if (!ids.Any())
		return new Dictionary<string, List<ContactNote>>();

	var now = DateTime.UtcNow;
	var notes = await _contactNotesRepository.GetContactNotesByContactIdsAsync(departmentId, ids);
	return (notes ?? Enumerable.Empty<ContactNote>())
		.Where(x => IsLiveAlertNote(x, departmentId, now))
		.GroupBy(x => x.ContactId)
		.ToDictionary(g => g.Key, g => g.ToList());
}

private async Task<Dictionary<string, Contact>> LoadContactsAsync(int departmentId, IEnumerable<string> contactIds)
{
	var ids = contactIds.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
	if (!ids.Any())
		return new Dictionary<string, Contact>();

	return ((await _contactsRepository.GetByIdsAsync(departmentId, ids)) ?? Enumerable.Empty<Contact>())
		.Where(c => !c.IsDeleted)
		.ToDictionary(c => c.ContactId, c => c);
}

Talk to Kody by mentioning @kody

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


foreach (var key in OnePerOperationalPeriod())
{
var definition = pack.Definitions.SingleOrDefault(d => d.Key == IncidentSupportPackKey + "." + key)

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

Invariant mismatch in Core/Resgrid.Services/Records/RecordTemplateCatalog.IncidentSupport.cs and Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs: SingleOrDefault() implies that no match is acceptable, but the code throws when the definition is missing, so exactly one match is required. Use Single() to make that contract explicit.

Kody rule violation: Use `First`/`Single` Instead of `FirstOrDefault`/`SingleOrDefault` for Non-Empty Collections

var definition = pack.Definitions.Single(d => d.Key == IncidentSupportPackKey + "." + key)
Prompt for LLM

File Core/Resgrid.Services/Records/RecordTemplateCatalog.IncidentSupport.cs:

Line 123:

Invariant mismatch in Core/Resgrid.Services/Records/RecordTemplateCatalog.IncidentSupport.cs and Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs: SingleOrDefault() implies that no match is acceptable, but the code throws when the definition is missing, so exactly one match is required. Use Single() to make that contract explicit.

Suggested Code:

				var definition = pack.Definitions.Single(d => d.Key == IncidentSupportPackKey + "." + key)

Talk to Kody by mentioning @kody

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

inspection.SignatureName = RecordsPreventionGate.Trim(signatureName, 200);
inspection.SignedOn = inspection.SignatureName == null ? null : now;
inspection.CompletedOn = now; inspection.StartedOn ??= now; inspection.InspectorUserId ??= userId;
inspection.Result = failed.Count == 0 ? (int)RmsInspectionResult.Pass : failedRequired ? (int)RmsInspectionResult.Fail : (int)RmsInspectionResult.Conditional;

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

Blocking async call identified in Core/Resgrid.Services/Records/RecordsInspectionsService.cs and the related locations listed: .Result or .Wait() can deadlock and waste threads, although the sample line does not show the offending call directly. Replace blocking access with await.

Kody rule violation: Avoid Blocking Calls to Async Methods

Prompt for LLM

File Core/Resgrid.Services/Records/RecordsInspectionsService.cs:

Line 313:

Blocking async call identified in Core/Resgrid.Services/Records/RecordsInspectionsService.cs and the related locations listed: .Result or .Wait() can deadlock and waste threads, although the sample line does not show the offending call directly. Replace blocking access with await.

Talk to Kody by mentioning @kody

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

inspection.SignatureName = RecordsPreventionGate.Trim(signatureName, 200);
inspection.SignedOn = inspection.SignatureName == null ? null : now;
inspection.CompletedOn = now; inspection.StartedOn ??= now; inspection.InspectorUserId ??= userId;
inspection.Result = failed.Count == 0 ? (int)RmsInspectionResult.Pass : failedRequired ? (int)RmsInspectionResult.Fail : (int)RmsInspectionResult.Conditional;

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

Async rule violation in Core/Resgrid.Services/Records/RecordsInspectionsService.cs and the related locations listed: the issue is blocking async work with .Result or .Wait(), despite the sample line not showing the violation directly. Replace blocking calls with await and keep async/await flow end-to-end.

Kody rule violation: Await async operations properly

Prompt for LLM

File Core/Resgrid.Services/Records/RecordsInspectionsService.cs:

Line 313:

Async rule violation in Core/Resgrid.Services/Records/RecordsInspectionsService.cs and the related locations listed: the issue is blocking async work with .Result or .Wait(), despite the sample line not showing the violation directly. Replace blocking calls with await and keep async/await flow end-to-end.

Talk to Kody by mentioning @kody

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

Comment on lines +291 to +296
entity.Title = RecordsPreventionGate.Require(input.Title, 200, "A hazard needs a title.");
entity.HazardType = input.HazardType; entity.Severity = Math.Clamp(input.Severity, 1, 4);
entity.Description = RecordsPreventionGate.Trim(input.Description, 4000);
entity.LocationDescription = RecordsPreventionGate.Trim(input.LocationDescription, 1000);
entity.GpsCoordinates = RecordsPreventionGate.Trim(input.GpsCoordinates, 100);
entity.ShouldAlert = input.ShouldAlert; entity.ModifiedOn = now;

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 medium

Severity enum mismatch in Core/Resgrid.Services/Records/RecordsOccupancyService.cs: RMS occupancy hazards now accept severity values 3 and 4, but the ownership-cutover projection still maps through ContactPreplanHazard, whose contact-facing enum defines only 0..2. Clamp severity to the ContactPreplanHazardSeverities range or add an explicit translation before projecting RMS hazards into the ContactPreplan view.

entity.Title = RecordsPreventionGate.Require(input.Title, 200, "A hazard needs a title.");
entity.HazardType = input.HazardType;
entity.Severity = Math.Clamp(input.Severity,
    (int)ContactPreplanHazardSeverities.Info,
    (int)ContactPreplanHazardSeverities.Danger);
entity.Description = RecordsPreventionGate.Trim(input.Description, 4000);
Prompt for LLM

File Core/Resgrid.Services/Records/RecordsOccupancyService.cs:

Line 291 to 296:

Severity enum mismatch in Core/Resgrid.Services/Records/RecordsOccupancyService.cs: RMS occupancy hazards now accept severity values 3 and 4, but the ownership-cutover projection still maps through ContactPreplanHazard, whose contact-facing enum defines only 0..2. Clamp severity to the ContactPreplanHazardSeverities range or add an explicit translation before projecting RMS hazards into the ContactPreplan view.

Suggested Code:

entity.Title = RecordsPreventionGate.Require(input.Title, 200, "A hazard needs a title.");
entity.HazardType = input.HazardType;
entity.Severity = Math.Clamp(input.Severity,
    (int)ContactPreplanHazardSeverities.Info,
    (int)ContactPreplanHazardSeverities.Danger);
entity.Description = RecordsPreventionGate.Trim(input.Description, 4000);

Talk to Kody by mentioning @kody

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

return entity;
}

public async Task<List<RmsPermit>> ListAsync(int departmentId, string userId, RmsPermitQuery query)

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

Responsibility overload in Core/Resgrid.Services/Records/RecordsPermitsService.cs: the service combines listing, counting, retrieval, mutations, transitions, auditing, protection, and expiry workflows in one class. Split permit query operations from command and workflow operations to restore single-responsibility boundaries.

Kody rule violation: Ensure Controllers Follow Single Responsibility Principle

// Move listing/query orchestration into a dedicated query service and keep controller/service responsibilities narrower.
Prompt for LLM

File Core/Resgrid.Services/Records/RecordsPermitsService.cs:

Line 64:

Responsibility overload in Core/Resgrid.Services/Records/RecordsPermitsService.cs: the service combines listing, counting, retrieval, mutations, transitions, auditing, protection, and expiry workflows in one class. Split permit query operations from command and workflow operations to restore single-responsibility boundaries.

Suggested Code:

// Move listing/query orchestration into a dedicated query service and keep controller/service responsibilities narrower.

Talk to Kody by mentioning @kody

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

public async Task<RecordsReleaseTelemetry> LogSnapshotAsync(int departmentId, CancellationToken cancellationToken = default)
{
var snapshot = await BuildAsync(departmentId, 24);
Logging.LogInfo(JsonConvert.SerializeObject(new { rms_release_telemetry = snapshot }));

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

Unstructured telemetry logging in Core/Resgrid.Services/Records/RecordsReleaseTelemetryService.cs and the related locations listed: Logging.LogInfo(JsonConvert.SerializeObject(new { rms_release_telemetry = snapshot })) collapses operation context into an opaque string. Emit a structured log entry with fields such as operation = "LogSnapshotAsync" and departmentId.

Kody rule violation: Include error context in structured logs

Logging.LogInfo("Release telemetry snapshot", new { operation = "LogSnapshotAsync", departmentId, rms_release_telemetry = snapshot });
Prompt for LLM

File Core/Resgrid.Services/Records/RecordsReleaseTelemetryService.cs:

Line 59:

Unstructured telemetry logging in Core/Resgrid.Services/Records/RecordsReleaseTelemetryService.cs and the related locations listed: Logging.LogInfo(JsonConvert.SerializeObject(new { rms_release_telemetry = snapshot })) collapses operation context into an opaque string. Emit a structured log entry with fields such as operation = "LogSnapshotAsync" and departmentId.

Suggested Code:

Logging.LogInfo("Release telemetry snapshot", new { operation = "LogSnapshotAsync", departmentId, rms_release_telemetry = snapshot });

Talk to Kody by mentioning @kody

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

i["program_name"] = "Annual life-safety inspection";
i["state"] = "ReinspectionRequired";
i["result"] = "Fail";
i["scheduled_on"] = DateTime.Now.AddDays(-3);

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

Timing source misuse in Core/Resgrid.Services/WorkflowSampleDataGenerator.cs and the related locations listed: DateTime.Now is used for timing-sensitive behavior and is vulnerable to daylight savings and system clock changes. Use Stopwatch for elapsed-time measurement.

Kody rule violation: Avoid `DateTime.Now` for Timing Operations

Prompt for LLM

File Core/Resgrid.Services/WorkflowSampleDataGenerator.cs:

Line 888:

Timing source misuse in Core/Resgrid.Services/WorkflowSampleDataGenerator.cs and the related locations listed: DateTime.Now is used for timing-sensitive behavior and is vulnerable to daylight savings and system clock changes. Use Stopwatch for elapsed-time measurement.

Talk to Kody by mentioning @kody

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

.WithColumn("IsProtected").AsBoolean().NotNullable().WithDefaultValue(false);

Create.Index("IX_ContactAttachments_Contact").OnTable("ContactAttachments")
.OnColumn("DepartmentId").Ascending().OnColumn("ContactId").Ascending().OnColumn("IsDeleted").Ascending();

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

Index validation gap in Providers/Resgrid.Providers.Migrations/Migrations/M0184_AddContactAttachments.cs: the index on DepartmentId, ContactId, and IsDeleted is added without evidence that those columns match real query predicates or production workload plans. Verify the access patterns and confirm the database index strategy against actual usage.

Kody rule violation: Add database indexes for query optimization

Prompt for LLM

File Providers/Resgrid.Providers.Migrations/Migrations/M0184_AddContactAttachments.cs:

Line 34:

Index validation gap in Providers/Resgrid.Providers.Migrations/Migrations/M0184_AddContactAttachments.cs: the index on DepartmentId, ContactId, and IsDeleted is added without evidence that those columns match real query predicates or production workload plans. Verify the access patterns and confirm the database index strategy against actual usage.

Talk to Kody by mentioning @kody

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

Create.Index("IX_RmsInvestigationReferrals_Case").OnTable("RmsInvestigationReferrals").OnColumn("DepartmentId").Ascending().OnColumn("RmsInvestigationCaseId").Ascending();
}

Execute.Sql("IF NOT EXISTS (SELECT 1 FROM [FeatureFlags] WHERE [FlagKey] = 'Records.Prevention.Occupancy') INSERT INTO [FeatureFlags] ([FlagKey], [Name], [Description], [Category], [IsEnabledGlobally]) VALUES ('Records.Prevention.Occupancy', 'Records Prevention - Occupancies', 'RMS-5 occupancy/property master and the Contacts pre-plan crosswalk. Requires Records.System. Seeded off.', 'Records', 0);");

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 audit trail in Providers/Resgrid.Providers.Migrations/Migrations/M0186_AddRmsPreventionAndInvestigations.cs: the migration seeds the security-relevant flag Records.Prevention.Occupancy without an immutable audit record containing timestamp, actor.user_id, actor.role, action, resource.id, result, trace_id, ip, and user_agent. Add an append-only audit write for the flag change or move the mutation through audited application code.

Kody rule violation: Emit tamper-evident audit logs with required fields

Prompt for LLM

File Providers/Resgrid.Providers.Migrations/Migrations/M0186_AddRmsPreventionAndInvestigations.cs:

Line 709:

Missing audit trail in Providers/Resgrid.Providers.Migrations/Migrations/M0186_AddRmsPreventionAndInvestigations.cs: the migration seeds the security-relevant flag Records.Prevention.Occupancy without an immutable audit record containing timestamp, actor.user_id, actor.role, action, resource.id, result, trace_id, ip, and user_agent. Add an append-only audit write for the flag change or move the mutation through audited application code.

Talk to Kody by mentioning @kody

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

public override void Up()
{
if (Schema.Table("rmsoperationalrecords").Exists() && !Schema.Table("rmsoperationalrecords").Index("IX_RmsOperationalRecords_Department_State_Modified").Exists())
Create.Index("IX_RmsOperationalRecords_Department_State_Modified").OnTable("rmsoperationalrecords").OnColumn("departmentid").Ascending().OnColumn("state").Ascending().OnColumn("modifiedon").Ascending();

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

Migration locking risk in Providers/Resgrid.Providers.MigrationsPg/Migrations/M0185_RmsReleaseReadinessPg.cs and the related locations listed: Create.Index("IX_RmsOperationalRecords_Department_State_Modified") on rmsoperationalrecords shows no concurrent or online creation strategy for an existing PostgreSQL table. Use a non-blocking approach such as CONCURRENTLY where supported and document rollback and downtime expectations.

Kody rule violation: Block risky database migrations (locking ops, downtime risk)

Prompt for LLM

File Providers/Resgrid.Providers.MigrationsPg/Migrations/M0185_RmsReleaseReadinessPg.cs:

Line 19:

Migration locking risk in Providers/Resgrid.Providers.MigrationsPg/Migrations/M0185_RmsReleaseReadinessPg.cs and the related locations listed: Create.Index("IX_RmsOperationalRecords_Department_State_Modified") on rmsoperationalrecords shows no concurrent or online creation strategy for an existing PostgreSQL table. Use a non-blocking approach such as CONCURRENTLY where supported and document rollback and downtime expectations.

Talk to Kody by mentioning @kody

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

Comment on lines +844 to +846
public Task<IEnumerable<RmsAccessAudit>> GetForAggregateAsync(int departmentId, string aggregateId, int take)
{
return QueryAsync<RmsAccessAudit>($"SELECT * FROM {Tbl("RmsAccessAudits")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("RecordId")} = {P}Id ORDER BY {Col("OccurredOn")} DESC, {Col("RmsAccessAuditId")} DESC {Paging()}", new { DepartmentId = departmentId, Id = aggregateId, Skip = 0, Take = Math.Clamp(take, 1, 1000) });

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

Audit lookup mismatch in Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs: GetForAggregateAsync filters on RecordId, but the new prevention/investigation audit writer stores case IDs in CorrelationId and leaves RecordId null. Query CorrelationId instead so the RecordInvestigationsService access-audit view returns the reads and exports audited by RecordsPreventionGate.AuditAsync.

public Task<IEnumerable<RmsAccessAudit>> GetForAggregateAsync(int departmentId, string aggregateId, int take)
{
	return QueryAsync<RmsAccessAudit>($"SELECT * FROM {Tbl("RmsAccessAudits")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("CorrelationId")} = {P}Id ORDER BY {Col("OccurredOn")} DESC, {Col("RmsAccessAuditId")} DESC {Paging()}", new { DepartmentId = departmentId, Id = aggregateId, Skip = 0, Take = Math.Clamp(take, 1, 1000) });
}
Prompt for LLM

File Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs:

Line 844 to 846:

Audit lookup mismatch in Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs: GetForAggregateAsync filters on RecordId, but the new prevention/investigation audit writer stores case IDs in CorrelationId and leaves RecordId null. Query CorrelationId instead so the RecordInvestigationsService access-audit view returns the reads and exports audited by RecordsPreventionGate.AuditAsync.

Suggested Code:

public Task<IEnumerable<RmsAccessAudit>> GetForAggregateAsync(int departmentId, string aggregateId, int take)
{
	return QueryAsync<RmsAccessAudit>($"SELECT * FROM {Tbl("RmsAccessAudits")} WHERE {Col("DepartmentId")} = {P}DepartmentId AND {Col("CorrelationId")} = {P}Id ORDER BY {Col("OccurredOn")} DESC, {Col("RmsAccessAuditId")} DESC {Paging()}", new { DepartmentId = departmentId, Id = aggregateId, Skip = 0, Take = Math.Clamp(take, 1, 1000) });
}

Talk to Kody by mentioning @kody

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

public int Inserts { get; private set; }
public int Updates { get; private set; }

protected static int DeptOf(T e) => (int)e.GetType().GetProperty("DepartmentId").GetValue(e);

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

Null reflection dereference in Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs and the related locations listed: GetProperty("DepartmentId") can return null, making .GetValue(e) a potential null reference. Add a null-safe check with ?. and a clear fallback or exception.

Kody rule violation: Add null checks before accessing properties

protected static int DeptOf(T e) => (int?)(e.GetType().GetProperty("DepartmentId")?.GetValue(e)) ?? 0;
Prompt for LLM

File Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs:

Line 22:

Null reflection dereference in Tests/Resgrid.Tests/Rms/RmsPreventionFakes.cs and the related locations listed: GetProperty("DepartmentId") can return null, making .GetValue(e) a potential null reference. Add a null-safe check with ?. and a clear fallback or exception.

Suggested Code:

		protected static int DeptOf(T e) => (int?)(e.GetType().GetProperty("DepartmentId")?.GetValue(e)) ?? 0;

Talk to Kody by mentioning @kody

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

.Concat(Resgrid.Model.RmsProtectedFields.Permits.Keys).Concat(Resgrid.Model.RmsProtectedFields.PlanReviews.Keys)
.Concat(Resgrid.Model.RmsProtectedFields.InvestigationCases.Keys).Concat(Resgrid.Model.RmsProtectedFields.InvestigationNotes.Keys)
.Concat(Resgrid.Model.RmsProtectedFields.InvestigationEvidence.Keys).Concat(Resgrid.Model.RmsProtectedFields.InvestigationCustody.Keys)
.Concat(Resgrid.Model.RmsProtectedFields.InvestigationReferrals.Keys).Concat(Resgrid.Model.RmsProtectedFields.QualityReviews.Keys)

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

Readability issue in Tests/Resgrid.Tests/Services/RemainingCandidateProtectionTests.cs and the related locations listed: the long chained query obscures the business assertion. Introduce intermediate variables or helper methods so the test names the expected field set instead of embedding the full chain inline.

Kody rule violation: Limit Lengthy LINQ Chains

expectedRms5FieldIds = expectedRms5FieldIds
	.Concat(Resgrid.Model.RmsProtectedFields.InvestigationReferrals.Keys)
	.Concat(Resgrid.Model.RmsProtectedFields.QualityReviews.Keys);
Prompt for LLM

File Tests/Resgrid.Tests/Services/RemainingCandidateProtectionTests.cs:

Line 37:

Readability issue in Tests/Resgrid.Tests/Services/RemainingCandidateProtectionTests.cs and the related locations listed: the long chained query obscures the business assertion. Introduce intermediate variables or helper methods so the test names the expected field set instead of embedding the full chain inline.

Suggested Code:

				expectedRms5FieldIds = expectedRms5FieldIds
					.Concat(Resgrid.Model.RmsProtectedFields.InvestigationReferrals.Keys)
					.Concat(Resgrid.Model.RmsProtectedFields.QualityReviews.Keys);

Talk to Kody by mentioning @kody

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

{
var attachment = meta;
if (includeData)
attachment = await _contactsService.GetContactAttachmentByIdAsync(meta.ContactAttachmentId) ?? meta;

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

N+1 attachment fetch in Web/Resgrid.Web.Services/Controllers/v4/ContactFilesController.cs and the related locations listed: await _contactsService.GetContactAttachmentByIdAsync(meta.ContactAttachmentId) executes once per item during iteration. Batch the attachment IDs and resolve them with a single GetContactAttachmentsByIdsAsync call.

Kody rule violation: Detect N+1 style queries and suggest batching

var ids = attachments.Select(a => a.ContactAttachmentId).ToList();
var fullAttachments = await _contactsService.GetContactAttachmentsByIdsAsync(ids);
// map results by id and reuse in the loop
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ContactFilesController.cs:

Line 90:

N+1 attachment fetch in Web/Resgrid.Web.Services/Controllers/v4/ContactFilesController.cs and the related locations listed: await _contactsService.GetContactAttachmentByIdAsync(meta.ContactAttachmentId) executes once per item during iteration. Batch the attachment IDs and resolve them with a single GetContactAttachmentsByIdsAsync call.

Suggested Code:

				var ids = attachments.Select(a => a.ContactAttachmentId).ToList();
				var fullAttachments = await _contactsService.GetContactAttachmentsByIdsAsync(ids);
				// map results by id and reuse in the loop

Talk to Kody by mentioning @kody

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

public async Task<ActionResult<StandardApiResponseV4Base>> ImportCodeSections(string codeSetId, [FromBody] HydrantImportInput input, CancellationToken cancellationToken)
{
if (!await FlagOnAsync()) return NotFound();
try { var created = await _inspections.ImportCodeSectionsAsync(DepartmentId, UserId, codeSetId, input?.Csv, cancellationToken); return Ok(Done(new StandardApiResponseV4Base { PageSize = created })); }

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

Input validation order bug in Web/Resgrid.Web.Services/Controllers/v4/RecordInspectionsController.cs and the related locations listed: _inspections.ImportCodeSectionsAsync(DepartmentId, UserId, codeSetId, input?.Csv, cancellationToken) is invoked before confirming that codeSetId and input?.Csv are non-empty. Add guard clauses for codeSetId and input?.Csv before the service call and return BadRequest() on invalid input.

Kody rule violation: Order validations before database queries

if (string.IsNullOrWhiteSpace(codeSetId) || string.IsNullOrWhiteSpace(input?.Csv)) return BadRequest();
try { var created = await _inspections.ImportCodeSectionsAsync(DepartmentId, UserId, codeSetId, input.Csv, cancellationToken); return Ok(Done(new StandardApiResponseV4Base { PageSize = created })); }
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/RecordInspectionsController.cs:

Line 78:

Input validation order bug in Web/Resgrid.Web.Services/Controllers/v4/RecordInspectionsController.cs and the related locations listed: _inspections.ImportCodeSectionsAsync(DepartmentId, UserId, codeSetId, input?.Csv, cancellationToken) is invoked before confirming that codeSetId and input?.Csv are non-empty. Add guard clauses for codeSetId and input?.Csv before the service call and return BadRequest() on invalid input.

Suggested Code:

if (string.IsNullOrWhiteSpace(codeSetId) || string.IsNullOrWhiteSpace(input?.Csv)) return BadRequest();
try { var created = await _inspections.ImportCodeSectionsAsync(DepartmentId, UserId, codeSetId, input.Csv, cancellationToken); return Ok(Done(new StandardApiResponseV4Base { PageSize = created })); }

Talk to Kody by mentioning @kody

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

public async Task<ActionResult<OccupancySavedResult>> Save([FromBody] OccupancyInput input, CancellationToken cancellationToken)
{
if (!await FlagOnAsync()) return NotFound();
if (input == null) return BadRequest();

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

Opaque client error in Web/Resgrid.Web.Services/Controllers/v4/RecordOccupanciesController.cs and the related locations listed: if (input == null) return BadRequest(); returns HTTP 400 without a reason payload. Return a minimal error body such as { error = "input is required" } so callers can diagnose the validation failure.

Kody rule violation: Use appropriate HTTP status codes

if (input == null) return BadRequest(new { error = "input is required" });
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/RecordOccupanciesController.cs:

Line 74:

Opaque client error in Web/Resgrid.Web.Services/Controllers/v4/RecordOccupanciesController.cs and the related locations listed: if (input == null) return BadRequest(); returns HTTP 400 without a reason payload. Return a minimal error body such as { error = "input is required" } so callers can diagnose the validation failure.

Suggested Code:

if (input == null) return BadRequest(new { error = "input is required" });

Talk to Kody by mentioning @kody

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

{
if (!await FlagOnAsync()) return NotFound();
if (input == null) return BadRequest();
try { return Ok(Done(new PermitTypeResult { Data = RecordsRms5ApiMapper.ToPermitType(await _permits.SaveTypeAsync(DepartmentId, UserId, new RmsPermitType { RmsPermitTypeId = input.PermitTypeId, Name = input.Name, Code = input.Code, Description = input.Description, DefaultValidityDays = input.DefaultValidityDays, RequiresPlanReview = input.RequiresPlanReview, FeeAmount = input.FeeAmount, ConditionsTemplate = input.ConditionsTemplate, IsActive = input.IsActive }, cancellationToken)), PageSize = 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

Controller mapping leakage in Web/Resgrid.Web.Services/Controllers/v4/RecordPermitsController.cs: the action constructs RmsPermitType inline inside the controller, mixing transport mapping with request handling. Delegate object construction to RecordsRms5ApiMapper.FromPermitType(input) or a service method to keep the controller thin.

Kody rule violation: Separate UI logic from business logic

try
{
	var permitType = RecordsRms5ApiMapper.FromPermitType(input);
	var saved = await _permits.SaveTypeAsync(DepartmentId, UserId, permitType, cancellationToken);
	return Ok(Done(new PermitTypeResult { Data = RecordsRms5ApiMapper.ToPermitType(saved), PageSize = 1 }));
}
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/RecordPermitsController.cs:

Line 46:

Controller mapping leakage in Web/Resgrid.Web.Services/Controllers/v4/RecordPermitsController.cs: the action constructs RmsPermitType inline inside the controller, mixing transport mapping with request handling. Delegate object construction to RecordsRms5ApiMapper.FromPermitType(input) or a service method to keep the controller thin.

Suggested Code:

			try
			{
				var permitType = RecordsRms5ApiMapper.FromPermitType(input);
				var saved = await _permits.SaveTypeAsync(DepartmentId, UserId, permitType, cancellationToken);
				return Ok(Done(new PermitTypeResult { Data = RecordsRms5ApiMapper.ToPermitType(saved), PageSize = 1 }));
			}

Talk to Kody by mentioning @kody

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

if (!await ModuleOnAsync(Flag)) return NotFound();
try
{
var saved = await _hydrants.SaveAsync(DepartmentId, UserId, model.Hydrant, cancellationToken);

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

Model validation bypass in Web/Resgrid.Web/Areas/User/Controllers/RecordHydrantsController.cs and the related locations listed: the action saves model.Hydrant through _hydrants.SaveAsync(DepartmentId, UserId, model.Hydrant, cancellationToken) without first checking ModelState. Return early on !ModelState.IsValid, prepare the model, and redisplay the view before persisting.

Kody rule violation: Always Validate `ModelState.IsValid` in Controllers

if (!ModelState.IsValid)
{
	Prepare(model);
	return View(model);
}
var saved = await _hydrants.SaveAsync(DepartmentId, UserId, model.Hydrant, cancellationToken);
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordHydrantsController.cs:

Line 84:

Model validation bypass in Web/Resgrid.Web/Areas/User/Controllers/RecordHydrantsController.cs and the related locations listed: the action saves model.Hydrant through _hydrants.SaveAsync(DepartmentId, UserId, model.Hydrant, cancellationToken) without first checking ModelState. Return early on !ModelState.IsValid, prepare the model, and redisplay the view before persisting.

Suggested Code:

				if (!ModelState.IsValid)
				{
					Prepare(model);
					return View(model);
				}
				var saved = await _hydrants.SaveAsync(DepartmentId, UserId, model.Hydrant, cancellationToken);

Talk to Kody by mentioning @kody

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

foreach (var o in await _occupancies.ListAsync(DepartmentId, UserId, new RmsOccupancyQuery { Take = 2000 }))
if (wanted.Contains(o.RmsOccupancyId)) map[o.RmsOccupancyId] = (o.OccupancyNumber + " " + o.Name).Trim();
}
catch (RecordsModuleDisabledException) { }

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

Exception swallowing in Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs and the related locations listed: catch (RecordsModuleDisabledException) { } suppresses failures without logging or explicit handling. Log the exception with context and either rethrow or convert it to a deliberate application response.

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs:

Line 288:

Exception swallowing in Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs and the related locations listed: catch (RecordsModuleDisabledException) { } suppresses failures without logging or explicit handling. Log the exception with context and either rethrow or convert it to a deliberate application response.

Talk to Kody by mentioning @kody

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

if (wanted.Count == 0) return;
try
{
foreach (var o in await _occupancies.ListAsync(DepartmentId, UserId, new RmsOccupancyQuery { Take = 2000 }))

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

Error-handling ambiguity in Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs and the related locations listed: awaited external calls run within methods that swallow a specific exception and do not add contextual handling for other async failures. Handle those failures with explicit logging or application mapping instead of silent suppression or context-free propagation.

Kody rule violation: Handle async operations with proper error handling

var occupancies = await _occupancies.ListAsync(DepartmentId, UserId, new RmsOccupancyQuery { Take = OccupancyQueryLimit });
foreach (var o in occupancies)
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs:

Line 285:

Error-handling ambiguity in Web/Resgrid.Web/Areas/User/Controllers/RecordInspectionsController.cs and the related locations listed: awaited external calls run within methods that swallow a specific exception and do not add contextual handling for other async failures. Handle those failures with explicit logging or application mapping instead of silent suppression or context-free propagation.

Suggested Code:

				var occupancies = await _occupancies.ListAsync(DepartmentId, UserId, new RmsOccupancyQuery { Take = OccupancyQueryLimit });
				foreach (var o in occupancies)

Talk to Kody by mentioning @kody

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

if (!await ModuleOnAsync(Flag)) return NotFound();
try
{
var bytes = await _investigations.ExportAsync(DepartmentId, UserId, id, Ip, cancellationToken);

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

Export control gap in Web/Resgrid.Web/Areas/User/Controllers/RecordInvestigationsController.cs: _investigations.ExportAsync(DepartmentId, UserId, id, Ip, cancellationToken) executes a bulk export path without visible approval, step-up MFA, rate limiting, watermarking, or export_id audit capture. Enforce those controls before invoking the export operation.

Kody rule violation: Define data export controls and watermarking

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordInvestigationsController.cs:

Line 270:

Export control gap in Web/Resgrid.Web/Areas/User/Controllers/RecordInvestigationsController.cs: _investigations.ExportAsync(DepartmentId, UserId, id, Ip, cancellationToken) executes a bulk export path without visible approval, step-up MFA, rate limiting, watermarking, or export_id audit capture. Enforce those controls before invoking the export operation.

Talk to Kody by mentioning @kody

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

{
byte[] bytes;
using (var stream = new System.IO.MemoryStream()) { await file.CopyToAsync(stream, cancellationToken); bytes = stream.ToArray(); }
await _attachments.AddAsync(DepartmentId, UserId, (RmsPreventionParentKind)parentKind, parentId, file.FileName, file.ContentType, bytes, description, restricted, cancellationToken);

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

Filename propagation risk in Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs and the related locations listed: file.FileName may contain sensitive or identifying content and is passed through directly to _attachments.AddAsync. Normalize to a safe basename with System.IO.Path.GetFileName(file.FileName) and avoid emitting raw filenames into logs or telemetry.

Kody rule violation: Mask PII and secrets in logs

var safeFileName = System.IO.Path.GetFileName(file.FileName);
await _attachments.AddAsync(DepartmentId, UserId, (RmsPreventionParentKind)parentKind, parentId, safeFileName, file.ContentType, bytes, description, restricted, cancellationToken);
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs:

Line 220:

Filename propagation risk in Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs and the related locations listed: file.FileName may contain sensitive or identifying content and is passed through directly to _attachments.AddAsync. Normalize to a safe basename with System.IO.Path.GetFileName(file.FileName) and avoid emitting raw filenames into logs or telemetry.

Suggested Code:

				var safeFileName = System.IO.Path.GetFileName(file.FileName);
				await _attachments.AddAsync(DepartmentId, UserId, (RmsPreventionParentKind)parentKind, parentId, safeFileName, file.ContentType, bytes, description, restricted, cancellationToken);

Talk to Kody by mentioning @kody

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

private async Task PopulateAsync(RecordsQualityRubricView model)
{
model.Definitions = new[] { new SelectListItem { Value = "", Text = Localizer["AllDefinitions"].Value } }
.Concat((await _definitions.ListAsync(DepartmentId)).Select(d => new SelectListItem { Value = d.Key, Text = d.Name, Selected = d.Key == model.Rubric?.DefinitionKey }))

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

Async LINQ composition issue in Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs: await _definitions.ListAsync(DepartmentId) is embedded directly inside a larger Concat projection, which obscures control flow and mixes I/O with query composition. Fetch definitions first, then build model.Definitions from the materialized result.

Kody rule violation: Use Awaitable Methods in Async Code

var definitions = await _definitions.ListAsync(DepartmentId);
model.Definitions = new[] { new SelectListItem { Value = "", Text = Localizer["AllDefinitions"].Value } }
	.Concat(definitions.Select(d => new SelectListItem { Value = d.Key, Text = d.Name, Selected = d.Key == model.Rubric?.DefinitionKey }))
	.ToList();
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs:

Line 149:

Async LINQ composition issue in Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs: await _definitions.ListAsync(DepartmentId) is embedded directly inside a larger Concat projection, which obscures control flow and mixes I/O with query composition. Fetch definitions first, then build model.Definitions from the materialized result.

Suggested Code:

				var definitions = await _definitions.ListAsync(DepartmentId);
				model.Definitions = new[] { new SelectListItem { Value = "", Text = Localizer["AllDefinitions"].Value } }
					.Concat(definitions.Select(d => new SelectListItem { Value = d.Key, Text = d.Name, Selected = d.Key == model.Rubric?.DefinitionKey }))
					.ToList();

Talk to Kody by mentioning @kody

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

}

[HttpGet]
public async Task<IActionResult> Index(int windowHours = 24)

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

Routing ambiguity in Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs: Index(int windowHours = 24) lacks an explicit HTTP verb attribute. Add [HttpGet] to make the action contract unambiguous.

Kody rule violation: Annotate REST API Actions with HTTP Verb Attributes

[HttpGet]
public async Task<IActionResult> Index(int windowHours = 24)
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs:

Line 184:

Routing ambiguity in Web/Resgrid.Web/Areas/User/Controllers/RecordsQualityController.cs: Index(int windowHours = 24) lacks an explicit HTTP verb attribute. Add [HttpGet] to make the action contract unambiguous.

Suggested Code:

		[HttpGet]
		public async Task<IActionResult> Index(int windowHours = 24)

Talk to Kody by mentioning @kody

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

<tbody>
@foreach (var hazard in Model.Hazards)
{
<tr style="background-color:@Resgrid.Web.Areas.User.Controllers.ContactsController.HazardSeverityColor(hazard.Severity)">

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

Layering violation in Web/Resgrid.Web/Areas/User/Views/Contacts/View.cshtml: the view calls Resgrid.Web.Areas.User.Controllers.ContactsController.HazardSeverityColor(hazard.Severity) directly, coupling presentation to controller implementation. Move HazardSeverityColor into the view model, a UI helper, or a dedicated service.

Kody rule violation: Enforce architecture boundaries and layering rules

<tr style="background-color:@HazardSeverityColor(hazard.Severity)">
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Contacts/View.cshtml:

Line 532:

Layering violation in Web/Resgrid.Web/Areas/User/Views/Contacts/View.cshtml: the view calls Resgrid.Web.Areas.User.Controllers.ContactsController.HazardSeverityColor(hazard.Severity) directly, coupling presentation to controller implementation. Move HazardSeverityColor into the view model, a UI helper, or a dedicated service.

Suggested Code:

<tr style="background-color:@HazardSeverityColor(hazard.Severity)">

Talk to Kody by mentioning @kody

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

points.forEach(function (p) {
var color = p.InService ? colors[p.FlowClass] || '#999' : '#000';
var m = L.circleMarker([p.Lat, p.Lon], { radius: 7, color: color, fillColor: color, fillOpacity: 0.8 }).addTo(map);
m.bindPopup('<a href="@Url.Action("Details", "RecordHydrants", new { area = "User" })?id=' + p.HydrantId + '">' + p.HydrantNumber + '</a>' + (p.FlowGpm ? '<br/>' + p.FlowGpm + ' gpm' : '') + (p.InService ? '' : '<br/><b>@L["OutOfService"]</b>'));

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 Security critical

Stored XSS in Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Index.cshtml: HydrantNumber is concatenated directly into the Leaflet popup HTML string, so markup or event handlers in stored data are injected by bindPopup. HTML-encode the popup content or build the popup with DOM text nodes instead of raw HTML.

var link = document.createElement('a');
link.href = '@Url.Action("Details", "RecordHydrants", new { area = "User" })?id=' + encodeURIComponent(p.HydrantId);
link.textContent = p.HydrantNumber || '';
var container = document.createElement('div');
container.appendChild(link);
if (p.FlowGpm) { container.appendChild(document.createElement('br')); container.appendChild(document.createTextNode(p.FlowGpm + ' gpm')); }
if (!p.InService) { container.appendChild(document.createElement('br')); var status = document.createElement('b'); status.textContent = '@L["OutOfService"]'; container.appendChild(status); }
m.bindPopup(container);
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Index.cshtml:

Line 98:

Stored XSS in Web/Resgrid.Web/Areas/User/Views/RecordHydrants/Index.cshtml: HydrantNumber is concatenated directly into the Leaflet popup HTML string, so markup or event handlers in stored data are injected by bindPopup. HTML-encode the popup content or build the popup with DOM text nodes instead of raw HTML.

Suggested Code:

var link = document.createElement('a');
link.href = '@Url.Action("Details", "RecordHydrants", new { area = "User" })?id=' + encodeURIComponent(p.HydrantId);
link.textContent = p.HydrantNumber || '';
var container = document.createElement('div');
container.appendChild(link);
if (p.FlowGpm) { container.appendChild(document.createElement('br')); container.appendChild(document.createTextNode(p.FlowGpm + ' gpm')); }
if (!p.InService) { container.appendChild(document.createElement('br')); var status = document.createElement('b'); status.textContent = '@L["OutOfService"]'; container.appendChild(status); }
m.bindPopup(container);

Talk to Kody by mentioning @kody

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

@{
ViewBag.Title = "Resgrid | " + L["Inspections"];
var now = DateTime.UtcNow;
string Occ(string id) => Model.OccupancyNames.TryGetValue(id ?? "", out var n) ? n : id;

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

Nullability mismatch in Web/Resgrid.Web/Areas/User/Views/RecordInspections/Index.cshtml and the related locations listed: Occ(string id) accepts a non-null parameter but already compensates with id ?? "", which indicates nullable input and risks unsafe rendering paths. Make the parameter string? and coalesce both lookup and fallback output to string.Empty.

Kody rule violation: Add null checks to prevent NullReferenceException

string Occ(string? id) => Model.OccupancyNames.TryGetValue(id ?? string.Empty, out var n) ? n : (id ?? string.Empty);
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/RecordInspections/Index.cshtml:

Line 8:

Nullability mismatch in Web/Resgrid.Web/Areas/User/Views/RecordInspections/Index.cshtml and the related locations listed: Occ(string id) accepts a non-null parameter but already compensates with id ?? "", which indicates nullable input and risks unsafe rendering paths. Make the parameter string? and coalesce both lookup and fallback output to string.Empty.

Suggested Code:

string Occ(string? id) => Model.OccupancyNames.TryGetValue(id ?? string.Empty, out var n) ? n : (id ?? string.Empty);

Talk to Kody by mentioning @kody

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

<div class="form-group"><label class="col-sm-3 control-label">@L["FrequencyMonths"]</label><div class="col-sm-9"><input class="form-control" type="number" min="1" max="120" asp-for="Editing.FrequencyMonths" /></div></div>
<div class="form-group"><label class="col-sm-3 control-label">@L["OccupancyTypesApply"]</label><div class="col-sm-9"><input class="form-control" asp-for="Editing.OccupancyTypesCsv" maxlength="200" placeholder="1,2,5" /><span class="help-block">@L["OccupancyTypesApplyHelp"] @string.Join(", ", Model.OccupancyTypes.Select(t => t.Value + "=" + t.Text))</span></div></div>
<div class="form-group"><label class="col-sm-3 control-label">@L["CodeSets"]</label><div class="col-sm-9"><select class="form-control" asp-for="Editing.RmsCodeSetId" asp-items="Model.CodeSetItems"></select></div></div>
<div class="form-group"><label class="col-sm-3 control-label">@L["Checklist"]</label><div class="col-sm-9"><textarea class="form-control" asp-for="ChecklistText" rows="10" style="font-family:monospace"></textarea><span class="help-block">@L["ChecklistHelp"]</span></div></div>

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

Inline style leakage in Web/Resgrid.Web/Areas/User/Views/RecordInspections/Programs.cshtml and the related locations listed: style="font-family:monospace" embeds presentation details directly in the view. Move the styling to a scoped CSS class such as checklist-textarea.

Kody rule violation: Use component-scoped styling

<div class="form-group"><label class="col-sm-3 control-label">@L["Checklist"]</label><div class="col-sm-9"><textarea class="form-control checklist-textarea" asp-for="ChecklistText" rows="10"></textarea><span class="help-block">@L["ChecklistHelp"]</span></div></div>
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/RecordInspections/Programs.cshtml:

Line 48:

Inline style leakage in Web/Resgrid.Web/Areas/User/Views/RecordInspections/Programs.cshtml and the related locations listed: style="font-family:monospace" embeds presentation details directly in the view. Move the styling to a scoped CSS class such as checklist-textarea.

Suggested Code:

					<div class="form-group"><label class="col-sm-3 control-label">@L["Checklist"]</label><div class="col-sm-9"><textarea class="form-control checklist-textarea" asp-for="ChecklistText" rows="10"></textarea><span class="help-block">@L["ChecklistHelp"]</span></div></div>

Talk to Kody by mentioning @kody

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

@Html.AntiForgeryToken()<input type="hidden" name="id" value="@c.RmsInvestigationCaseId" /><input type="hidden" name="rowVersion" value="@c.RowVersion" />
<div class="form-group"><label class="col-sm-3 control-label">@L["Title"]</label><div class="col-sm-9"><input class="form-control" name="title" value="@Text(c.Title)" maxlength="200" readonly="@(!Model.CanWrite)" /></div></div>
<div class="form-group"><label class="col-sm-3 control-label">@L["OpenedOn"]</label><div class="col-sm-9"><p class="form-control-static">@RmsEnumDisplay.Utc(c.OpenedOn) · @Model.UserName(c.OpenedByUserId)</p></div></div>
<div class="form-group"><label class="col-sm-3 control-label">@L["LeadInvestigator"]</label><div class="col-sm-9">@if (Model.IsLead && !c.IsClosed) { <select class="form-control" name="leadInvestigatorUserId">@foreach (var m in Model.Members) { <option value="@m.Value" selected="@(m.Value == c.LeadInvestigatorUserId)">@m.Text</option> }</select> } else { <p class="form-control-static">@Model.UserName(c.LeadInvestigatorUserId)</p><input type="hidden" name="leadInvestigatorUserId" value="@c.LeadInvestigatorUserId" /> }</div></div>

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 Security critical

Privilege escalation in Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml: the form posts leadInvestigatorUserId for non-lead investigators through a hidden field, and RecordInvestigationsController.Update forwards that value into RecordsInvestigationsService.UpdateAsync before the service overwrites LeadInvestigatorUserId. Stop posting leadInvestigatorUserId for non-leads and enforce the lead-only authorization check server-side before applying any lead-investigator change.

<div class="form-group"><label class="col-sm-3 control-label">@L["LeadInvestigator"]</label><div class="col-sm-9">@if (Model.IsLead && !c.IsClosed) { <select class="form-control" name="leadInvestigatorUserId">@foreach (var m in Model.Members) { <option value="@m.Value" selected="@(m.Value == c.LeadInvestigatorUserId)">@m.Text</option> }</select> } else { <p class="form-control-static">@Model.UserName(c.LeadInvestigatorUserId)</p> }</div></div>
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml:

Line 40:

Privilege escalation in Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml: the form posts leadInvestigatorUserId for non-lead investigators through a hidden field, and RecordInvestigationsController.Update forwards that value into RecordsInvestigationsService.UpdateAsync before the service overwrites LeadInvestigatorUserId. Stop posting leadInvestigatorUserId for non-leads and enforce the lead-only authorization check server-side before applying any lead-investigator change.

Suggested Code:

<div class="form-group"><label class="col-sm-3 control-label">@L["LeadInvestigator"]</label><div class="col-sm-9">@if (Model.IsLead && !c.IsClosed) { <select class="form-control" name="leadInvestigatorUserId">@foreach (var m in Model.Members) { <option value="@m.Value" selected="@(m.Value == c.LeadInvestigatorUserId)">@m.Text</option> }</select> } else { <p class="form-control-static">@Model.UserName(c.LeadInvestigatorUserId)</p> }</div></div>

Talk to Kody by mentioning @kody

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

</div>
<div class="col-sm-6">
<div class="btn-group top-page-buttons" style="float:right;padding-right:15px;">
<a class="btn btn-default" asp-controller="RecordInvestigations" asp-action="Export" asp-route-area="User" asp-route-id="@c.RmsInvestigationCaseId"><i class="fa fa-download"></i> @L["ExportCase"]</a>

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

Sensitive export authorization gap in Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml: the ExportCase action is exposed without any visible consent or lawful-basis workflow for sensitive investigation or health-related data. Require an explicit consent record, or equivalent authorization artifact, and pass its identifier into the export operation.

Kody rule violation: Require explicit consent before processing sensitive data

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml:

Line 24:

Sensitive export authorization gap in Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Details.cshtml: the ExportCase action is exposed without any visible consent or lawful-basis workflow for sensitive investigation or health-related data. Require an explicit consent record, or equivalent authorization artifact, and pass its identifier into the export operation.

Talk to Kody by mentioning @kody

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

</div>
<div class="col-sm-6">
<div class="btn-group top-page-buttons" style="float:right;padding-right:15px;">
<a class="btn btn-success" asp-controller="RecordInvestigations" asp-action="Open" asp-route-area="User"><i class="fa fa-plus"></i> @L["OpenCase"]</a>

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 Security critical

Authorization bypass in Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Index.cshtml: the Open case entry point is visible to any user who can reach the investigations index, while RecordInvestigationsController is limited to RecordRestricted_View and RecordsInvestigationsService.OpenAsync only calls RequireRestrictedAsync before creating the case and assigning the caller as lead. Gate the Open link behind the same write-role check used elsewhere and require stronger server-side authorization than restricted-view for Open.

@if (Model.CanAdminister)
{
    <a class="btn btn-success" asp-controller="RecordInvestigations" asp-action="Open" asp-route-area="User"><i class="fa fa-plus"></i> @L["OpenCase"]</a>
}
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Index.cshtml:

Line 19:

Authorization bypass in Web/Resgrid.Web/Areas/User/Views/RecordInvestigations/Index.cshtml: the Open case entry point is visible to any user who can reach the investigations index, while RecordInvestigationsController is limited to RecordRestricted_View and RecordsInvestigationsService.OpenAsync only calls RequireRestrictedAsync before creating the case and assigning the caller as lead. Gate the Open link behind the same write-role check used elsewhere and require stronger server-side authorization than restricted-view for Open.

Suggested Code:

@if (Model.CanAdminister)
{
    <a class="btn btn-success" asp-controller="RecordInvestigations" asp-action="Open" asp-route-area="User"><i class="fa fa-plus"></i> @L["OpenCase"]</a>
}

Talk to Kody by mentioning @kody

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


@if (Has(Model.TacticalSummary))
{
<div class="alert alert-info"><strong>@localizer["TacticalSummary"]:</strong> <span data-adp-field="@Adp("tacticalsummary")">@Model.TacticalSummary</span></div>

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

Sensitive data exposure in Web/Resgrid.Web/Areas/User/Views/Shared/_ContactPreplanSummary.cshtml and the related locations listed: @Model.TacticalSummary renders operational preplan content that can qualify as PHI/ePHI without minimization. Render only the minimum necessary metadata or a redacted placeholder unless the view is explicitly covered by PHI handling and auditing controls.

Kody rule violation: Do not log PHI; mask and drop sensitive fields

<div class="alert alert-info"><strong>@localizer["TacticalSummary"]:</strong> <span data-adp-field="@Adp("tacticalsummary")">[REDACTED]</span></div>
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Shared/_ContactPreplanSummary.cshtml:

Line 23:

Sensitive data exposure in Web/Resgrid.Web/Areas/User/Views/Shared/_ContactPreplanSummary.cshtml and the related locations listed: @Model.TacticalSummary renders operational preplan content that can qualify as PHI/ePHI without minimization. Render only the minimum necessary metadata or a redacted placeholder unless the view is explicitly covered by PHI handling and auditing controls.

Suggested Code:

    <div class="alert alert-info"><strong>@localizer["TacticalSummary"]:</strong> <span data-adp-field="@Adp("tacticalsummary")">[REDACTED]</span></div>

Talk to Kody by mentioning @kody

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

Comment on lines +26 to +33
try
{
var prevention = await scope.Resolve<IRecordsPreventionSweepService>().SweepAsync(cancellationToken);
Logging.LogInfo($"Prevention sweep: departments={prevention.DepartmentsEvaluated} inspections={prevention.InspectionsGenerated} violationsOverdue={prevention.ViolationsBecameOverdue} permitsNotified={prevention.PermitsExpiringNotified} permitsExpired={prevention.PermitsExpired} errors={prevention.Errors}");
}
catch (Exception ex)
{
Logging.LogException(ex, "Prevention sweep failed; the due-state result still stands.");

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

Error suppression in Workers/Resgrid.Workers.Framework/Logic/RmsDueStateEvaluationLogic.cs: the prevention sweep exception is caught, logged, and discarded, so Process still reports success while inspections, violations, and permit expiry processing remain stale. Propagate the failure through the returned tuple or rethrow after logging so job monitoring and retries can detect the failed prevention sweep.

var prevention = await scope.Resolve<IRecordsPreventionSweepService>().SweepAsync(cancellationToken);
Logging.LogInfo($"Prevention sweep: departments={prevention.DepartmentsEvaluated} inspections={prevention.InspectionsGenerated} violationsOverdue={prevention.ViolationsBecameOverdue} permitsNotified={prevention.PermitsExpiringNotified} permitsExpired={prevention.PermitsExpired} errors={prevention.Errors}");
Prompt for LLM

File Workers/Resgrid.Workers.Framework/Logic/RmsDueStateEvaluationLogic.cs:

Line 26 to 33:

Error suppression in Workers/Resgrid.Workers.Framework/Logic/RmsDueStateEvaluationLogic.cs: the prevention sweep exception is caught, logged, and discarded, so Process still reports success while inspections, violations, and permit expiry processing remain stale. Propagate the failure through the returned tuple or rethrow after logging so job monitoring and retries can detect the failed prevention sweep.

Suggested Code:

var prevention = await scope.Resolve<IRecordsPreventionSweepService>().SweepAsync(cancellationToken);
Logging.LogInfo($"Prevention sweep: departments={prevention.DepartmentsEvaluated} inspections={prevention.InspectionsGenerated} violationsOverdue={prevention.ViolationsBecameOverdue} permitsNotified={prevention.PermitsExpiringNotified} permitsExpired={prevention.PermitsExpired} errors={prevention.Errors}");

Talk to Kody by mentioning @kody

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

@ucswift

ucswift commented Sep 8, 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 5a54729 into master Sep 8, 2026
17 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.

3 participants